;;; ********************************************************************** ;;; $Name$ ;;; $Revision: 1004 $ ;;; $Date: 2006-01-11 14:26:40 +0000 (Wed, 11 Jan 2006) $ ;;; ;;; ;;; 1. Basic processes definition and file output. ;;; ;;; Its a good idea to define processes inside of functions so that ;;; different versions of the process can be created by calling the ;;; function with different input values. ;;; (in-package :cm) (define (sinus len cycl low hi rhy dur amp) ;; sinus is a function that creates a process. the process ;; will play midi notes in a "sine wave" of cycl periods over ;; len number of notes. sin's value (-1 to 1) is rescaled ;; to lie between the specified low and hi keynums. (process for i below len output (new midi :time (now) :keynum (rescale (sin (* 2 pi (/ i len) cycl)) -1 1 low hi) :amplitude amp :duration dur) wait rhy)) ;; Now call the function to create a process. (sinus 80 4 20 100 .1 .1 .6) ;; Notice that you didn't actually hear anything...This is because the ;; function only creates a process, it doesn't execute it. In order ;; to generate output from a process you need to pass it to the ;; 'events' function along with some destination. Most (but not all) ;; destinations are simply files on the disk. You can specify full ;; pathnames for files or you can set CM's working directory and then ;; provide just the file name and extension without having to type the ;; full directory path. You use 'pwd' and 'cd' to do this: (pwd) ; print the current working directory (cd) ; go to your home directory (cd "/tmp") ; the / will work on Linux, OS X and Windows ;; Assuming you did a (cd) to some directory where you have write ;; permission, you can now use events to generate the sinus process to ;; a midi file in the current working directory: (events (sinus 80 4 20 100 .1 .1 .6) "test.mid") ;; Each type of output file (.mid, .clm, .sco, and so on) has a shell ;; command is called after a file of that type is written to play it ;; CM attempts to define a valid player for your machine when it first ;; starts up. Before attempting to make sound first check the value ;; of *midi-player* to see if it is valid for your machine, if it is ;; not then set it to the proper command string: *midi-player* ;; Now lets generate a midi file and play it! (events (sinus 80 4 20 100 .1 .1 .6) "test.mid") ;; If you dont want to hear anything, pass :play nil to events. THe ;; setting is "sticky", ie you only need to set it each time you want ;; to change the value. (events (sinus 80 4 20 100 .1 .1 .6) "test.mid" :play #f) ;; now turn it back on (events (sinus 80 4 20 100 .1 .1 .6) "test.mid" :play #t) ;; Setting *midi-player* and *audio-player* is a fine thing to do in ;; your ~/.cminit.lisp file. ;;; ********************************************************************** ;;; ;;; 2. More on events and objects. ;;; ;;; You can mix concurrent output from multiple objects by ;;; specifying them in a list to 'events'. You can also give each ;;; object in the list its own start time by passing a list of starts ;;; after the output file. In this example the first process starts at ;;; time 0 and the second starts at time 2. ;;; (events (list (sinus 80 4 20 100 .1 .1 .6) (sinus 60 5.7 50 80 .1 .1 .6)) "test.mid" '(0 2)) ; start time offsets in the scorefile. ;;; If you dont want to use a process to output events you can ;;; simply pass a list of objects to the events function: (define stuff (loop repeat 20 ;; increment start randomly 70% of time for start = 0 then (+ start (odds .7 (pick .25 .5 .75 1.0) 0 )) for k = (between 50 90) ; pick a key, any key collect (new midi :time start :keynum k :duration (pick .5 1)))) (events stuff "test.mid") ;; if you want to give a name to your stuff you can use ;; a seq object: (new seq :name 'stuff :subobjects (loop repeat 20 ;; increment start randomly 70% of time for start = 0 then (+ start (odds .7 (pick .25 .5 .75 1.0) 0 )) for k = (between 50 90) ; pick a key, any key collect (new midi :time start :keynum k :duration (pick .5 1)))) ;; Once you've created an object with a name you can reference ;; it by specifying its name with the #& read macro. If your Lisp ;; doesnt allow this then use (find-object 'stuff) instead of #&stuff ;; This may not work in some schemes.... (object-name #&stuff) (list-subobjects #&stuff) ;; You can also run a process and output its events to a seq. (new seq :name 'wavy) ; create empty seq (events (sinus 80 4 20 100 .1 .1 .6) ; cache output in new seq #&wavy) (list-subobjects #&wavy) ; list wavy's subobjects. (save-object #&wavy "wave.cm") ; save data to reload later (events #&wavy "test.mid") ; output wavy (events (list (sinus 60 5.7 50 80 .1 .1 .6) #&wavy) "test.mid") ; mix both to file. ;;; ********************************************************************** ;;; ;;; 3. Initializing IO objects. the 'io' macro lets you initialize ;;; various options in files and ports. For example, you can set ;;; "file versioning" to true or false using the io macro instead of ;;; passing it to events.. Versioning creates file names with numbers ;;; append to the basename so that sucessive outputs don't overwrite ;;; the same file. ;;; (io "test.mid" :versioning #t) (events (sinus 60 5.7 50 80 .1 .1 .6) ; do this several times.. "test.mid") (events (sinus 60 5.7 50 80 .1 .1 .6) "test.mid" :versioning #f) ;;; ********************************************************************** ;;; ;;; 4. Defining new objects. ;;; In this example we define a new object called simp that will output ;;; to clm, sco and midi files. The simp object is declared with four ;;; required parameters: beg end amp and freq. We also add an 'ins' slot ;;; so that it can handle the differnet instrument naming conventions in ;;; clm and csound. If you only use one syntheisis package you wouldn't ;;; need to do this. ;;; note that we associate the system function OBJECT-TIME with the ;;; 'beg' slot so these values will become the time value used by the ;;; scheduler when it executes. Some slot must be so designated or you ;;; will lose bigtime if you try to output the object to a score file. ;;; (defobject simp () ((ins :initform 'simp :accessor object-name) (beg :accessor object-time) (dur :initform 1) (amp :initform .5) (freq :initform 440)) (:parameters beg dur amp freq)) ;; Now define a simple process that output simps... (define (simp-1 num) (process repeat num output (new simp :beg (now) :dur 1 :amp .5 :freq (between 220 440)) wait (pick 0 1 2))) ;; ... and output 20 simps to a clm file. (events (simp-1 20) "test.clm") ;; Next we define a variant process that outputs simps ;; with names picked randomly from a list. (define (simp-2 num names) (process repeat num output (new simp :beg (now) :ins (and names (pickl names)) :dur 1 :amp .5 :freq (between 220 440)) wait (pick 0 1 2))) (events (simp-2 20 '("i1" "i2" "i3")) ; write to i1 i2 or i3 "test.sco") ;; Finally we define a method on the system function ;; 'object->midi' that converts simp data into midi data. ;; Choose the version for your Lisp: ;; Scheme: (define-method (object->midi (obj )) ;; create a midi object and pass it values from a simp. ;; convert simp's hertz values to keynums. (new midi :time (object-time obj) :duration (simp-dur obj) :keynum (keynum (simp-freq obj) ':hz) :amplitude (simp-amp obj))) ;; CL: (defmethod object->midi ((obj simp)) ;; create a midi object and pass it values from a simp. ;; convert simp's hertz values to keynums. (new midi :time (object-time obj) :duration (simp-dur obj) :keynum (keynum (simp-freq obj) ':hz) :amplitude (simp-amp obj))) ;; try out the conversion... (object->midi (new simp :beg 0 :dur 1 :amp .5 :freq 440)) ;; ... and now write some simp events to a midi file. (events (simp-2 20 #f) "test.mid") ;;; ********************************************************************** ;;; ;;; 5. patterns and interpolation ;;; (define (ex4) ;; create a heap of keynums out of a list of notes. (process with pat = (new heap :keynums '(c4 d ef f g af bf c5)) and rhy = .1 for i from 0 ;; iterpolate an amp between .2 and .8 every 6 notes. for a = (interp (mod i 6) 0 .2 5 .8) repeat 48 output (new midi :time (now) :keynum (next pat) :duration (* rhy 1.25) :amplitude a) wait rhy)) (events (ex4) "test.mid") ;;; ********************************************************************** ;;; ;;; 6. Using two output statements for simultaneous voices. ;;; (define (simul ) (process with pat = (new heap :keynums '(c4 d ef f g af bf c5)) and rhy = .1 for i from 0 for k = (next pat) repeat 40 output (new midi :time (now) :keynum k :duration (* rhy 1.25) :amplitude (interp (mod i 6) 0 .2 5 .8)) output (new midi :time (now) :keynum (+ k (pick -24 -12 12 24)) :duration (* rhy 1.25) :amplitude (interp (mod i 6) 0 .2 5 .8)) wait rhy)) (events (simul) "test.mid") ;;; ********************************************************************** ;;; ;;; 7. Chords. A cyclic pattern holds 3 chords and a single note. ;;; The second chord selects three members from a heap each time ;;; it is picked.The doeach macro maps each element regardless of ;;; whether the element is a single note or a list of ;;; notes generated by a chord. ;;; (define (chordy) (let ((pat (new cycle :of (list (new chord :of '(c4 e4 g4)) (new chord :of (new heap :of '(df4 ef4 gf4 af4 bf4 df5 ef5) :for 3)) (new chord :of '(e4 a4 cs5)) (new weighting :of '(bf3 (ef1 :weight .5 :max 2)) :for 1))))) (process repeat 32 do (doeach (k (next pat)) (output (new midi :keynum k :time (now)))) wait .5))) (events (chordy) "test.mid") ;; Like the above example but this one strums the chord. ;; Strum speed is independant of actual rhythm. (define (chordy-2 ) (let ((pat (new cycle :of (list (new chord :of '(c4 e4 g4)) (new chord :of (new heap :of '(df4 ef4 gf4 af4 bf4 df5 ef5) :for 3)) (new chord :of '(e4 a4 cs5)) (new weighting :of '(bf3 (ef1 weight .5 max 2)) :for 1))))) (process with ss = .05 repeat 32 for z = (now) do (doeach (k (next pat)) (output (new midi :time z :keynum k :duration .45 :amplitude .6)) (incf z ss)) wait .5))) (events (chordy-2) "test.mid") ;;; ********************************************************************** ;;; ;;; 8. Defining a recursive process with four input args. ;;; sprout's second arg is the time to start the sprouted object. ;;; (define (sierpinski knum ints dur amp depth) (let ((len (length ints))) (process for i in ints for k = (transpose knum i) output (new midi :time (now) :duration dur :amplitude amp :keynum k) when (> depth 1) ;; sprout a process on output note sprout (sierpinski (transpose k 12) ints (/ dur len) amp (- depth 1)) wait dur))) (events (sierpinski 'a0 '(0 7 5 ) 3 .5 4) "test.mid") (events (sierpinski 'a0 '(0 -1 2 13) 6 .5 4) "test.mid") ;;; ********************************************************************** ;;; ;;; 9. more than one instance of the same process can be ;;; scheduled as long it only side effects local varibales. ;;; (define (octie ) (let ((p (new heap :keynums '(c4 d ef f g af b))) (x (pick -24 -12 0 12 24))) ; pick a new octave each time (process with rhy = .1 for i from 0 repeat 40 output (new midi :time (now) :keynum (transpose (next p) x) :duration (* rhy 1.25) :amplitude (interp (mod i 6) 0 .2 5 .8)) wait rhy))) ;; try several times to hear random octave selection. (events (list (octie) (octie)) "test.mid") (events (list (octie) (octie)) "test.mid") (events (list (octie) (octie)) "test.mid") ;;; ********************************************************************** ;;; ;;; 10. tkunze's ghosts. A slightly more complex example demonstrating ;;; iteration (dolist), using let inside process, and sprouting ;;; different types of objects. ;;; (define (ghosts ) (process repeat 12 for tim = (now) for ahead = (* (+ tim .5) 2) for tone = (between 53 77) for high = (>= tone 65) for rhy = (rhythm (pick 1/16 1/8 3/16) ) for amp = (if high .6 .4) output (new midi :time tim :keynum tone :duration rhy :amplitude amp) if high sprout (new midi :time ahead :keynum (+ tone 24) :duration ahead :amplitude .5) and sprout (let ((knum tone)) (process repeat 5 for k = (+ 39 (mod knum 13)) then (+ k 13) do (output (new midi :time (now) :keynum k :amplitude .3 :duration 10)) (wait (/ rhy 4)))) at (* ahead 2) if (= rhy .75) do (dolist (i '(-18 -23)) (sprout (new midi :time (+ tim .5) :keynum (transpose tone i) :duration .05 :amplitude .4) (now))) wait rhy )) (events (ghosts) "test.mid") ;;; ********************************************************************** ;;; ;;; 11. pvals and random patterns with changing probabilities. ;;; cage demonstrates random probability controlled as a function ;;; of time. over the course of 100 passes the process gradually prefers ;;; the notes C,A,G and E over the other notes of G dorian. A random ;;; weight W is assigned to the notes C, A, G and E when the pattern is ;;; created. Then, when the process runs, a value for W is interpolated ;;; on each pass. Since the pattern was created before the process began ;;; running, the CAGE weights in the pattern must be specified as a PVAL ;;; (pattern value), which has the effect of delaying evaluation of W ;;; until the process actually reads the pattern using the NEXT function. ;;; (define (cage offset) (let* ((w 0) ; weight of C, A, G and E is 0 (prob (pval w)) ; pval evals W as pattern runs (pat1 (new weighting :of `((g3 :weight ,prob) ; delay G's weight using pval (a3 :weight ,prob) ; delay A's weight using pval bf3 (c4 :weight ,prob) ; delay C's weight using pval d4 (e4 :weight ,prob) ; delay E's weight using pval f4 (g4 :weight ,prob) ; delay G's weight using pval (r :max 1 :weight .25)) ; allow rests :for 1)) (pat2 (new weighting of (list 1/16 1/8 (new cycle :of 1/32 :for 2 ))))) (process repeat 100 for n = (next pat1) for r = (rhythm (next pat2) 65) for i from 0 ;; assign a new weight to W based on current pass. set w = (interp i 0 .5 90 4) output (new midi :time (now) :duration r :keynum (if offset (transpose n offset) n)) wait r))) ;;; Four cage processes in different octaves (events (loop for o in '(-12 0 12 24) collect (cage o)) "test.mid") ;;; ********************************************************************** ;;; ;;; 12. CMN output. ;;; This only works in Common Lisp and only if you loaded CMN into Lisp ;;; before you built CM. ;;; ;;; If you want to control how CMN displays the notes you send it ;;; you can preset staff attributes using the :staffing init to ;;; cmn files. THe value of :staffing is a list of staff description, ;;; each discription is itself a list: ;;; ( :name :clef :meter ) ;;; for example: ;;; (0 :name "Banjo" :clef :treble :meter (4 4)) ;;; Id is an integer that identifies the staff. This integer is the ;;; "connection" between the data you want to display in CMN and the ;;; staff it will appear in. THe value of :Name is an optional string ;;; name for the staff, it defaults "staff-n" where n is the staff id. ;;; The value of :clef is an optional keyword clef name or list of ;;; clef names. If a clef not specified then CMN will do whatever it ;;; thinks best. If the clef is :both then you get a grand staff. If ;;; the clef is a list it limits CMN's clef choices to that list when ;;; it displays the data. THe value :meter is an meter for the staff. ;;; If you provide; one it should be a list like (3 4). ;;; ;;;(define mystaffs ;;; '((0 :name "Cello" :clef :bass :meter (4 4)) ;;; (1 :name "Oboe" :clef :treble :meter (4 4)) ;;; (2 :name "Flute" :clef :treble :meter (4 4)))) ;;; ;;; You can load a cmn file after it is written by setting ;;; the CMN output hook: ;;; ;;; (set-cmn-output-hook! #'load) ;;; ;;; You can set score attributes as inits to the file. The special init ;;; ':exact-rhythms t' means you are sending rational time values ;;; (ratios) rather than weird floating point values. In this ;;; case a duration of 1 means "quater note". For example: ;;; ;;; (io "test.cmn" :exact-rhythms t :size 24 :title "Hiho!") ;;; ;;; The connection between CMN and your data is made by implemeting ;;; a method on CM's generic function 'object->cmn'. This function ;;; converts an object you want to display into a LIST of display data. ;;; CM predefines a method for MIDI events, you can do the same for ;;; whatever your data is. The method for MIDI events looks like: ;;; ;;; (defmethod object->cmn (obj) ;;; (list (midi-channel obj) ;;; (midi-duration obj) ;;; (note (midi-keynum obj)))) ;;; ;;; The 'object->cmn" method can return a list one of two formats. ;;; The first format produces "notes": ;;; ;;; (id duration note ... ) ;;; ;;; Id is an integer identifying the staff that the display should ;;; appear on. If there is currently no staff for that id then one ;;; will be automatically created. Use 'set-cmn-staff!' to predefine ;;; staves. Duration is the duration of the cmn note. Note is a note ;;; name like 'cs4' or whatever. The ... args are whatever else you ;;; want to attach to the cmn note. These are symbols or expressions ;;; that will be evaluaed in the context of CMN's variables and ;;; functions. THe second possible format for the data list is ;;; ;;; (id cmn-variable/expr) ;;; ;;; This adds a single cmn variable or expression to the staff ;;; belonging to id. ;;; (define (testit stf len nts) (let ((nts (new heap :notes nts)) ;; choose quater or two eighths (rhy (new weighting :of (list 1 (new cycle :of '(1/2 1/2)))))) (process while (< (now) len) for n = (next nts) for r = (next rhy) output (new midi :time (now) :duration r :keynum n :channel stf) wait r))) (define staffs '((0 :name "Viola" :clef :alto :meter (4 4)) (2 :name "Flute" :clef :treble :meter (4 4)))) ;;; generate a .eps file (events (list (testit 0 12 '(c3 d ef f g)) (testit 1 12 '(c5 d ef f g))) "testit.eps" :staffing staffs :size 24 :title "Hiho!") ;;; generate a .cmn file (events (list (testit 0 12 '(c3 d ef f g)) (testit 1 12 '(c5 d ef f g))) "testit.cmn" :staffing staffs :size 24 :title "Hiho!") ;;; ********************************************************************** ;;; ;;; 13. Microtonal output in MIDI and the 'each' process clause ;;; define a tuning that uses harmonics 8-16 as scale degrees. (define harms (new tuning :ratios (loop for i from 8 to 16 collect (/ i 8)) :lowest (hertz 'c5))) (define (playharms ) (process with p = (new cycle of (list (new weighting :of (list 0 8 16) :for 1) (new chord :of (new heap :of '(1 2 3 4 5 6 7) :for 3))) ) repeat 31 for x = (next p) if (number? x) output (new midi :time (now) :duration .2 ;; convert from tuning keynum to floating point ;; keynum in the standard scale :keynum (keynum (hertz x :in harms) :hz #t)) else ;; if a chord (list) of notes is selected fromthe pattern ;; then iterate over each element in the chord and output it. each n in x output (new midi :time (now) :duration .2 ;; convert from tuning keynum to floating point ;; keynum in the standard scale :keynum (keynum (hertz n :in harms) :hz #t)) wait .25)) ;;; Channel tuning in MIDI for microtonal output. the first example ;;; quantizes to 4 steps per semitone using 4 channels on the MIDI synth. ;;; the second example performs note-by-note tuning, ie no quantization ;;; and all channels are cycled in round robin style to minimize the ;;; likelyhood that a pitch bend will affect a sounding note. the third ;;; example turns off channel tuning. (events (playharms) "test.mid" :channel-tuning 4) (events (playharms) "test.mid" :channel-tuning #t) (events (playharms) "test.mid" :channel-tuning #f)