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))