The way to play back break point envelopes is inspired by the SuperCollider envelope generator. The function to create a generic envelope is MAKE-ENVELOPE, for example:

SCRATCH> (defparameter env1 (make-envelope '(0 1 0) '(.2 .8)))

The first argument of MAKE-ENVELOPE is a list of levels and the second argument is a list of times in seconds to specify the durations of the segments. For example, the break-point pairs of the envelope ENV1 are:

time level
0 0
0.2 1
1.0 0

We can set the shape of the segments by using the :CURVE keyword. The default is :LINEAR and the possible values are

The :BASE keyword is an alternative to :CURVE. If BASE is a number, it is the envelope's base in the style of the sound synthesis package CLM (Common Lisp Music), where BASE is e^k and the curvature depends on the highest and lowest levels. CURVE is ignored if BASE is non-NIL. The utility ENVELOPE-BASE->CURVES returns the list of curvature values for the :CURVE keyword related to an envelope's base in CLM style:

SCRATCH> (envelope-base->curves 10 '(0 1 .5 0))
(2.3025851 -0.597837 -1.704748)

If the envelope is sustained, the :RELEASE-NODE keyword specifies the release point (starting from 0). The default is -1 that means "envelope without sustain".

If the :LOOP-NODE keyword has a non negative value, it is the starting point of the loop of the segments during the sustain phase of the envelope. The ending point is the point that precedes the release point.

BREAKPOINTS->ENV and FREQ-BREAKPOINTS->ENV create and return a new ENVELOPE from a sequence of break-point pairs. BREAKPOINTS->ENV with the keywords BASE, SCALER, OFFSET and DURATION defines an envelope in the style of CLM, for example:

(defun* make-clm-env (breakpoint-list (scaler 1.0) duration (offset 0.0)
                      base end length)
  (breakpoints->env breakpoint-list :scaler scaler :offset offset
                    :base base
                    :duration (or duration
                                  (and end (* end *sample-duration*))
                                  (and length (* length *sample-duration*)))))

There are functions to create frequently used envelope shapes:

We can edit an existent envelope with

For example:

SCRATCH> (edit-envelope env1 :adsr '(.15 .09 .85 1.5))
#<ENVELOPE :POINTS 4 :LOOP-NODE -1 :RELEASE-NODE 2>
SCRATCH> (mapcar (lambda (point) (envelope-level env1 point)) '(0 1 2 3))
(9.999999747378752d-6 1.0d0 0.8500000238418579d0 9.999999747378752d-6)
SCRATCH> (setf (envelope-level env1 2) 0.82)
0.8199999928474426d0

Here is a DSP to test a global envelope (defined out of the DSP):

(dsp! env-test ((env envelope) gate amp dur)
  (stereo (* (envelope env gate dur #'free)
             (white-noise amp))))

SCRATCH> (env-test env1 1 .3 1 :id 1)
SCRATCH> (set-control 1 :gate 0)
0
SCRATCH> (env-test env1 1 .3 1 :id 1)
SCRATCH> (set-control 1 :gate -1) ; immediate cutoff
-1
SCRATCH> (env-test env1 1 .3 1 :id 1)
SCRATCH> (set-control 1 :gate -5.2) ; release in 4.2 seconds
-5

The ENVELOPE VUG plays back the segments of an ENVELOPE. When GATE is 0, it starts the release phase of the envelope. If GATE is -1, there is an immediate cutoff. If GATE is less than -1, there is a release stage with a custom duration (- -1.0 GATE).

The fourth argument of ENVELOPE is DONE-ACTION, a one-argument function called at the end of the envelope. The function argument is the DSP node. In the previous example, the DSP terminates at the end of the envelope, so we cannot retrig the envelope after the end. A little variation is

(dsp! env-test-2 ((env envelope) gate amp dur)
  (stereo (* (envelope env gate dur #'identity)
             (white-noise amp))))

SCRATCH> (env-test-2 env1 1 .3 1 :id 1)
SCRATCH> (set-control 1 :gate 0)
0
SCRATCH> (set-control 1 :gate 1) ; retrig after the release
1
SCRATCH> (set-control 1 :gate 0)
0
SCRATCH> (free 1)

The DSP is alive after the release because the DONE-ACTION function is simply #'IDENTITY.

The DSP-SEQ defines a sequence of DSP's by using the STOP-HOOK of the functions defined by DSP!. The last argument is an arbitrary form useful to define recursive sequences. For example:

(defvar env2 (make-perc .001 .4))

(dsp! env-test-3 (freq amp pos (env envelope) gate)
  (foreach-channel
    (cout (pan2 (* (envelope env gate 1 #'stop)
                   (sine freq amp 0))
                pos))))

(defun seq-test (rep freq amp pos)
  (when (plusp rep)
    (dsp-seq (env-test-3 freq amp pos env2 1)
             (env-test-3 (* freq 7/4) amp pos env2 1)
             (env-test-3 (* freq 2) amp pos env2 1)
             (seq-test (1- rep) freq amp pos))))

(defun phr1 (time)
  (at time #'seq-test 8 200 .3 .5)
  (at (+ time #[2 b]) #'seq-test 6 400 .3 .4)
  (at (+ time #[4 b]) #'seq-test 4 600 .3 .6))

SCRATCH> (setf (bpm *tempo*) 120)
120
SCRATCH> (phr1 (now))

In this example the DONE-ACTION function of the envelope is #'STOP. The macro expansion of DSP-SEQ shows the stop hooks:

SCRATCH> (macroexpand-1
          '(dsp-seq (env-test-3 freq amp pos env2 1)
                    (env-test-3 (* freq 7/4) amp pos env2 1)
                    (env-test-3 (* freq 2) amp pos env2 1)
                    (seq-test (1- rep) freq amp pos)))
(ENV-TEST-3 FREQ AMP POS ENV2 1 :STOP-HOOK
            (LIST
             (LAMBDA (#:N912)
               (DECLARE (IGNORE #:N912))
               (ENV-TEST-3 (* FREQ 7/4) AMP POS ENV2 1 :STOP-HOOK
                           (LIST
                            (LAMBDA (#:N912)
                              (DECLARE (IGNORE #:N912))
                              (ENV-TEST-3 (* FREQ 2) AMP POS ENV2 1 :STOP-HOOK
                                          (LIST
                                           (LAMBDA (#:N912)
                                             (DECLARE (IGNORE #:N912))
                                             (SEQ-TEST (1- REP) FREQ AMP
                                                       POS))))))))))
T

SCALE-ENVELOPE, NORMALIZE-ENVELOPE and RESCALE-ENVELOPE are utilities to scale the envelope levels. For example:

(dsp! env-test-4 ((env envelope) gate amp dur)
  (stereo (sine (envelope env gate dur #'identity) amp 0)))

(defun retrig-test ()
  (rt-eval ()
    (let ((time (now)))
      (at time #'set-control 1 :gate 0)
      (at (+ time #[1 s]) #'set-control 1 :gate 1)
      (at (+ time #[2.5 s]) #'set-control 1 :gate 0)
      (at (+ time #[6 s]) #'set-control 1 :gate 1))))

SCRATCH> (rescale-envelope env1 220 880)
#<ENVELOPE :POINTS 4 :LOOP-NODE -1 :RELEASE-NODE 2>
SCRATCH> (env-test-4 env1 1 .3 8 :id 1)
SCRATCH> (retrig-test)
SCRATCH> (free 1)

We can also define local envelopes, for example:

(dsp! env-test-5 (freq amp pos gate
                  (a single-float) (d single-float)
                  (s single-float) (r single-float))
  (foreach-channel
    (cout (pan2 (* (envelope (make-adsr a d s r) gate 1 #'stop)
                   (sine freq amp 0))
                pos))))

SCRATCH> (env-test-5 440 .2 .1 1 0.001 .09 .9  0.02 :id 1)
SCRATCH> (env-test-5 448 .2 .9 1 1.5   .5  .7  2    :id 2)
SCRATCH> (env-test-5 111 .2 .5 1 .1    .1  .99 4    :id 3)
SCRATCH> (set-control 0 :gate 0)

The structure TEMPO-ENVELOPE is specific for a temporal envelope. The constructor is MAKE-TEMPO-ENVELOPE, and SET-TEMPO-ENVELOPE allows to change an existent instance.

;;; The utilities to get time, SPB, BPS and BPM are:

(beats->seconds tempo-env beats &optional offset)

(seconds->beats tempo-env seconds &optional offset)

(spb-at tempo-env beats)

(bps-at tempo-env beats)

(bpm-at tempo-env beats)

;;; The syntax for the #[... b.* ...] read-macro is:

#[NUM-OF-BEATS b.*]   ; *TEMPO* by default

#[NUM-OF-BEATS b.* TEMPO]

#[NUM-OF-BEATS b.* TEMPO-ENVELOPE OFFSET-IN-BEATS]

;;; Example:

;;; After 8 beats, there is an acceleration from 60 to 120 bpm in 4 beats,
;;; with coeff 4 for the curvature. Then, after 2 beats, there is a
;;; deceleration from 120 to 96 bpm in 2 beats, with sinusoidal curvature.
SCRATCH> (defvar *tenv1* (make-tempo-envelope '(60 60 120 120 96) '(8 4 2 2)
                                              :curve '(:step 4 :step :sin)))
*TENV1*
SCRATCH> (loop for beats below 20 by 0.5 collect (beats->seconds *tenv1* beats))
(0.0d0 0.5d0 1.0d0 1.5d0 2.0d0 2.5d0 3.0d0 3.5d0 4.0d0 4.5d0 5.0d0 5.5d0 6.0d0
 6.5d0 7.0d0 7.5d0 8.0d0 8.498612626829395d0 8.993299378541845d0
 9.481513456442876d0 9.959055899352716d0 10.419003790659431d0
 10.849943170489647d0 11.233055600417206d0 11.537314720727547d0
 11.787314720727547d0 12.037314720727547d0 12.287314720727547d0
 12.537314720727547d0 12.790429835847638d0 13.060025984954574d0
 13.352929835847638d0 13.662314720727547d0 13.974814720727547d0
 14.287314720727547d0 14.599814720727547d0 14.912314720727547d0
 15.224814720727547d0 15.537314720727547d0 15.849814720727547d0)
SCRATCH> (loop for beats below 20 by 0.5 collect (spb-at *tenv1* beats))
(1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0
 1.0d0 1.0d0 1.0d0 1.0d0 0.9939482867384511d0 0.9839706983599575d0
 0.9675204361700447d0 0.9403985389889412d0 0.8956820902047142d0
 0.8219571299439862d0 0.7004052197806022d0 0.5d0 0.5d0 0.5d0 0.5d0 0.5d0
 0.5183058261758408d0 0.5625d0 0.6066941738241592d0 0.625d0 0.625d0 0.625d0
 0.625d0 0.625d0 0.625d0 0.625d0 0.625d0)
SCRATCH> (loop for beats below 20 by 0.5 collect (bpm-at *tenv1* beats))
(60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0
 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.0d0 60.36531356866102d0
 60.97742554733141d0 62.01419397145924d0 63.80273629998226d0
 66.98805374827423d0 72.99650774254955d0 85.6646956725917d0 120.0d0 120.0d0
 120.0d0 120.0d0 120.0d0 115.76177030980232d0 106.66666666666667d0
 98.8966147833654d0 96.0d0 96.0d0 96.0d0 96.0d0 96.0d0 96.0d0 96.0d0 96.0d0)
SCRATCH> (loop for beats below 20 by 0.5 collect (bps-at *tenv1* beats))
(1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0 1.0d0
 1.0d0 1.0d0 1.0d0 1.0d0 1.0060885594776836d0 1.0162904257888568d0
 1.0335698995243208d0 1.0633789383330376d0 1.116467562471237d0
 1.216608462375826d0 1.4277449278765284d0 2.0d0 2.0d0 2.0d0 2.0d0 2.0d0
 1.9293628384967052d0 1.7777777777777777d0 1.64827691305609d0 1.6d0 1.6d0 1.6d0
 1.6d0 1.6d0 1.6d0 1.6d0 1.6d0)
SCRATCH> *sample-rate*
48000.0d0
SCRATCH> (loop for beats below 20 by 0.5 collect #[1 beat *tenv1* beats])
(48000.0d0 48000.0d0 48000.0d0 48000.0d0 48000.0d0 48000.0d0 48000.0d0
 48000.0d0 48000.0d0 48000.0d0 48000.0d0 48000.0d0 48000.0d0 48000.0d0
 48000.0d0 47933.40608781097d0 47678.37017000858d0 47179.23982144708d0
 46356.31299892179d0 44999.536042394655d0 42762.58901457268d0
 39074.486868373184d0 32993.834411419215d0 26604.43777489638d0 24000.0d0
 24000.0d0 24000.0d0 24149.525525764348d0 25090.140682897298d0 27000.0d0
 28909.859317102702d0 29850.474474235652d0 30000.0d0 30000.0d0 30000.0d0
 30000.0d0 30000.0d0 30000.0d0 30000.0d0 30000.0d0)

A BUFFER structure holds an array of type SAMPLE and other informations useful to interpret the data. The MAKE-BUFFER function creates a new BUFFER. For example, a one channel buffer with 8192 frames:

SCRATCH> (defvar buf-test (make-buffer 8192))

There are some facilities to fill the buffer data, for example by using a function which takes two arguments: a foreign array and the array size. The gen routines in the INCUDINE.GEN package return this type of function.

SCRATCH> (defvar waveform-1 (make-buffer 65536 :fill-function (gen:partials '(1))))
SCRATCH> WAVEFORM-1
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>

The WAVEFORM-1 buffer data represent one period of a sinusoid created by GEN:PARTIALS.

(dsp! wt-lookup-test ((buf buffer) freq amp)
  (stereo (osc buf freq amp 0 :cubic)))

SCRATCH> (wt-lookup-test waveform-1 440 .3)
SCRATCH> (free 0)

GEN:PARTIALS returns a function called to fill a foreign array with a composite waveform made up of weighted sums of sinusoids. The argument is a list where every element can be:

Examples:

SCRATCH> (fill-buffer waveform-1 (gen:partials '(1.0 0.0 0.5 0.0 0.25 0.0 0.125
                                                 0.0 0.0625)))
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (wt-lookup-test waveform-1 440 .3 :id 1)
SCRATCH> (defvar waveform-2
                 (make-buffer 65536 :fill-function (gen:partials '(1 (3 .5) (7 .2 .75)))))
SCRATCH> (set-control 1 :buf waveform-2)
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (fill-buffer waveform-1 (gen:partials '(16 8 4 2 1)) :normalize-p t)
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (set-control 1 :buf waveform-1)
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (free 1)

(dsp! wt-lookup-test-2 ((buf buffer) amp rep)
  (stereo (osc *sine-table* (osc buf rep) amp 0 :cubic)))

SCRATCH> (rescale-buffer waveform-2 100 1500)
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (wt-lookup-test-2 waveform-2 .3 .2 :id 1)
SCRATCH> (set-control 1 :rep .5)
0.5
SCRATCH> (rt-eval () (rescale-buffer waveform-2 100 800))
NIL
SCRATCH> (fill-buffer waveform-1 (gen:partials '((1 800 .75 1000))
                                               :normalize-p nil))
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (set-control 1 :buf waveform-1)
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (free 1)

There are other gen routines, for example GEN:GBUZZ, GEN:RAND, and GEN:POLYNOMIAL.

Other useful functions to edit a BUFFER are MAP-BUFFER, MAP-INTO-BUFFER, SCALE-BUFFER, NORMALIZE-BUFFER and RESCALE-BUFFER.

We can use a BUFFER to store the data of a soundfile. The format of the soundfile is one of the formats available in libsndfile.

(dsp! bplay ((buf buffer) rate start-pos (loop-p boolean))
  (foreach-channel
    (cout (buffer-play buf rate start-pos loop-p #'stop))))

SCRATCH> (defvar loop-1 (buffer-load "/home/test/loop-1.wav"))
SCRATCH> (bplay loop-1 1 0 t :id 1)
SCRATCH> (set-control 1 :rate .3)
0.3
SCRATCH> (set-control 1 :rate 2)
2
SCRATCH> (set-controls 1 :rate 1 :start-pos #[300 ms])
NIL
SCRATCH> (defvar loop-2 (make-buffer 0 :file "/home/test/loop-2.wav"))
LOOP-2
SCRATCH> (set-control 1 :buf loop-2)
#<BUFFER :FRAMES 310464 :CHANNELS 2 :SR 44100.0>
SCRATCH> (fill-buffer waveform-2 "/home/test/loop-3.wav")
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (set-control 1 :buf waveform-2)
#<BUFFER :FRAMES 65536 :CHANNELS 1 :SR 48000.0>
SCRATCH> (set-control 1 :loop-p nil)
NIL

Here is an example obtained from a Pd patch:

(dsp! b09-sampler-loop-smooth ((buf buffer) freq chunk-size)
  (with-samples ((phs (phasor freq 0)))
    (stereo (* (cos (* (- phs 0.5d0) pi))
               (buffer-read buf (* phs 441
                                   (samphold chunk-size phs 0 1))
                            :wrap-p t :interpolation :cubic)))))

SCRATCH> (b09-sampler-loop-smooth loop-1 .75 50 :id 1)
SCRATCH> (set-controls 1 :chunk-size 20 :freq 10)
SCRATCH> (set-control 1 :freq 30)
SCRATCH> (set-controls 1 :chunk-size 100 :freq 1)
SCRATCH> (free 1)

BUFFER-RECORD is the combination of BUFFER-WRITE and COUNTER VUGs:

(define-vug buffer-record ((buf buffer) in)
  (buffer-write buf (counter 0 (buffer-size buf) :loop-p t) in))

(dsp! buffer-record-test ((buf buffer))
  (when (zerop current-channel)
    (buffer-record buf (audio-in 0))))

(dsp! buffer-play-test ((buf buffer))
  (out (buffer-play buf 1 0 t #'free)))

SCRATCH> (defvar btest (make-buffer 44100))
SCRATCH> (buffer-record-test btest :id 1)
SCRATCH> (buffer-play-test btest :id 2)
SCRATCH> (free 1)
SCRATCH> (buffer-record-test btest :id 1)
SCRATCH> (free 1)
SCRATCH> (free 2)

A low pass brickwall filter is a good example to introduce the FFT analysis with Incudine. The DSP in the following example uses the mouse (it works only with X window system at moment) to control the cutoff of the filter.

(define-vug pv-lp-wall ((abuf abuffer) threshold)
  (dofft-polar (i nbins ((compute-abuffer abuf)) () :result abuf)
    (if (>= i (sample->fixnum threshold))
        (setf mag0 0.0d0))))

(dsp! pv-lp-wall-test ((buf buffer) (size fixnum) (hop-size fixnum) amp)
  (with ((fft (make-fft size))
         (abuf (make-abuffer fft))
         (ifft (make-ifft size))
         (d-nbins (sample (abuffer-nbins abuf))))
    (declare (type sample d-nbins))
    (setf (fft-input fft) (buffer-play buf 1 0 t #'identity))
    (with-control-period (hop-size)
      (compute-ifft ifft (pv-lp-wall abuf (lag (lin-mouse-x 0 d-nbins) .02))))
    ;; IFFT-OUTPUT is not a VUG or a UGEN, therefore it is not performance-time
    ;; by default. We can change this behavior by using TICK
    (stereo (* amp (tick (ifft-output ifft))))))

SCRATCH> (new-fft-plan 1024 +fft-plan-best+)
#<FFT-PLAN :SIZE 1024 :FLAGS 0>
SCRATCH> (pv-lp-wall-test loop-1 1024 512 .5 :id 1)
SCRATCH> (set-control 1 :hop-size 800)
800
SCRATCH> (set-control 1 :hop-size 2048)
2048
SCRATCH> (new-fft-plan 4096)
#<FFT-PLAN :SIZE 4096 :FLAGS 64>
SCRATCH> (set-control 1 :size 4096)
4096
SCRATCH> (free 1)

When we use a FFT-PLAN for the first time, it is better to calculate and store a new FFT-PLAN in non-realtime with the utility NEW-FFT-PLAN as in the prior example to avoid xruns. The possible values for the FLAGS argument are +FFT-PLAN-OPTIMAL+, +FFT-PLAN-BEST+ and +FFT-PLAN-FAST+ (default). GET-FFT-PLAN returns a FFT-PLAN related to a specific size:

SCRATCH> (get-fft-plan 1024)
#<FFT-PLAN :SIZE 1024 :FLAGS 0>
SCRATCH> (get-fft-plan 4096)
#<FFT-PLAN :SIZE 4096 :FLAGS 64>
SCRATCH> (get-fft-plan 2048)
NIL
SCRATCH> (new-fft-plan 2048 +fft-plan-optimal+)
#<FFT-PLAN :SIZE 2048 :FLAGS 32>
SCRATCH> (new-fft-plan 2048)
#<FFT-PLAN :SIZE 2048 :FLAGS 32>
SCRATCH> (fft-plan-list)
(#<FFT-PLAN :SIZE 1024 :FLAGS 0> #<FFT-PLAN :SIZE 2048 :FLAGS 32>
 #<FFT-PLAN :SIZE 4096 :FLAGS 64>)

PV-LP-WALL-TEST is defined with a local FFT, a local ABUFFER (Analysis Buffer) related to FFT and a local IFFT.

The input of WITH-CONTROL-PERIOD VUG-MACRO is updated every N samples, on demand or never.

In the example the output is calculated every HOP-SIZE samples. It is interesting to notice that we can change the size of the FFT/IFFT size and the HOP-SIZE in realtime.

FFT-INPUT sets the input of the local FFT.

COMPUTE-ABUFFER updates the ABUFFER with a copy of the calculated FFT and returns the ABUFFER.

The DOFFT-POLAR macro is an utility to change the values of one or more ABUFFERs in polar coordinates. MAG0 and PHASE0 are the magnitude and the phase of the nth bin in the first ABUFFER respectively, MAG1 and PHASE1 for the bin in the second ABUFFER, etc. There is also the DOFFT-COMPLEX macro for the calculations in complex coordinates, and the more flexible DOFFT macro.

IFFT-OUTPUT returns the next sample of the last calculated IFFT.

The next example, inspired by PVLocalMax of SuperCollider, shows the DOFFT-POLAR macro with two ABUFFERs:

(define-vug pv-local-max ((abuf-src abuffer) (abuf-dest abuffer) threshold)
  (dofft-polar (i nbins ((compute-abuffer abuf-src)) (abuf-dest)
                :result abuf-dest :index-start 1 :index-end (1- nbins))
    (if (or (< mag0 threshold)
            (< (abuffer-realpart abuf-src (1- i)) threshold)
            (< (abuffer-realpart abuf-src (1+ i)) threshold))
        (setf mag1 0.0d0 phase1 0.0d0)
        (setf mag1 mag0 phase1 phase0))))

(dsp! pv-local-max-test ((buf buffer) (size fixnum) (hop-size fixnum) amp)
  (with ((fft (make-fft size))
         (abuf0 (make-abuffer fft))
         (abuf1 (make-abuffer fft))
         (ifft (make-ifft size)))
    (setf (fft-input fft) (buffer-play buf 1 0 t #'identity))
    (with-control-period (hop-size)
      (compute-ifft ifft (pv-local-max abuf0 abuf1
                                       (lag (lin-mouse-x 0 50) .02))))
    (stereo (* amp (tick (ifft-output ifft))))))

SCRATCH> (pv-local-max-test loop-1 1024 512 .5)
SCRATCH> (free 0)

DOFFT-POLAR takes two lists of ABUFFERs, ABUFFER-SRC-LIST and ABUFFER-DEST-LIST. The data of the ABUFFERs in ABUFFER-SRC-LIST are automatically converted from complex to polar form if necessary.

Here is another example about FFT, where it is possible to modulate the HOP-SIZE using the mouse:

(dsp! hop-size-mod ((size fixnum) amp)
  (with ((fft (make-fft size))
         (abuf (make-abuffer fft))
         (ifft (make-ifft size)))
    (setf (fft-input fft) (sine 440 .5 0))
    (with-control-period ((sample->fixnum (lag (lin-mouse-x 150 10000) .02)))
      (compute-ifft ifft abuf))
    (stereo (* amp (tick (ifft-output ifft))))))

SCRATCH> (hop-size-mod 1024 .5)
SCRATCH> (free 0)

The PVBUFFER structure is used to store a sequence of spectral data. For example, an instance of PVBUFFER is required by PART-CONVOLVE, a VUG to compute the partitioned convolution between a signal and a multi-channel impulse response:

(defvar *btest* (buffer-load "/path/to/someloop.wav"))

(defvar *auditorium*
  (make-part-convolve-buffer (buffer-load "/path/to/auditorium.wav") 8192))

;;; There are two keywords in MAKE-PART-CONVOLVE-BUFFER, START and FRAMES,
;;; the offset and the number of frames of the input buffer, respectively.
;;; The whole buffer is the default.

(dsp! pconv-test ((inbuf buffer) (pvbuf pvbuffer) dry wet)
  (with-samples ((in (buffer-play inbuf 1 0 t #'free)))
    (stereo (* dry (delay-s in 65536
                            (ash (pvbuffer-fft-size pvbuf) -1))))
    (foreach-channel (cout (* wet (part-convolve in pvbuf))))))

SCRATCH> (pconv-test *btest* *auditorium* .3 .08 :id 1)

;;; It is possible to change the PVBUFFER during the playback:

(defvar *grotte-frasassi*
  (make-part-convolve-buffer (buffer-load "/path/to/grotte_frasassi.wav") 8192))

SCRATCH> (set-control 1 :pvbuf *grotte-frasassi*)
SCRATCH> (set-control 1 :pvbuf *auditorium*)
SCRATCH> (free 1)

Sourceforge project page