Getting Started with Incudine | ||
---|---|---|
Part 2 | Part 3 | Part 4 |
A BUS
is a place where to store a value of
SAMPLE
type. For example:
(dsp! get-bus-test () (foreach-channel (cout (pan2 (sine (bus 0) (bus 1) 0) (bus 2))))) (dsp! mod-bus-test (fcar fmod amod) (setf (bus 0) (+ fcar (sine fmod amod 0)))) (defun set-bus-test (freq amp pos) (rt-eval () (setf (bus 0) freq (bus 1) amp (bus 2) pos))) (defun bus-test-list () (rt-eval (:return-value-p t) (list (bus 0) (bus 1) (bus 2)))) SCRATCH> *number-of-bus-channels* 4096 SCRATCH> (set-bus-test 440 .3 .5) SCRATCH> (get-bus-test :id 1) SCRATCH> (set-bus-test 110 .4 .1) SCRATCH> (set-bus-test 880 .1 .9) SCRATCH> (set-bus-test 220 .3 .5) SCRATCH> (mod-bus-test 440 8 50 :id 2 :before 1) SCRATCH> (bus-test-list) (489.87698199469247d0 0.30000001192092896d0 0.5d0) SCRATCH> (set-control 2 :fmod 113) SCRATCH> (free 2) SCRATCH> (bus-test-list) (390.2414056116658d0 0.30000001192092896d0 0.5d0) SCRATCH> (free 1) SCRATCH> (set-bus-test 0 0 0)
Here is an example to test some VUGs dedicated to MIDI messages:
(dsp! midi-test () (foreach-channel (cout (pan2 (bpf (white-noise (lin-midi-cc 0 7 0 1)) (lag (exp-midi-cc 0 8 50 5000) .02) (lag (lin-midi-cc 0 9 .1 18) .02)) (lin-midi-cc 0 10 0 1))))) SCRATCH> (pm:initialize) :PM-NO-ERROR SCRATCH> (defvar *midiin* (pm:open (pm:get-default-input-device-id))) *MIDIIN* SCRATCH> (recv-start *midiin* :timeout 64) #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING> SCRATCH> (midi-test) ;; Set the CC MIDI 7 (volume), 8 (cutoff frequency), 9 (Q factor) ;; and 10 (panpot) to control the MIDI-TEST istance. SCRATCH> (free 0)
N.B. if you want to use Jack MIDI, there is a specific tutorial here.
It is possible to define one or more responders associated with a
PM:STREAM
SCRATCH> (defvar resp (make-responder *midiin* (lambda (st d1 d2) (nrt-msg info "~D ~D ~D" st d1 d2)))) RESP SCRATCH> (setf (logger-level) :info) :INFO ;;; ... send some MIDI message ... SCRATCH> (all-responders *midiin*) (#S(RESPONDER :RECEIVER #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING> :FUNCTION #<CLOSURE ...)) SCRATCH> (remove-responder resp) SCRATCH> (setf (logger-level) :warn) :WARN SCRATCH> (recv-stop *midiin*)
The following example shows the steps to use a VOICER
for
voice management:
(dsp! simple (freq amp pos (env envelope) gate) (foreach-channel (cout (pan2 (* (envelope env gate 1 #'free) (sine freq amp 0)) pos)))) SCRATCH> (defparameter env1 (make-adsr .01 .09 .9 .2)) SCRATCH> (defparameter voi (voicer:create 20 (simple 440 .3 .5 env1 1))) SCRATCH> (voicer:control-value voi 'freq) 440 T SCRATCH> (voicer:control-value voi 'amp) 0.3 T SCRATCH> (voicer:trigger voi 123) #<VOICER-NODE 123> SCRATCH> (dump (node 0)) group 0 node 1 SIMPLE 440.0d0 0.3d0 0.5d0 ... NIL SCRATCH> (setf (voicer:control-value voi 'freq) 220) 220 SCRATCH> (setf (voicer:control-value voi 'amp) .2) 0.2 SCRATCH> (voicer:trigger voi 123) #<VOICER-NODE 123> SCRATCH> (voicer:set-controls voi :freq 880 :amp .1) 0.1 SCRATCH> (voicer:trigger voi 123) #<VOICER-NODE 123> SCRATCH> voi #<VOICER :POLYPHONY 20 :COUNT 3> SCRATCH> (dump (node 0)) group 0 node 3 SIMPLE 880.0d0 0.1d0 0.5d0 ... node 2 SIMPLE 220.0d0 0.2d0 0.5d0 ... node 1 SIMPLE 440.0d0 0.3d0 0.5d0 ... NIL SCRATCH> (voicer:release voi 123) NIL SCRATCH> (voicer:release voi 123) NIL SCRATCH> (voicer:release voi 123) NIL SCRATCH> voi #<VOICER :POLYPHONY 20 :COUNT 0> SCRATCH> (dump (node 0)) group 0 NIL
The first argument of VOICER:CREATE
is the max number of
voices and the second argument is a list
(dsp-name dsp-arg1 dsp-arg2 ...)
practically the function call to perform an instance of the DSP.
VOICER:DEFINE-MAP
is an utility to define a mapping
function to call immediately after a VOICER:TRIGGER
,
before the new event.
SCRATCH> (voicer:define-map foo voi (freq amp pos) (setf freq (+ 100 (random 2000)) ;; Ignorable STYLE-WARNING, a defensive test is ;; (when freq ;; (setf amp (random (if (> freq 800) .1 .3)))) amp (random (if (> freq 800) .1 .3)) pos (random 1.0))) #<VOICER :POLYPHONY 20 :COUNT 0> SCRATCH> (voicer:trigger voi 123) #<VOICER-NODE 123> SCRATCH> (voicer:trigger voi 123) #<VOICER-NODE 123> SCRATCH> (voicer:trigger voi 123) #<VOICER-NODE 123> SCRATCH> (dump (node 0)) group 0 node 5 SIMPLE 105.0d0 0.13262039422988892d0 0.2647184133529663d0 ... node 4 SIMPLE 2013.0d0 0.02539736032485962d0 0.9377354383468628d0 ... node 3 SIMPLE 960.0d0 0.027095390483736992d0 0.8115837574005127d0 ... NIL SCRATCH> (set-controls 0 :gate 0) NIL SCRATCH> voi #<VOICER :POLYPHONY 20 :COUNT 0> SCRATCH> (voicer:remove-map voi 'foo) NIL
VOICER:MIDI-BIND
allows to control a VOICER
with MIDI messages:
SCRATCH> (recv-start *midiin*) #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING> SCRATCH> (voicer:midi-bind voi *midiin*) #<MIDI-EVENT :VOICER #<VOICER :POLYPHONY 20 :COUNT 0> :RESPONDER #S(RESPONDER :RECEIVER #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING> :FUNCTION #<CLOSURE (LAMBDA (INCUDINE::STATUS INCUDINE::DATA1 INCUDINE::DATA2 STREAM) :IN INCUDINE::MIDI-RESPONDER-WRAPPER) {1006369ADB}>)>
Done. Now try to play a connected MIDI keyboard.
SCRATCH> (recv-stop *midiin*) #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING> SCRATCH> (remove-all-responders *midiin*) SCRATCH> (pm:terminate) :PM-NO-ERROR
For offline rendering, there are BOUNCE-TO-DISK
and
BOUNCE-TO-BUFFER
(dsp! oscilla (freq amp pos atk rel) (foreach-channel (cout (pan2 (* (envelope (make-perc atk rel) 1 1 #'free) (osc *sine-table* freq amp 0 :cubic)) pos)))) (defun nrt-test-1 (filename) ;; The bounce ends 2 seconds after the last event (bounce-to-disk (filename :pad 2) (at 0 #'oscilla 200 .3 (random 1.0) .5 1.8) (at #[1.5 beats] #'oscilla 400 .35 (random 1.0) .001 1) (at #[2.6 beats] #'oscilla 800 .8 (random 1.0) 1 .2))) (defun oscilla-events (time) (oscilla (+ 100 (random 1000)) (+ .1 (random .4)) (random 1.0) (+ .01 (random .5)) (+ .01 (random .5))) (let ((next-time (+ time (+ #[177 ms] (random #[3/2 s]))))) (at next-time #'oscilla-events next-time))) (defun nrt-test-2 (filename &optional (duration 10)) (bounce-to-disk (filename :duration duration) (oscilla-events (now)))) SCRATCH> (init) ; INIT is useless after RT-START SCRATCH> (nrt-test-1 "/tmp/nrt-test-1.wav") peak amps: 0.323 0.765 samples out of range: 0 0 "/tmp/nrt-test-1.wav" SCRATCH> (nrt-test-2 "/tmp/nrt-test-2a.wav") peak amps: 0.410 0.469 samples out of range: 0 0 "/tmp/nrt-test-2a.wav" SCRATCH> (nrt-test-2 "/tmp/nrt-test-2b.wav" 60) peak amps: 0.860 0.625 samples out of range: 0 0 "/tmp/nrt-test-2b.wav"
The body of BOUNCE-TO-DISK
is the body of a function
without arguments or a single (FUNCALL FN)
, where
FN
is a function without arguments.
It is possible to import the events from a rego file, which contains time-tagged lisp functions, lisp statements and lisp tags.
The syntax of a time-tagged lisp function is:
start-time-in-beats [time-increment]* function-name [arg1] [arg2] ...
The optional numbers between start-time-in-beats
and
function-name
increment the start time. For example:
0.8 foo 220 .2 2.5 .15 foo 440 .5 3.2 .25 -.11 foo 432 .2
is equivalent to
0.8 foo 220 .2 (+ 2.5 .15) foo 440 .5 (+ 3.2 .25 -.11) foo 432 .2
Here is a rego file for the test
;;; /tmp/test.rego ;; Start two instances 0 simple2 440 .3 .5 :id 1 1.3 simple2 880 .3 .5 :id 2 ;; Change some controls 2 set-control 1 :freq 200 2 set-control 2 :freq 208 5.7 set-control 1 :freq 220 5.7 set-control 2 :freq 231 ;; Don't worry for the order of the times 4 set-controls 1 :freq 300 :amp .1 :pos .1 4 set-controls 2 :freq 312 :amp .1 :pos .9 ;; Game Over 8 free 0
and the lisp code is
(dsp! simple2 (freq amp pos) (foreach-channel (cout (pan2 (sine freq amp 0) pos)))) (defun nrt-test-3 (outfile &optional (regofile "/tmp/test.rego")) (bounce-to-disk (outfile :pad 0) (funcall (regofile->function regofile)))) SCRATCH> (nrt-test-3 "/tmp/nrt-test-3.wav") peak amps: 0.424 0.424 samples out of range: 0 0 "/tmp/nrt-test-3.wav"
A line comment starts with a semicolon. A line continuation requires
the character \
at the end of the line. The comments and
the blocks in Org markup language are ignored too.
TIME
and TEMPO-ENV
are predefined variable
bindings usable within a rego file. They are the time offset in beats
and the temporal envelope of the events, respectively.
It is possible to create other variable bindings through WITH
at the beginning of the score. For example:
;;; /tmp/test2.rego with (id 1) (last 4) ;; simple oscillators 0 simple 440 .2 :id id 1 simple 448 .2 :id (+ id 1) (1- last) simple 661 .2 :id (+ id 2) last free 0
We can also add a DECLARE
expression after the bindings.
If we use the symbol //
to separate the functions with the
same time-tag, we get a polyphonic vertical sequencer in text files.
A quoted function name is ignored, useful to mute an instrument.
For example:
2.5 foo 440 .08 // bar 550 .1 // 'baz 660 .05 // sev 770 .1 3.2 // baz 330 .03 4.5 foo 220 .02 // sev 772 .07
is equivalent to
2.5 foo 440 .08 2.5 bar 550 .1 2.5 sev 770 .1 3.2 baz 330 .03 4.5 foo 220 .02 4.5 sev 772 .07
Another example:
0 .11 i1 1 2 3 // .25 i2 1 2 3 // .05 i3 1 2 3 1 2 i1 1 2 3 // .05 i2 1 2 3 // -.15 i3 1 2 3 3
is equivalent to
0.05 i3 1 2 3 0.11 i1 1 2 3 0.25 i2 1 2 3 1.85 i3 1 2 3 2.00 i1 1 2 3 2.05 i2 1 2 3
An isolated number is not a lisp tag otherwise a time-tagged function gets confused. A single column of numbers is useful for rhythm templates.
DUR
is a local function to convert the duration from beats
to seconds with respect to TEMPO-ENV
.
TEMPO
is a local macro to change the tempo of the score.
The syntax is
(tempo bpm)
(tempo bpms beats &key curve loop-node release-node
restart-level real-time-p)
We can define a loop with lisp tags and GO
:
;;; /tmp/loop.rego with (n 0.0) (seed-random-state 12345) ;; From 60 to 180 bpm in 4 beats with sinusoidal curvature (tempo '(60 180) '(4) :curve :sin) start n simple3 (+ 100 (random 1000)) 0.3 (dur 0.5) (if (< (incf n 0.5) 5) (go start))
A simple test:
(dsp! simple3 (freq amp dur) (stereo (* (envelope (make-perc .1 .9) 1 dur #'free) (sine freq amp 0)))) ;;; Off-line rendering (bounce-to-disk ("/tmp/loop.wav" :pad .3) (funcall (regofile->function "/tmp/loop.rego"))) ;;; Realtime #1 (rt-start) (funcall (regofile->function "/tmp/loop.rego")) ;;; Realtime #2 (regofile->function "/tmp/loop.rego" 'loop1) (loop1) ;;; From rego to lisp file and compilation (compile-file (regofile->lispfile "/tmp/loop.rego" 'loop1)) ;;; From regofile to list... (regofile->list "/tmp/loop.rego") ;;; ...and vice versa (regolist->file (regofile->list "/tmp/loop.rego") "/tmp/test.rego")
Part 2 | Home | Part 4 |