It is sometimes easier to compute objects than it is to create them using commands. Common Music provides many high level functions and macros that allow musical material to be algorithmically described. These forms are by definition Lisp code, so it is usually easiest to use a text editor that supports Lisp evaluation and formatting (for example, Emacs and Fred) when working in this manner. If the editor you are using does not support Lisp, then you can "evaluate" by copying text, pasting it to Stella's prompt and the pressing the Return key.
Stella [Top-Level]: (setf foo #!top-level) #<CONTAINER: Top-Level> Stella [Top-Level]: ,foo #<CONTAINER: Top-Level> Stella [Top-Level]:Use nth-object to reference a sub-object:
Stella [Top-Level]: (nth-object 0 foo) #<THREAD: Pulse> Stella [Top-Level]:Note that nth-object indexing starts at 0.
The following six sections explain the syntax of each of these macros in detail.
object {class} {slot value}* [Macro]Creates musical event data. class is the class of event to create. Following class comes zero or more slot initializations. Each initialization is a pair {slot value} where slot is the name of a slot in the object and value is its evaluated value.
In the next example a midi-note is created with a rhythm of .1 and a note of C4.
Stella [Top-Level]: (object midi-note rhythm .1 note 'c4) #<MIDI C4 0.1 unset unset 0> Stella [Top-Level]:
thread {name} ({slot value}*) {form}* [Macro]Creates a thread object.name is the name for the thread. Following name comes a list holding zero or more slot initializations, as described under object macro. If no slots are to be initialized then the initialization list is written (), the empty list. Otherwise, the list can contain initializations for the following slots, which are also available for merge, algorithm and generator objects:
start {number}
A local start time (seconds) for the object.
initializer {function}
A function to call before the object begins output processing.
finializer {function}
A function to call just after the object finishes output processing.
Following the initialization list comes the body of the thread definition. Objects created inside the body will automatically become sub-objects of the new thread.
The next example creates a thread named Test holding 30 midi-note objects:
(thread test () (loop repeat 30 do (object midi-note note (between 40 80) rhythm (pickl .1 .2 .4) duration .2 amplitude .4)))
heap {name} ({slot value}*) {form}* [Macro]Creates a heap object. A heap is a type of thread that, when called upon to produce objects, first randomly reorders them. Marco arguments are the same as for thread.
merge {name} ({slot value}*) {form}* [Macro]Creates a merge object. A merge processes its sub-objects in parallel, using a scheduling queue. The sub-objects should all be containers, i.e. other threads, merges, algorithms or generators. Macro arguments are the same as for thread.
The next example creates a merge named M with two sub-threads. Sub-thread T1 holds two notes and T2 holds 10 notes.
(merge m () (thread t1 () (object midi-note note 'c3 rhythm .5 duration 1 amplitude .7) (object midi-note note 'fs3 rhythm .5 duration .5 amplitude .7)) (thread t2 () (let ((r .1)(d .2)(a .5)) (doitems (x (notes c4 d ef f g in random for 10)) (object midi-note note x rhythm r duration d amplitude a))))) Stella [Top-Level]: mix m 2 Stella [Top-Level]:
algorithm name type ({slot value}*) {form}* [Macro]Creates an algorithm object. An algorithm computes musical events given a class specification and a program for changing slot values as the algorithm executes. name is the name of the algorithm. class is the class of musical event that the algorithm will create. Following class comes a slot initialization list containing zero or more initializations. In addition to those initializations discussed under thread, algorithm initializations may also include entries for event class slots, and for two slots provided by the algorithm class itself:
length {integer}
The number of times the algorithm executes before stopping.
end {number}
The last start time (in seconds) for the algorithm before stopping.
An algorithm differs from a thread, merge or heap in two important ways:
(algorithm pulse midi-note (length 80 rhythm .1 duration .1) (setf note (item (notes c4 d ef f g af bf c5 in random))) (setf amplitude (interpl (mod count 8) 0 .25 7 .75))) Stella [Top-Level]: mix pulse 1 Stella [Top-Level]:
generator name type ({slot value}*) {form}* [Macro]Creates a generator object. A generator saves the events it computes but is like an algorithm in all other respects. When a generator executes, it first checks to see if it already has events. If it does, it processes them in sequential order like a thread. If a generator does not have events, or if the generator has been set "unfrozen", the current events (if any) are thrown away and a new set will be computed and cached the next time the generator is run.
(generator g midi-note (amplitude .5) (setf note (item (steps 1 2 3 in random for 10 from 'c4) :kill t)) (setf rhythm (item (rhythms s e q in random))) (setf duration rhythm)) Stella [Top-Level]: mix g 1 Stella [Top-Level]: mix g 1 [Mixes sounds identical despite random notes] Stella [Top-Level]: unfreeze g G unfrozen. Stella [Top-Level]: mix g 1 [Mix sounds different] Stella [Top-Level]:
(merge m () (loop for p in '(c4 c5 c6) for s from 0 do (algorithm n midi-note (start s amplitude .1) (setf note (item (intervals 0 2 3 5 7 8 in random from p) :kill 10)) (setf rhythm (item (rhythms e s 32 in random))) (setf duration rhythm))))The composer wants to create three new algorithms to run inside a merge, where each algorithm generates a different series of notes according to the three different interval stream offsets in p. The code would work except for two bugs:
name name &optional new [Macro]Used in place of a symbolic name to specify a name for an object. Ifnew is nil (the default), name returns name as its value. Otherwise, name serves as a root to generate a new unique name.
Using name, our previous example now looks like:
(merge m () (loop for p in '(c4 c5 c6) for s from 0 for n in '(nood1 nood2 nood3) do (algorithm (name n) midi-note (start s amplitude .1) (setf note (item (intervals 0 2 3 5 7 8 in random from p) :kill 10)) (setf rhythm (item (rhythms e s 32 in random))) (setf duration rhythm))))
However, note in the preceding example the composer means for each algorithm to use a different value of p, for the first algorithm p should be c4, for the second C5, and for the third C6. Lexical closures capture a variable's binding (definition); this is not necessarily the same as a variable's current value. To ensure that each individual value of a lexical variable is captured, use the macro with-vars-snapshotted:
with-vars-snapshotted ({var}+) {body}* [Macro]Insures that forms in body reference a unique binding of one or more var.
(merge m () (loop for p in '(c4 c5 c6) for s from 0 for n in '(nood1 nood2 nood3) do (with-vars-snapshotted (p n s) (algorithm (name n) midi-note (start s amplitude .1) (setf note (item (intervals 0 2 3 5 7 8 in random from p) :kill 10)) (setf rhythm (item (rhythms e s 32 in random))) (setf duration rhythm)))))The algorithms defined in the loop will now work as expected.
vars {variable | (variable value)}* [Macro]The syntax of the vars declaration is identical to the binding list syntax of let: each variable is either the name of a variable, or a binding list (variable value), where variable is the name of the variable and value is its initial value. The entries in the vars declarations are processed in sequential order, so variables can use definitions "to their left" as values. For example,
(vars a (b 2) (c (* b 3)))declares a to be nil, b to be 2 and c to be 6.
Here is a comparison of three different ways of defining a variable named x:
(algorithm foo midi-note (length 100) (vars (x (random 10))) (print x) ...)
(algorithm foo midi-note (length 100) (let ((x (random 10)) (print x) ...)
(let ((x (random 10))) (algorithm foo midi-note () (print x) ...)
(algorithm ritardando midi-note (amplitude .9) (vars (len (between 5 30))) (setf note (item (notes c4 d ef for len) :kill t)) (setf rhythm (interpl cnt 0 .1 (1- len) .3)) (setf duration rhythm)) Stella [Top-Level]: seq ritardando Start time offset: (<cr>=None) 2 Number of times to sequence: (<cr>=1) 5 Length of pause between selections: (<cr>=None) 1 Stella [Top-Level]:Here is a more complicated, but very elegant, example written by Tobias Kunze ( tkunze@ccrma.stanford.edu)
;;; A third-order recursive cellular automaton. ;;; The algorithm maintains three past note values ;;; to compute each new note based on the formula: ;;; ;;; (+ last ;;; (* (- 12 (abs x)) (if (>= x 0) -1 1)) ;;; 1) ;;; ;;; where x represents the interval from the oldest to the ;;; second-to-oldest note. Sounds best with a softly ;;; reverberated percussive sound (vibe, harp or piano). Set ;;; length to some higher number (ca. 1000 or more) to see that ;;; this generates up to 24 different patterns in lots of ;;; different phrases (algorithm cell midi-note (length 200 rhythm .1 duration .5 amplitude .5) (with-past-values ((note 3 60 60 60)) ;; convert oldest interval to inverse complement (let ((width (- (- (past-value note 3) (past-value note 2))))) (incf width (if (>= width 0) -12 12)) ;; transpose by last note. if the new note is out of ;; bounds shift it up or down and increment by whole step ;; otherwise increment by half step (incf width (past-value note 1)) (setf note (cond ((< width 36) ; raise 1 to 5 octaves+2 steps (+ width (* 12 (between 1 6)) 2)) ((> width 98) ; lower 1 to 5 octaves-2 steps (- width (* 12 (between 1 6)) -2)) (t (+ 1 width))))))) Stella [Top-Level]: mix cell 1 Stella [Top-Level]:
mute {name} ({slot value}*) {form}* [Macro]Creates a silent algorithm. The next example defines a mute named Foo that prints its current count and time values as it executes:
(mute foo (length 4 rhythm .5) (format t "~%Count=~S, time=~S" count time)) Stella [Top-Level]: mix foo Start time offset: (<cr>=None) <cr> [Foo executes but no sound results] Count=0, time=0.0 Count=1, time=0.5 Count=2, time=1.0 Count=3, time=1.5 Count=4, time=2.0 Stella [Top-Level]:
sprout object [Function]Inserts object into the scheduler, which defaults to the outermost executing merge.
Here is a example of a mute that sprouts 6 algorithms. Each time the mute executes it binds the variable off to a new pitch offset and rep to a value between 10 and 20.
(mute ma (rhythm 2) (let ((off (item (degrees c3 c4 c5 ) :kill 2)) (rep (between 20 30))) (sprout (algorithm nil midi-note (rhythm .2 duration .175 start (+ time (random .05)) amplitude .5) (setf note (item (intervals 0 2 3 5 7 8 9 in heap from off for rep returning note) :kill t)))))) Stella [Top-Level]: open test.midi Stream: #<File: "test.midi">. Stella [Top-Level]: mix ma 0 Play file test.midi? (<cr>=Yes)
(mute x () (setf rhythm (item (rhythms q h h. w in random) :kill 2)) (let (pitch) (setf pitch (note (between 100.00 300.00))) (loop for beg from 0 by 1.5 repeat (+ 1 (random 4)) do (format t "Sprouting new algorithm at ~A to ~A~&" (+ time beg) (+ time beg 5)) (sprout (algorithm nil midi-note (start (+ time beg) end (+ time beg 5) amplitude .25) (setf note (transpose pitch (+ -3 (random 7)))) (setf rhythm (+ .05 (random .15))) (setf duration rhythm)))))) Stella [Top-Level]: mix x 0 Sprouting new algorithm at 0.0 to 5.0 Sprouting new algorithm at 1.0 to 6.0 [...output elided] Sprouting new algorithm at 12.0 to 17.0 Sprouting new algorithm at 13.5 to 18.5 Play file /user/hkt/test.midi? (<cr>=Yes) Stella [Top-Level]:
Next Chapter
Previous Chapter
Table of Contents