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 a minimal test to use the 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)
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(INCUDINE::RESPONDER
    :RECEIVER #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING>
    :FUNCTION #<FUNCTION (LAMBDA (ST D1 D2)) {1009E44DEB}>))
SCRATCH> (remove-responder resp)
SCRATCH> (setf (logger-level) :warn)
:WARN
SCRATCH> (recv-stop *midiin*)

The follow example shows the steps to use a VOICER in Incudine:

(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))
ENV1
SCRATCH> (defparameter voi (voicer:create 20 (simple 440 .3 .5 env1 1)))
VOI
SCRATCH> (describe voi)
#<VOICER :POLYPHONY 20 :COUNT 0>
  [structure-object]

Slots with :INSTANCE allocation:
  NODE-POOL             = #<CONS-POOL 20>
  GENERIC-POOL          = #<CONS-POOL 60>
  OBJECTS               = (NIL)
  OBJECT-HASH           = #<HASH-TABLE :TEST EQL :COUNT 0 {1006AE5503}>
  POLYPHONY             = 20
  AVAILABLE-NODES       = 20
  COUNT                 = 0
  SPINLOCK              = #<SPINLOCK "Voicer">
  TRIGGER-FUNCTION      = #<CLOSURE (LAMBDA (#:V82 #:TAG80 #:VNODE83)) {1006AE60AB}>
  RELEASE-FUNCTION      = #<FUNCTION INCUDINE.VOICER::RELEASE-FUNCTION-DEFAULT>
  OBJECT-FREE-FUNCTION  = #<FUNCTION (LAMBDA (INCUDINE.VOICER::ID)) {100553022B}>
  STEAL-FUNCTION        = NIL
  ARGUMENTS             = #<HASH-TABLE :TEST EQL :COUNT 5 {1006AE5963}>
  ARGUMENT-MAPS         = #<HASH-TABLE :TEST EQL :COUNT 0 {1006AE5DA3}>
; No value
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

And now we see how to control our VOICER with the 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(INCUDINE::RESPONDER
                           :RECEIVER #<RECEIVER PORTMIDI:INPUT-STREAM RUNNING>
                           :FUNCTION #<CLOSURE (LAMBDA #) {100717A0EB}>)>

Done.

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-local-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 here if we called 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 (for Algol-like style).

The syntax of a time-tagged lisp function is:

start-time-in-beats function-name [arg1] [arg2] ...

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 to import it 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 `;' and there is a line continuation with `\' at the end of the line.

TIME and TEMPO-ENV are pre-defined local variables usable inside a rego file. They are respectively the time offset in beats and the temporal envelope of the events.

It is possible to define other local variables by inserting the bindings after 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

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

We can also add a DECLARE expression after the bindings.

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-local-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