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")

Sourceforge project page