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
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
(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
(send /map:discrete/ :get-vector) return - vector. The underlying vector.
method
(send /map:discrete/ :get-length) return - integer. The number of map elements, excluding guard point.
method
(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
(send /map:discrete/ :set-imode sym ) Set the current indexing mode. See :get-imode for possible values
method
(send /map:discrete/ :get-interpolate ) return - bool. True if interpolation mode is active
method
(send /map:discrete/ :set-interpolate flag) flag - bool. If true use interpolation for non-whole number indexes.
method
(send /map:discrete/ :has-guard-point ) return - bool. true if there is a guard point
method
(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
(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
(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
(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
(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
String representation of map return - string.
method
(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
(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
(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
(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
A very thin wrapper around a function. map:functional provides the same interface for "getting" map values as map:discrete
method
(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
(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
(send /map:functional/ :get-function) return - closure.
method
(send map:functional :get i) i - flonum. The "index" return - any. fn(i)
function
(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 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 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 [: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 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 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 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 c) Create a map object with constant value c - flonum. The constant return - object. An instance of map:functional which always returns c
;; 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))