Getting Started with Incudine | ||
---|---|---|
Part 1 | Part 2 | Part 3 |
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
- :STEP
- :LIN or :LINEAR
- :EXP or :EXPONENTIAL
- :SIN or :SINE
- :WEL or :WELCH
- :SQR or :SQUARE
- :CUB or :CUBIC
- a number that represents the curvature value for all the segments
- a list of the prior values to specify the curvature values for each segment
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:
- MAKE-LINEN
- MAKE-PERC
- MAKE-CUTOFF
- MAKE-ASR
- MAKE-ADSR
- MAKE-DADSR
We can edit an existent envelope with
- EDIT-ENVELOPE
- SET-ENVELOPE-BASE
- (SETF ENVELOPE-LEVEL)
- (SETF ENVELOPE-TIME)
- (SETF ENVELOPE-CURVE)
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:
-
value – relative stregth of the partial. A negative value
implies a phase inversion. The number of the partial is the position
in the list plus a possible offset introduced by a previous list
(partial-number strength [phase] [dc])
. The following examples generate the same waveform:(partials '(1 0 .5 0 0 0 .2 .1)) (partials '(1 0 .5 (7 .2) .1))
Partials 1, 3, 7 and 8 respectively with relative strengths 1, .5, .2 and .1
-
(partial-number strength)
–STRENGTH
is the strength of the partialPARTIAL-NUMBER
(not necessarily an integer value). A negativeSTRENGTH
value implies a phase inversion. -
(partial-number strength phase)
–PHASE
is the initial phase of the partial. It is a multiplier for+TWOPI+
(partial-number strength phase dc)
–DC
is the DC offset of the partial.
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.
-
If
N
is positive, the input is updated every N samples. -
If
N
is zero, the input is not updated. -
If
N
is negative, the input is updated and N becomes zero.
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)
Part 1 | Home | Part 3 |