Main Page       Index


map.lsp


 map.lsp
 Version 1.001 11 June 2005    Minor bug fix
 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

 A map defines a relationship between a numeric index and a function on that
 index. The intent is to map MIDI velocity and controller values but more
 general maps are possible. Two map classes are defined. The
 map:discrete class is backed by an array. The map:functional class is a
 thin wrapper around some mapping function. Both classes expose the method
 :get for retrieving map values.


class

map:discrete

 Provides a thin wrapper around a vector. 
 There are three possible indexing modes. 
 'LIMIT  - Out of bounds indexes are limited to 0 or array length.
 'CYCLE  - Out of bounds indexes wrap around, negative index are accepted.
 'NORMAL - Out of bounds index raise an error.

 Linear interpolation is also possible. This of course assumes the array
 contains numeric data. The intended use of map:discrete is for mapping MIDI
 controller and velocity values.


method

map:discrete :new

 (send map:discrete :new  length [:imode][:interpolate][:guardpoint][:fillfn])
 Constructor for map:discrete object

 length       - Integer. The number of data points.

 :imode       - Symbol. Determines how index values are calculated. There are
                three possible imodes:

                'LIMIT  - out of bounds indexes are limited to either 0 or 
                          array length.
                'CYCLE  - out of bounds indexes are wrapped to other extreme.
                'NIL    - out of bound indexes cause an array out of bounds 
                          error.

 :interpolate - bool. If true fractional array values interpolate (linear)
                between adjacent map elements. Interpolation may only be 
                used if the map contains purely numeric data.
                Default 'NIL

 :guardpoint - bool. If true add guard point to end of map array.
               Default 'NIL

 :fillfn     - closure. A single argument function used to initially fill the 
               array. The default is an identity #'(lambda (n) n)
           
 return      - Object. A new instance of map:discrete


method

map:discrete :get-vector

 (send /map:discrete/ :get-vector)

 return - vector. The underlying vector.



method

map:discrete :get-length

 (send /map:discrete/ :get-length)

 return - integer. The number of map elements, excluding guard point.


method

map:discrete :get-imode

 (send /map:discrete/ :get-imode)
 Get the current indexing mode

 return - symbol. The indexing is indicated by one of the following symbols.
          'LIMIT out of bound indexes are limited to 0 or map length.  
          'CYCLE out of bound indexes are wrapped to the other extreme
          Any other value is treated as a normal array index. Out of bounds
          indexes throw an error.


method

map:discrete :set-imode

 (send /map:discrete/ :set-imode sym )
 Set the current indexing mode. See :get-imode for possible values


method

map:discrete :get-interpolate

 (send /map:discrete/ :get-interpolate )
 
 return - bool. True if interpolation mode is active 


method

map:discrete :set-interpolate

 (send /map:discrete/ :set-interpolate flag)
 
 flag - bool. If true use interpolation for non-whole number indexes. 


method

map:discrete :has-guard-point

 (send /map:discrete/ :has-guard-point )

 return - bool. true if there is a guard point


method

map:discrete :get-guard-value

 (send /map:discrete/ :get-guard-value)

 return - any. The value held in the guard point. If there is no guard point
          an error is raised


method

map:discrete :set-guard-value

 (send /map:discrete/ :set-guard-value  val)

 val - any. Set the value of the guard point. If there is no guard point an
       error is thrown


method

map:discrete :get-index

 (send /map:discrete/ :get-index i)
 Determine the actual array index i' for the argument index i using the
 current indexing mode.

 i      - flonum. The index value
 return - integer (flonum?). The actual array index


method

map:discrete :set

 (send /map:discrete/ :set i val)
 Set map location at index i to val
 
 i      - integer. The index. The actual array index is determined by the 
          current indexing mode and may not equal i

 val    - any. The value to store at index i, typically a number.

 return - integer. The actual array index.


method

map:discrete :get

 (send /map:discrete/ :get i)
 Get indexed value from map. The actual index is determined by the indexing
 mode and may not be equal to i. If i contains a fractional component and
 interpolation is enabled the result is interpolated between adjacent map
 elements.

 i      - flonum. The index

 return - flonum. fn(i)


method

map:discrete :repr

 String representation of map

 return - string.


method

map:discrete :apply-function

 (send /map-discrete/ :apply-function  fn [x0 [x1]])
 Apply function to each map element between indexes x0 and x1.

 fn  - closure. The function to be applied should have the form
       (lambda (x) ...)
 x0  - flonum. Minimum index to effect, default 0
 x1  - flonum. Maximum index to effect, default map length.


method

map:discrete :clamp

 (send /map-discrete/ :clamp  minval maxval [x0 [x1]])
 Restrict map values to interval (minval, maxval) over indexes x0...x1

 minval - flonum. The minimum allowed value.
 maxval - flonum. The maximum allowed value.
 x0     - flonum. Minimum index to effect, default 0
 x1     - flonum. Maximum index to effect, default map length.


method

map:discrete :add

 (send /map-discrete/ :add  c [x0 [x1]])
 Add constant value to each map element in index interval x0..x1

 c      - flonum. The constant value.
 x0     - flonum. Minimum index to effect, default 0
 x1     - flonum. Maximum index to effect, default map length.


method

map:discrete :scale

 (send /map-discrete/ :scale  r [x0 [x1]])
 Scale each map element in index interval x0..x1

 r      - flonum. The scale factor.
 x0     - flonum. Minimum index to effect, default 0
 x1     - flonum. Maximum index to effect, default map length.


class

map:functional

 A very thin wrapper around a function. map:functional provides the same
 interface for "getting" map values as map:discrete


method

map:functional :new

 (send /map:functional/ :new   fn)
 Constructor for map:fictional

 fn     - closure. The function to provide the mapping. fn should accept a
          single numeric value and return an arbitrary value, typically a 
          number.

 return - object. A new instance of map:functional


method

map:functional :set-function

 (send /map:functional/ :set-function fn)

 fn - closure. Set the function used to provide the mapping. fn should take
      a single numeric argument and return an arbitrary value, typically a
      number.


method

map:functional :get-function

 (send /map:functional/ :get-function)

 return - closure.


method

map:functional :get

 (send map:functional :get i)

 i      - flonum. The "index"

 return - any. fn(i)


function

map:linear

 (map:linear y0 y1 [x0 [x1]])
 Create a new linear map object through the points (x0,y0)(x1,y1) 
 map(x0) --> y0
 map(x1) --> y1

 y0     - flonum Range value 0
 y1     - flonum Range value 1
 x0     - flonum Domain value 0, x0 != x1, default 0 
 x1     - flonum Domain value 1, x0 != x1, default 128

 return - object. A new instance of map:functional


function

map:quad

 (map:quad  y0 y1 [x0 [x1 [c]]])
 Create exponential map (quadratic by default) through the points
 (x0,y0) and (x1,y1)

 y0     - flonum. Range value 0.
 y1     - flonum. Range value 1.
 x0     - flonum. Domain value 0, x0 != x1, default 0.
 x1     - flonum. Domain value 1, x0 != x1, default 128.
 c      - flonum. Exponential growth, default 2.

 return - object. A new instance of map:functional


function

map:linear-knee

 (map:linear-knee  y0 y1 y2 [x0 [x1 [x2]]])
 Create linear map with rollover point. May be used for compression 
 like curves.

 y0 - flonum. 
 y1 - flonum.
 y2 - flonum.
 x0 - flonum. x0 < x1 < x2,  Default 0
 x1 - flonum. x0 < x1 < x2,  Default 64
 x2 - flonum. x0 < x1 < x2,  Default 128

 return - object. A new instance of map:functional. 
 Typical curve

 y2                 *
                 *
              *
           *
        *
 y1    *
      *
     *
 y0 *
    x0  x1          x2


function

map:split

 (map:split [:y0][:y1][:y2][:x0][:x1][:x2][:c1][:c2])
 map:split is a generalization of map:linear-knee. It produces a 2-part 
 map where the graph may be linear or non-linear, separately, on either 
 side of the break point.

 :y0    - Flonum. The map value f(x0) => y0, default 0.0
 :y1    - Flonum. The map value f(x1) => y1, default (mean y0 y2)
 :y2    - Flonum. The map value f(x2) => y2, default 1.0
 :x0    - Flonum. The minimum map domain, default 0.0
 :x1    - Flonum. The domain of map at the break point. 
          It is an error for x1 <= x0, default (mean x0 x2)
 :x2    - Flonum. The maximum map domain.
          It is an error for x2 <= x1, default 128.0
 :c1    - Flonum. The linearity of the graph over domain (x0,x1).
          0 < c1. For c1 = 1 the graph is linear. Default 1
 :c2    - Flonum. The linearity of the graph over domain (x1,x2)
          0 < c2. For c2 = 1 the graph is linear. Default 2
 return - Object. A new instance of map:functional.


function

map:graph

 (map:graph flag mapobj [:x0][:x1][:sp][:dot][:yscale][:zeros])
 Construct and optionally display crude bar graph representation of
 map object.

 flag    - bool. If true display result, otherwise return a string.
 mapobj  - object. An instance of map.
 :x0     - flonum. Domain point 0, default 0.
 :x1     - flonum. Domain point 1, default 128.
 :sp     - string. Character used for bargraph space, default '-'.
 :dot    - string. Character used to terminate bargraph, default 'X'.
 :yscale - flonum. Scale factor applied to y-axis, default 0.75
 :zeros  - integer. Default 3
 return  - string or nil.


function

map:ctrl

 (map:ctrl  mx ctrl [mn [limit]])
 Map MIDI controller to parameter value.
 A simplified mapping for MIDI controller values.
 
 mx     - flonum. The maximum parameter value
 ctrl   - flonum. The MIDI controller value. 0 <= ctrl < 128
 mn     - flonum. The minimum parameter value, default 0
 limit  - boolean. If true limit result to interval (mn,mx)
          default nil
 return - flonum. (map:ctrl mx 000 mn) --> mn
                  (map:ctrl mx 128 mn) --> mx


function

map:keyword

 (map:keyword sym args maxval minval [default [limit])
 A convenience function provides the composition of map:ctrl and
 get-keyword-value. Typically used to map MIDI controller to parameter
 values. 

 sym     - Symbol. Keyword symbol as used by get-keyword-value
 args    - List. List of key symbol/value pairs as used by get-keyword-value
 maxval  - Flonum. The mapping for sym(128) --> maxval
 minval  - Flonum. The mapping for sym(0) ---> minval
 default - Flonum. The default symbol value sym(default)
 limit   - Boolean. If true result is limited to interval [minval,maxval] 


function

map:constant

 (map:constant c)
 Create a map object with constant value
 
 c      - flonum. The constant
 return - object. An instance of map:functional which always returns c


View the Sourcecode :



;; map.lsp
;; Version 1.001 11 June 2005    Minor bug fix
;; 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
;;
;; A map defines a relationship between a numeric index and a function on that
;; index. The intent is to map MIDI velocity and controller values but more
;; general maps are possible. Two map classes are defined. The
;; map:discrete class is backed by an array. The map:functional class is a
;; thin wrapper around some mapping function. Both classes expose the method
;; :get for retrieving map values.
;;

(require 'linfn)
(require 'su)
(provide 'map)
(current-file "map")


; ***************************************************************************  
;			      map:discrete class
; ***************************************************************************  

;; @doc class map:discrete
;; Provides a thin wrapper around a vector. 
;; There are three possible indexing modes. 
;; 'LIMIT  - Out of bounds indexes are limited to 0 or array length.
;; 'CYCLE  - Out of bounds indexes wrap around, negative index are accepted.
;; 'NORMAL - Out of bounds index raise an error.
;;
;; Linear interpolation is also possible. This of course assumes the array
;; contains numeric data. The intended use of map:discrete is for mapping MIDI
;; controller and velocity values.
;; 

(setf map:discrete (send class :new '(.map .length .index-mode .interpolate .has-guard-point)))


;; @doc method map:discrete :new
;; (send map:discrete :new  length [:imode][:interpolate][:guardpoint][:fillfn])
;; Constructor for map:discrete object
;;
;; length       - Integer. The number of data points.
;;
;; :imode       - Symbol. Determines how index values are calculated. There are
;;                three possible imodes:
;;
;;                'LIMIT  - out of bounds indexes are limited to either 0 or 
;;                          array length.
;;                'CYCLE  - out of bounds indexes are wrapped to other extreme.
;;                'NIL    - out of bound indexes cause an array out of bounds 
;;                          error.
;;
;; :interpolate - bool. If true fractional array values interpolate (linear)
;;                between adjacent map elements. Interpolation may only be 
;;                used if the map contains purely numeric data.
;;                Default 'NIL
;;
;; :guardpoint - bool. If true add guard point to end of map array.
;;               Default 'NIL
;;
;; :fillfn     - closure. A single argument function used to initially fill the 
;;               array. The default is an identity #'(lambda (n) n)
;;           
;; return      - Object. A new instance of map:discrete
;;

(send map:discrete :answer :isnew '(length &key imode interpolate guardpoint fillfn)
      '((setf .length length)
	(setf .index-mode (or imode nil))
	(setf .interpolate (or interpolate nil))
	(setf .has-guard-point (or guardpoint nil))
	(setf fillfn (or fillfn #'idfn))
	(setf .map (if .has-guard-point
		       (make-array (+ .length 1))
		     (make-array .length)))
	(dotimes (i (length .map))
	  (setf (aref .map i )(funcall fillfn i)))))


;; @doc method map:discrete :get-vector
;; (send /map:discrete/ :get-vector)
;;
;; return - vector. The underlying vector.
;;

(send map:discrete :answer :get-vector '()
      '(.map))


;; @doc method map:discrete :get-length
;; (send /map:discrete/ :get-length)
;;
;; return - integer. The number of map elements, excluding guard point.
;;

(send map:discrete :answer :get-length '()
      '(.length))


;; @doc method map:discrete :get-imode
;; (send /map:discrete/ :get-imode)
;; Get the current indexing mode
;;
;; return - symbol. The indexing is indicated by one of the following symbols.
;;          'LIMIT out of bound indexes are limited to 0 or map length.  
;;          'CYCLE out of bound indexes are wrapped to the other extreme
;;          Any other value is treated as a normal array index. Out of bounds
;;          indexes throw an error.
;;

(send map:discrete :answer :get-imode '()
      '(.imode))


;; @doc method map:discrete :set-imode
;; (send /map:discrete/ :set-imode sym )
;; Set the current indexing mode. See :get-imode for possible values
;;

(send map:discrete :answer :set-imode '(sym)
      '(setf .imode sym))


;; @doc method map:discrete :get-interpolate
;; (send /map:discrete/ :get-interpolate )
;; 
;; return - bool. True if interpolation mode is active 
;;

(send map:discrete :answer :get-interpolate '()
      '(.interpolate))


;; @doc method map:discrete :set-interpolate
;; (send /map:discrete/ :set-interpolate flag)
;; 
;; flag - bool. If true use interpolation for non-whole number indexes. 
;;

(send map:discrete :answer :set-interpolate '(val)
      '(setf .interpolate val))


;; @doc method map:discrete :has-guard-point
;; (send /map:discrete/ :has-guard-point )
;;
;; return - bool. true if there is a guard point
;;

(send map:discrete :answer :has-guard-point '()
      '(.has-guard-point))


;; @doc method map:discrete :get-guard-value
;; (send /map:discrete/ :get-guard-value)
;;
;; return - any. The value held in the guard point. If there is no guard point
;;          an error is raised
;;

(send map:discrete :answer :get-guard-value '()
      '((if .has-guard-point
	    (aref .map (1- (length .map)))
	  (error "map:discrete:  Map does not have a guard point"))))


;; @doc method map:discrete :set-guard-value
;; (send /map:discrete/ :set-guard-value  val)
;;
;; val - any. Set the value of the guard point. If there is no guard point an
;;       error is thrown
;;

(send map:discrete :answer :set-guard-value '(val)
      '((if .has-guard-point
	    (setf (aref .map (1- (length .map))))
	  (error "map:discrete: Map does not have a guard point"))))
      

;; For index i return the limit mode index i'
;;

(send map:discrete :answer :get-limit-mode-index '(i)
      '((clamp i 0 (1- .length))))


;; For index i return the cycle mode index i'
;;

(send map:discrete :answer :get-cycle-mode-index '(i)
       '((let (( r (rem (truncate i) .length)))
 	  (cond 
 	   ((zerop r) 0)
 	   ((plusp i) r)
 	   (t (+ .length r))))))


;; @doc method map:discrete :get-index
;; (send /map:discrete/ :get-index i)
;; Determine the actual array index i' for the argument index i using the
;; current indexing mode.
;;
;; i      - flonum. The index value
;; return - integer (flonum?). The actual array index
;;

(send map:discrete :answer :get-index '(i)
      '((cond ((eq .index-mode 'LIMIT)
	       (send self :get-limit-mode-index i))
	      ((eq .index-mode 'CYCLE)
	       (send self :get-cycle-mode-index i))
	      (t i))))


;; @doc method map:discrete :set
;; (send /map:discrete/ :set i val)
;; Set map location at index i to val
;; 
;; i      - integer. The index. The actual array index is determined by the 
;;          current indexing mode and may not equal i
;;
;; val    - any. The value to store at index i, typically a number.
;;
;; return - integer. The actual array index.
;;

(send map:discrete :answer :set '(i val)
      '((let (index)
	  (setf index (send self :get-index i))
	  (setf (aref .map index) val)
	  index
	  )))


;; Get value from array without interpolation
;;

(send map:discrete :answer :get-non-interpolate '(i)
      '((let (index)
	  (setf index (send self :get-index i))
	  (aref .map (truncate index))
	  )))


;; Get value from array using interpolation
;;

(send map:discrete :answer :get-interpolate '(i)
      '((let (i0 i1 f v0 v1)
	  (setf i0 (send self :get-index i))
	  (setf i1 (send self :get-index (1+ i)))
	  (setf f (cdr (split-number i)))
	  (setf v0 (aref .map (truncate i0)))
	  (setf v1 (aref .map (truncate i1)))
	  (+ (* v0 (- 1 f))(* v1 f)))))


;; @doc method map:discrete :get
;; (send /map:discrete/ :get i)
;; Get indexed value from map. The actual index is determined by the indexing
;; mode and may not be equal to i. If i contains a fractional component and
;; interpolation is enabled the result is interpolated between adjacent map
;; elements.
;;
;; i      - flonum. The index
;;
;; return - flonum. fn(i)
;;

(send map:discrete :answer :get '(i)
      '((if .interpolate
	    (send self :get-interpolate i)
	  (send self :get-non-interpolate i))))


;; @doc method map:discrete :repr
;; String representation of map
;;
;; return - string.
;;

(send map:discrete :answer :repr '()
      '((let ((acc (format nil "map:discrete  length ~a  mode ~a  interpolate ~a guard point ~a~%"
			   .length .index-mode .interpolate .has-guard-point)))
	  (dotimes (i .length)
	    (setf acc (strcat acc (format nil "[~a] --> ~a~%" i (aref .map i)))))
	  (if .has-guard-point
	      (setf acc (strcat acc (format nil "[guard] --> ~a~%" (aref .map (1- (length .map)))))))
	  acc)))


;; @doc method map:discrete :apply-function
;; (send /map-discrete/ :apply-function  fn [x0 [x1]])
;; Apply function to each map element between indexes x0 and x1.
;;
;; fn  - closure. The function to be applied should have the form
;;       (lambda (x) ...)
;; x0  - flonum. Minimum index to effect, default 0
;; x1  - flonum. Maximum index to effect, default map length.
;;

(send map:discrete :answer :apply-function '(fn &optional x0 x1)
      '((let (dif offset j)
	  (setq x0 (or x0 0))
	  (setq x1 (or x1 .length))
	  (setq dif (abs (- x1 x0)))
	  (setq offset (min x0 x1))
	  (dotimes (i dif)
	    (setq j (+ i offset))
	    (send self :set j (funcall fn (send self :get j)))))))


;; @doc method map:discrete :clamp
;; (send /map-discrete/ :clamp  minval maxval [x0 [x1]])
;; Restrict map values to interval (minval, maxval) over indexes x0...x1
;;
;; minval - flonum. The minimum allowed value.
;; maxval - flonum. The maximum allowed value.
;; x0     - flonum. Minimum index to effect, default 0
;; x1     - flonum. Maximum index to effect, default map length.
;;

(send map:discrete :answer :clamp '(minval maxval &optional x0 x1)
      '((send self :apply-function #'(lambda (x)(clamp x minval maxval)) x0 x1)))


;; @doc method map:discrete :add
;; (send /map-discrete/ :add  c [x0 [x1]])
;; Add constant value to each map element in index interval x0..x1
;;
;; c      - flonum. The constant value.
;; x0     - flonum. Minimum index to effect, default 0
;; x1     - flonum. Maximum index to effect, default map length.
;;

(send map:discrete :answer :add '(c &optional x0 x1)
      '((send self :apply-function #'(lambda (x)(+ x c)) x0 x1)))


;; @doc method map:discrete :scale
;; (send /map-discrete/ :scale  r [x0 [x1]])
;; Scale each map element in index interval x0..x1
;;
;; r      - flonum. The scale factor.
;; x0     - flonum. Minimum index to effect, default 0
;; x1     - flonum. Maximum index to effect, default map length.
;;

(send map:discrete :answer :scale '(r &optional x0 x1)
      '((send self :apply-function #'(lambda (x)(* x r)) x0 x1)))


; ***************************************************************************  
;			     map:functional class
; ***************************************************************************

;; @doc class map:functional 
;; A very thin wrapper around a function. map:functional provides the same
;; interface for "getting" map values as map:discrete
;;

(setf map:functional (send class :new '(.fn)))



;; @doc method map:functional :new
;; (send /map:functional/ :new   fn)
;; Constructor for map:fictional
;;
;; fn     - closure. The function to provide the mapping. fn should accept a
;;          single numeric value and return an arbitrary value, typically a 
;;          number.
;;
;; return - object. A new instance of map:functional
;;

(send map:functional :answer :isnew '(&optional (fn #'idfn))
      '((setf .fn fn)))


;; @doc method map:functional :set-function
;; (send /map:functional/ :set-function fn)
;;
;; fn - closure. Set the function used to provide the mapping. fn should take
;;      a single numeric argument and return an arbitrary value, typically a
;;      number.
;;

(send map:functional :answer :set-function '(fn)
      '((setf .fn fn)))


;; @doc method map:functional :get-function
;; (send /map:functional/ :get-function)
;;
;; return - closure.
;;

(send map:functional :answer :get-function '()
      '(.fn))


;; @doc method map:functional :get
;; (send map:functional :get i)
;;
;; i      - flonum. The "index"
;;
;; return - any. fn(i)
;;

(send map:functional :answer :get '(i)
      '((funcall .fn i)))


(send map:functional :answer :repr '()
      '((let ((acc "map:functional"))
	  (setf acc (strcat acc (format nil "~%~a~%" (get-lambda-expression .fn))))
	  acc)))


; ***************************************************************************  
;			       Factory Methods
; ***************************************************************************  

;; @doc function map:linear
;; (map:linear y0 y1 [x0 [x1]])
;; Create a new linear map object through the points (x0,y0)(x1,y1) 
;; map(x0) --> y0
;; map(x1) --> y1
;;
;; y0     - flonum Range value 0
;; y1     - flonum Range value 1
;; x0     - flonum Domain value 0, x0 != x1, default 0 
;; x1     - flonum Domain value 1, x0 != x1, default 128
;;
;; return - object. A new instance of map:functional
;;

(defun map:linear (y0 y1 &optional (x0 0)(x1 128))
  (let* ((dy (float (- y1 y0)))
	 (dx (float (- x1 x0)))
	 (slope (/ dy dx))
	 (c (- y0 (* slope x0))))
    (send map:functional :new #'(lambda (x)(+ (* slope x) c)))))


;; @doc function map:quad
;; (map:quad  y0 y1 [x0 [x1 [c]]])
;; Create exponential map (quadratic by default) through the points
;; (x0,y0) and (x1,y1)
;;
;; y0     - flonum. Range value 0.
;; y1     - flonum. Range value 1.
;; x0     - flonum. Domain value 0, x0 != x1, default 0.
;; x1     - flonum. Domain value 1, x0 != x1, default 128.
;; c      - flonum. Exponential growth, default 2.
;;
;; return - object. A new instance of map:functional
;;

(defun map:quad (y0 y1 &optional (x0 0)(x1 128)(c 2))
  (let* ((dy (float (- y1 y0)))
	 (dx (- (expt (float x1) c)(expt (float x0) c)))
	 (a (/ dy dx))
	 (b (- y0 (* a (expt (float x0) c)))))
    (send map:functional :new #'(lambda (x)(+ (* a (expt (float x) c)) b)))))


;; @doc function map:linear-knee
;; (map:linear-knee  y0 y1 y2 [x0 [x1 [x2]]])
;; Create linear map with rollover point. May be used for compression 
;; like curves.
;;
;; y0 - flonum. 
;; y1 - flonum.
;; y2 - flonum.
;; x0 - flonum. x0 < x1 < x2,  Default 0
;; x1 - flonum. x0 < x1 < x2,  Default 64
;; x2 - flonum. x0 < x1 < x2,  Default 128
;;
;; return - object. A new instance of map:functional. 
;; Typical curve
;;
;; y2                 *
;;                 *
;;              *
;;           *
;;        *
;; y1    *
;;      *
;;     *
;; y0 *
;;    x0  x1          x2
;;

(defun map:linear-knee (y0 y1 y2 &optional (x0 0)(x1 64)(x2 128))
  (send map:discrete :new (- x2 x0) 
	:fillfn #'(lambda (i)(linfn:3pt i x0 y0 x1 y1 x2 y2))))


;; @doc function map:split
;; (map:split [:y0][:y1][:y2][:x0][:x1][:x2][:c1][:c2])
;; map:split is a generalization of map:linear-knee. It produces a 2-part 
;; map where the graph may be linear or non-linear, separately, on either 
;; side of the break point.
;;
;; :y0    - Flonum. The map value f(x0) => y0, default 0.0
;; :y1    - Flonum. The map value f(x1) => y1, default (mean y0 y2)
;; :y2    - Flonum. The map value f(x2) => y2, default 1.0
;; :x0    - Flonum. The minimum map domain, default 0.0
;; :x1    - Flonum. The domain of map at the break point. 
;;          It is an error for x1 <= x0, default (mean x0 x2)
;; :x2    - Flonum. The maximum map domain.
;;          It is an error for x2 <= x1, default 128.0
;; :c1    - Flonum. The linearity of the graph over domain (x0,x1).
;;          0 < c1. For c1 = 1 the graph is linear. Default 1
;; :c2    - Flonum. The linearity of the graph over domain (x1,x2)
;;          0 < c2. For c2 = 1 the graph is linear. Default 2
;; return - Object. A new instance of map:functional.
;;

(defun map:split  (&key (y0 0) y1 (y2 1) (x0 0) x1 (x2 128)(c1 1)(c2 1))
  (setf x1 (or x1 (mean x0 x2)))
  (setf y1 (or y1 (mean y1 y2)))
  (assert (> x2 x1 x0)
	  "map:split expects domain to be monotonically increasing.")
  (let (dy1 dx1 a1 b1   dy2 dx2 a2 b2)
    (setf dy1 (float (- y1 y0))
	  dx1 (- (expt (float x1) c1)(expt (float x0) c1))
	  a1 (/ dy1 dx1)
	  b1 (- y0 (* a1 (expt (float x0) c1))))
    (setf dy2 (float (- y2 y1))
	  dx2 (- (expt (float x2) c2)(expt (float x1) c2))
	  a2 (/ dy2 dx2)
	  b2 (- y1 (* a2 (expt (float x1) c2))))
    (send map:functional :new 
	  #'(lambda (x)
	      (if (< x x1)
		  (+ (* a1 (expt (float x) c1)) b1)
		(+ (* a2 (expt (float x) c2)) b2))))))


;; Draws single graph line
;;

(defun map:bargraph (x y &key (sp " ")(dot "*")(yscale 1.0)(zeros 3))
  (let ((acc (strcat (su:zfill (->string x) zeros) " "))
	(dots (truncate (* y yscale))))
    (dotimes (i dots)
      (setq acc (strcat acc sp)))
    (strcat acc dot)))


;; @doc function map:graph
;; (map:graph flag mapobj [:x0][:x1][:sp][:dot][:yscale][:zeros])
;; Construct and optionally display crude bar graph representation of
;; map object.
;;
;; flag    - bool. If true display result, otherwise return a string.
;; mapobj  - object. An instance of map.
;; :x0     - flonum. Domain point 0, default 0.
;; :x1     - flonum. Domain point 1, default 128.
;; :sp     - string. Character used for bargraph space, default '-'.
;; :dot    - string. Character used to terminate bargraph, default 'X'.
;; :yscale - flonum. Scale factor applied to y-axis, default 0.75
;; :zeros  - integer. Default 3
;; return  - string or nil.
;;

(defun map:graph (flag mapobj &key (x0 0)(x1 128)(sp "-")(dot "X")(yscale 0.75)(zeros 3))
  (let ((acc "")
	(points (- (max x0 x1)(min x0 x1)))
	(offset (min x0 x1))
	(j nil)
	(y nil))
    (dotimes (i points)
      (setq j (+ i offset))
      (setq y (send mapobj :get j))
      (setq acc (strcat acc (map:bargraph j y :sp sp :dot dot :yscale yscale :zeros zeros) "\n"))
      )
    (format flag "~a" acc)))


;; @doc function map:ctrl
;; (map:ctrl  mx ctrl [mn [limit]])
;; Map MIDI controller to parameter value.
;; A simplified mapping for MIDI controller values.
;; 
;; mx     - flonum. The maximum parameter value
;; ctrl   - flonum. The MIDI controller value. 0 <= ctrl < 128
;; mn     - flonum. The minimum parameter value, default 0
;; limit  - boolean. If true limit result to interval (mn,mx)
;;          default nil
;; return - flonum. (map:ctrl mx 000 mn) --> mn
;;                  (map:ctrl mx 128 mn) --> mx
;;

(defun map:ctrl (mx ctrl &optional (mn 0)(limit 'nil))
  (let (dx dy slope b)
    (setq dx 128.0
	  dy (float (- mx mn))
	  slope (/ dy dx)
	  b (- mx (* slope dx)))
    (if limit
	(clamp (+ (* slope ctrl) b) mn mx)
      (+ (* slope ctrl) b))))


;; @doc function map:keyword 
;; (map:keyword sym args maxval minval [default [limit])
;; A convenience function provides the composition of map:ctrl and
;; get-keyword-value. Typically used to map MIDI controller to parameter
;; values. 
;;
;; sym     - Symbol. Keyword symbol as used by get-keyword-value
;; args    - List. List of key symbol/value pairs as used by get-keyword-value
;; maxval  - Flonum. The mapping for sym(128) --> maxval
;; minval  - Flonum. The mapping for sym(0) ---> minval
;; default - Flonum. The default symbol value sym(default)
;; limit   - Boolean. If true result is limited to interval [minval,maxval] 
;;

(defun map:keyword (sym args maxval minval &optional (default 0)(limit 'nil))
  (map:ctrl maxval (get-keyword-value sym args default) minval limit))


;; @doc function map:constant 
;; (map:constant c)
;; Create a map object with constant value
;; 
;; c      - flonum. The constant
;; return - object. An instance of map:functional which always returns c
;;

(defun map:constant (c)
  (map:linear c c 0 1))


Main Page       Index