Main Page       Index


definst.lsp


 definst.lsp
 Version 1.00  31 October 2004
 Author Steven Jones

 Contact jones57@swbell.net include the word "nyquist" in subject line

 The contents of this file are released under the terms of the GNU General
 Public License. See the file LICENSE.txt

 The genesis of definst was to provide a uniform way to call "instrument" or
 sound producing functions. The task is divided into two parts. First a,
 typically monophonic, "instrument" function is created. Secondly the
 instrument is "wrapped" in another function to provide polyphony as well as
 other common features. Both instruments and wrappers are implemented as 
 macros

function

definst:comp

 (definst:comp n [mode])
 A compressor of sorts. Calculates attenuation in db as function of number
 of notes played.

 n      - integer. Number of voices

 mode   - number | Boolean. 
         If mode is nil return 0 
         If mode is t return fn(x) ~= -6*log2(n)
         if mode is  a number return mode.

 return - flonum. 


macro

definst

 (definst name args expr)
 Create a new instrument function named name using a syntax similar to
 defun.  See examples for usage

 name - symbol. The symbol to which the function is bound
 
 args - list. Argument list to name. There are two standard arguments, pitch
        and dur, which are automatically inserted at the start of args. For 
        example if the contents of args (foo &optional bar) the actual 
        argument list to name is (pitch dur foo &optional bar) where pitch is 
        a MIDI note number and dur is the tones duration in seconds.

 expr - list. The expression which forms the function's body.


function

definst:ampscale

 (definst:ampscale signal args)
 Provides an amplitude-as-function-of-velocity  function for use with the
 sumfn argument in the wrapper and wrapper-m macros.
 See examples for usage

 signal - sound. The source signal

 args   - list. A list of arguments. All arguments are ignored with exception
          of the keyword :vel and the value immediately following it.
 

 return - sound. The signal is attenuated -6db for a velocity of 0, has
          unity gain for a velocity of 64, and is boosted +6db for a 
          velocity of 128


macro

defwrapper

 (defwrapper name instfn [sumfn])
 Creates an instrument wrapper named name around instrument instfn.
 
 name   - symbol. The name of the new wrapper function

 instfn - closure. A sound generating function with the form 
          (lambda pitch dur [keyword args...]) which should return 
          a mono sound.
          

 sumfn  - closure. A sound processing function which is applied to the 
          final output. It should have the form (lambda (signal args)...)
          where signal is a 2 channel sound and args is the argument
          list used to invoke the wrapper. The default sumfn is simply
          an identity which returns signal. definst:ampscale 
          is an appropriate function which scales amplitude in response
          to velocity.

 The resulting function has the lambda list

 (lambda name plist dur &rest args)

 where plist is either single MIDI note number or a list of note numbers
 and dur is the tones duration. For each note in plist instfn is executed. 
 The amplitude is scaled approx -6db for each doubling of notes.
 Additional arguments usually take the form of :key value pairs and are visible 
 to both instfn and sumfn. Keywords which the wrapper responds to are:
 
 :arpeg i - a flonum which sets the delay time between playing adjacent
            notes in plist. non-zero values result in arpeggios. Default 0

 :pos p   - A number in interval 0<= p <= 1 or a control signal over the 
            same interval. Sets the pan position.


macro

defwrapper-m

 (defwrapper name instfn [sumfn])
 Creates an instrument wrapper named name around instrument instfn.
 defwrapper-m is identical to defwrapper except that the instrument 
 function may return multi-channel sounds and the :pos keyword is not
 recognized. Although the wrapper does not recognize pos it would be
 possible for instfn too.

 see defwrapper and examples.


example

definst

;; @doc example definst
;;
;; ***************************************************************************  
;;				   Examples
;; ***************************************************************************  

;; First we setup a MIDI velocity map to attenuate the signal by +/-6db
;; in response to velocity. 
;;
 (nice-load "map")
 (setq demo:*velmap* map:*vel6*)

;; Next we define a simple mono-phonic instrument, named foo-mono, with
;; optional vibrato and wave table selection, and velocity amplitude
;; scale. Note it would be more efficient to to use sumfn to scale amplitude
;; once but if you which the instrument function to respond to velocity its
;; possible.


 (definst foo-mono (vel vib vfrq tab pos)
  (let* ((hz (step-to-hz pitch))
	 (vamp (* hz (or vib 0))))
    (mult 
     (scale-db (send demo:*velmap* :get (or vel 64))
	      (fmosc pitch 
		     (scale vamp (lfo (or vfrq 5) dur))
		     (or tab *saw-table*)))
     (percussion dur))))


;; The lambda list for foo-mono is
;; (foo-mono pitch dur [:vel][:vib][:vfrq][:tab])  Where
;; pitch - The pitch as MIDI key number
;; dur   - The duration in seconds
;; vel   - Velocity 
;; vib   - Vibrato depth
;; vfrq  - Vibrato frequency, default 5 Hz
;; tab   - Wave table, default *saw-table*
;;         (note *saw-table* is NOT band limited)


;; Now we define a wrapper for foo-mono named foo

 (defwrapper foo #'foo-mono)

;; The lambda list for foo is
;; (foo plist dur [:vel][:arpeg][:pos][:vib][:vfrq][:tab])  Where
;;
;; plist - List of MIDI key numbers to be played. Alternatively a single key
;; number my be used. foo calls foo-mono once for each note in plist.
;; dur - The duration
;; vel - Velocity ~ Passed to foo-mono
;; arpeg - Arpeggio parameter, default 0
;; pos - Pan position, default 0.5
;; vib - Vibrato depth ~ passed to foo-mono
;; vfrq - Vibrato frequency ~ passed to foo-mono
;; tab - Wave table ~ passed to foo-mono
;;
;; If we had included pos or arpeg in the arglist while defining
;; foo-mono these variable would also be visible within foo-mono.


 (play 
 (sim 
					;We can use a single MIDI key number
  (at 0 (cue (foo a2 1)))
  					;or a list of key numbers.
  (at 1 (cue (foo (list c3 e4 g4) 1)))
  					;Vel controls amplitude
  (at 2 (cue (foo b3 1 :vel   0)))
  (at 3 (cue (foo b3 1 :vel  64)))
					;pos controls pan position
  (at 4 (cue (foo c4 1 :pos 0)))
  (at 5 (cue (foo c4 1 :pos 1)))
  (at 6 (cue (foo e4 3 :pos (ramp 3))))

  					;Vib and vfrq control vibrato
  (at 9  (cue (foo (list c3 e4 g4) 1 :vib 0.00 :vfrq 5)))
  (at 10 (cue (foo (list c3 e4 g4) 1 :vib 0.01 :vfrq 5)))
  (at 11 (cue (foo (list c3 e4 g4) 1 :vib 0.10 :vfrq 5)))

  (at 12 (cue (foo (list d4 f4 a4) 1 :vib 0.10 :vfrq 1)))
  (at 13 (cue (foo (list d4 f4 a4) 1 :vib 0.10 :vfrq 6)))
  					;Arpeggio
  (at 14 (cue (foo (list d4 f4 a4 d5 f5 a5) 1 :vib 0.01 :vfrq 1  :arpeg .1)))

  ))


;; ***************************************************************************  
;;			      *** Example 2 ***
;; ***************************************************************************  
;;
;; While instfn is executed once for each note in plist, sumfn is executed
;; only once and is applied to the sum of the results.
;;
;; Here we redefine the wrapper foo to include a dynamic filter. The filter
;; takes two keyword arguments, attack and depth and is defined as a lambda
;; expression.

 (defwrapper foo #'foo-mono 
  #'(lambda (sig args)
      (let* ((attack (get-keyword-value ':attack args 0))
	     (decay 0)
	     (hold (max 0 (- dur attack decay)))
	     (depth (get-keyword-value ':depth args 1000)))
	(lp sig (scale depth (asd attack hold decay))))))


 (play (sim 
       (at 0.0 (foo (list c3 g4 e4) 1 :vel 127 :attack 0.5 :depth 5000))
       (at 0.5 (foo (list a4 c3 e4) 1 :vel 127 :attack 1.0 :depth 1000))
       (at 1.0 (foo (list g4 d4 b4) 1 :vel 127 :attack 0.6 :depth 6000))
       (at 1.5 (foo (list f4 a4 c5) 1 :vel 127 :attack 0.6 :depth 1000 
                     :vib 0.01 :vfrq 7))
       (at 2.0 (foo (list c2 c3 c4 c5 g4 g3 g2 e2 e3 e4 c3 c2) 4 
                     :vel 127 :vib 0.01 :arpeg .1 :attack 4.0 :depth 6000))
))


View the Sourcecode :



;; definst.lsp
;; Version 1.00  31 October 2004
;; Author Steven Jones
;;
;; Contact jones57@swbell.net include the word "nyquist" in subject line
;;
;; The contents of this file are released under the terms of the GNU General
;; Public License. See the file LICENSE.txt
;;
;; The genesis of definst was to provide a uniform way to call "instrument" or
;; sound producing functions. The task is divided into two parts. First a,
;; typically monophonic, "instrument" function is created. Secondly the
;; instrument is "wrapped" in another function to provide polyphony as well as
;; other common features. Both instruments and wrappers are implemented as 
;; macros
;;

(require 'map)
(require 'map-vel6)
(provide 'definst)
(current-file "definst")


;; @doc function definst:comp
;; (definst:comp n [mode])
;; A compressor of sorts. Calculates attenuation in db as function of number
;; of notes played.
;;
;; n      - integer. Number of voices
;;
;; mode   - number | Boolean. 
;;         If mode is nil return 0 
;;         If mode is t return fn(x) ~= -6*log2(n)
;;         if mode is  a number return mode.
;;
;; return - flonum. 
;;

(defun definst:comp (n &optional (mode t))
  (cond ((numberp mode) mode)
	((null mode) 0)
	(t (* -6 (log2 n)))))


;; @doc macro definst
;; (definst name args expr)
;; Create a new instrument function named name using a syntax similar to
;; defun.  See examples for usage
;;
;; name - symbol. The symbol to which the function is bound
;; 
;; args - list. Argument list to name. There are two standard arguments, pitch
;;        and dur, which are automatically inserted at the start of args. For 
;;        example if the contents of args (foo &optional bar) the actual 
;;        argument list to name is (pitch dur foo &optional bar) where pitch is 
;;        a MIDI note number and dur is the tones duration in seconds.
;;
;; expr - list. The expression which forms the function's body.
;;

(defmacro definst (name arglist exp)
  `(defun ,name (pitch dur &key ,@arglist) ,exp))


;; @doc function definst:ampscale
;; (definst:ampscale signal args)
;; Provides an amplitude-as-function-of-velocity  function for use with the
;; sumfn argument in the wrapper and wrapper-m macros.
;; See examples for usage
;;
;; signal - sound. The source signal
;;
;; args   - list. A list of arguments. All arguments are ignored with exception
;;          of the keyword :vel and the value immediately following it.
;; 
;;
;; return - sound. The signal is attenuated -6db for a velocity of 0, has
;;          unity gain for a velocity of 64, and is boosted +6db for a 
;;          velocity of 128
;;

(defun definst:ampscale (sig args)
  (scale-db (send map:*vel6* :get (get-keyword-value ':vel args '64)) sig))


;; @doc macro defwrapper
;; (defwrapper name instfn [sumfn])
;; Creates an instrument wrapper named name around instrument instfn.
;; 
;; name   - symbol. The name of the new wrapper function
;;
;; instfn - closure. A sound generating function with the form 
;;          (lambda pitch dur [keyword args...]) which should return 
;;          a mono sound.
;;          
;;
;; sumfn  - closure. A sound processing function which is applied to the 
;;          final output. It should have the form (lambda (signal args)...)
;;          where signal is a 2 channel sound and args is the argument
;;          list used to invoke the wrapper. The default sumfn is simply
;;          an identity which returns signal. definst:ampscale 
;;          is an appropriate function which scales amplitude in response
;;          to velocity.
;;
;; The resulting function has the lambda list
;;
;; (lambda name plist dur &rest args)
;;
;; where plist is either single MIDI note number or a list of note numbers
;; and dur is the tones duration. For each note in plist instfn is executed. 
;; The amplitude is scaled approx -6db for each doubling of notes.
;; Additional arguments usually take the form of :key value pairs and are visible 
;; to both instfn and sumfn. Keywords which the wrapper responds to are:
;; 
;; :arpeg i - a flonum which sets the delay time between playing adjacent
;;            notes in plist. non-zero values result in arpeggios. Default 0
;;
;; :pos p   - A number in interval 0<= p <= 1 or a control signal over the 
;;            same interval. Sets the pan position.
;;

(defmacro defwrapper (name instfn &optional (sumfn #'(lambda (sig args) sig)))
  `(defun ,name (plist dur &rest args)
     (let* ((plist (->list plist))
	    (pcount (length plist))
	    (arpeg (float (get-keyword-value ':arpeg args 0)))
	    (pos (get-keyword-value ':pos args 0.5))
	    (comp (get-keyword-value ':comp args 't))
	    (arglist (cons dur args)))
       (funcall ,sumfn
		(pan (scale-db (definst:comp pcount comp)
			       (simrep (index pcount)
				       (at (* index arpeg)
					   (cue (apply ,instfn (cons (nth index plist) arglist))))))
		     pos)
		args))))


;; @doc macro defwrapper-m 
;; (defwrapper name instfn [sumfn])
;; Creates an instrument wrapper named name around instrument instfn.
;; defwrapper-m is identical to defwrapper except that the instrument 
;; function may return multi-channel sounds and the :pos keyword is not
;; recognized. Although the wrapper does not recognize pos it would be
;; possible for instfn too.
;;
;; see defwrapper and examples.
;;

(defmacro defwrapper-m (name instfn &optional (sumfn #'(lambda (sig args) sig)))
  `(defun ,name (plist dur &rest args)
     (let* ((plist (->list plist))
	    (pcount (length plist))
	    (arpeg (float (get-keyword-value ':arpeg args 0)))
	    (comp (get-keyword-value ':comp args 't))
	    (arglist (cons dur args)))
       (funcall ,sumfn
		(scale-db (definst:comp pcount comp)
			  (simrep (index pcount)
				  (at (* index arpeg)
				      (cue (apply ,instfn (cons (nth index plist) arglist))))))
		args))))


#| Remove this line to execute examples

;; @doc example definst
;;
;; ***************************************************************************  
;;				   Examples
;; ***************************************************************************  

;; First we setup a MIDI velocity map to attenuate the signal by +/-6db
;; in response to velocity. 
;;
 (nice-load "map")
 (setq demo:*velmap* map:*vel6*)

;; Next we define a simple mono-phonic instrument, named foo-mono, with
;; optional vibrato and wave table selection, and velocity amplitude
;; scale. Note it would be more efficient to to use sumfn to scale amplitude
;; once but if you which the instrument function to respond to velocity its
;; possible.


 (definst foo-mono (vel vib vfrq tab pos)
  (let* ((hz (step-to-hz pitch))
	 (vamp (* hz (or vib 0))))
    (mult 
     (scale-db (send demo:*velmap* :get (or vel 64))
	      (fmosc pitch 
		     (scale vamp (lfo (or vfrq 5) dur))
		     (or tab *saw-table*)))
     (percussion dur))))


;; The lambda list for foo-mono is
;; (foo-mono pitch dur [:vel][:vib][:vfrq][:tab])  Where
;; pitch - The pitch as MIDI key number
;; dur   - The duration in seconds
;; vel   - Velocity 
;; vib   - Vibrato depth
;; vfrq  - Vibrato frequency, default 5 Hz
;; tab   - Wave table, default *saw-table*
;;         (note *saw-table* is NOT band limited)


;; Now we define a wrapper for foo-mono named foo

 (defwrapper foo #'foo-mono)

;; The lambda list for foo is
;; (foo plist dur [:vel][:arpeg][:pos][:vib][:vfrq][:tab])  Where
;;
;; plist - List of MIDI key numbers to be played. Alternatively a single key
;; number my be used. foo calls foo-mono once for each note in plist.
;; dur - The duration
;; vel - Velocity ~ Passed to foo-mono
;; arpeg - Arpeggio parameter, default 0
;; pos - Pan position, default 0.5
;; vib - Vibrato depth ~ passed to foo-mono
;; vfrq - Vibrato frequency ~ passed to foo-mono
;; tab - Wave table ~ passed to foo-mono
;;
;; If we had included pos or arpeg in the arglist while defining
;; foo-mono these variable would also be visible within foo-mono.

 (play 
 (sim 
					;We can use a single MIDI key number
  (at 0 (cue (foo a2 1)))
  					;or a list of key numbers.
  (at 1 (cue (foo (list c3 e4 g4) 1)))
  					;Vel controls amplitude
  (at 2 (cue (foo b3 1 :vel   0)))
  (at 3 (cue (foo b3 1 :vel  64)))
					;pos controls pan position
  (at 4 (cue (foo c4 1 :pos 0)))
  (at 5 (cue (foo c4 1 :pos 1)))
  (at 6 (cue (foo e4 3 :pos (ramp 3))))

  					;Vib and vfrq control vibrato
  (at 9  (cue (foo (list c3 e4 g4) 1 :vib 0.00 :vfrq 5)))
  (at 10 (cue (foo (list c3 e4 g4) 1 :vib 0.01 :vfrq 5)))
  (at 11 (cue (foo (list c3 e4 g4) 1 :vib 0.10 :vfrq 5)))

  (at 12 (cue (foo (list d4 f4 a4) 1 :vib 0.10 :vfrq 1)))
  (at 13 (cue (foo (list d4 f4 a4) 1 :vib 0.10 :vfrq 6)))
  					;Arpeggio
  (at 14 (cue (foo (list d4 f4 a4 d5 f5 a5) 1 :vib 0.01 :vfrq 1  :arpeg .1)))

  ))


;; ***************************************************************************  
;;			      *** Example 2 ***
;; ***************************************************************************  
;;
;; While instfn is executed once for each note in plist, sumfn is executed
;; only once and is applied to the sum of the results.
;;
;; Here we redefine the wrapper foo to include a dynamic filter. The filter
;; takes two keyword arguments, attack and depth and is defined as a lambda
;; expression.

 (defwrapper foo #'foo-mono 
  #'(lambda (sig args)
      (let* ((attack (get-keyword-value ':attack args 0))
	     (decay 0)
	     (hold (max 0 (- dur attack decay)))
	     (depth (get-keyword-value ':depth args 1000)))
	(lp sig (scale depth (asd attack hold decay))))))


 (play (sim 
       (at 0.0 (foo (list c3 g4 e4) 1 :vel 127 :attack 0.5 :depth 5000))
       (at 0.5 (foo (list a4 c3 e4) 1 :vel 127 :attack 1.0 :depth 1000))
       (at 1.0 (foo (list g4 d4 b4) 1 :vel 127 :attack 0.6 :depth 6000))
       (at 1.5 (foo (list f4 a4 c5) 1 :vel 127 :attack 0.6 :depth 1000 
                     :vib 0.01 :vfrq 7))
       (at 2.0 (foo (list c2 c3 c4 c5 g4 g3 g2 e2 e3 e4 c3 c2) 4 
                     :vel 127 :vib 0.01 :arpeg .1 :attack 4.0 :depth 6000))
))


Main Page       Index