/* * Utility functions derived from SuperCollider, including * - converting MIDI note values to cps * - random number generation with nonuniform distribution * - value clipping * - mapping between value ranges (linear and exponential) * - support for Spec objects, including a global named Spec register * * NOTE that map_init() must be called before attempting to access * global named Spec objects. * * Many thanks to James McCartney for many of the functions that * these were ported from - see http://www.audiosynth.com/. * * Copyright (c) Daniel Jones 2008. * * * This program can be freely distributed and modified under the * terms of the GNU General Public License version 2. * * *-------------------------------------------------------------------*/ import java.util.Hashtable; final int MAP_LIN = 0, MAP_EXP = 1, MAP_AMP = 2; // squared Hashtable map_index = new Hashtable(128); /*------------------------------------------------------------------- * midicps: Map from MIDI note to frequency *-------------------------------------------------------------------*/ float midicps (float note) { return 440.0 * pow(2, (note - 69) / 12); } /*------------------------------------------------------------------- * cpsmidi: Map from frequency to MIDI note *-------------------------------------------------------------------*/ float cpsmidi (float freq) { return (log(freq / 440.0) / log(2.0)) * 12 + 69; } /*------------------------------------------------------------------- * random2: Generate uniformly random number between [-limit..limit] *-------------------------------------------------------------------*/ float random2 (float limit) { return random(limit * 2) - limit; } /*------------------------------------------------------------------- * linrand: Random number up to limit with linear distribution *-------------------------------------------------------------------*/ float linrand (float limit) { return min(random(limit), random(limit)); } /*------------------------------------------------------------------- * bilinrand: Random number up to limit with bilinear distribution *-------------------------------------------------------------------*/ float bilinrand (float limit) { return (random(1) - random(1)) * limit; } /*------------------------------------------------------------------- * clip: Clips value between [v_min, v_max] *-------------------------------------------------------------------*/ float clip (float value, float v_min, float v_max) { if (value < v_min) return v_min; else if (value > v_max) return v_max; else return value; } /*------------------------------------------------------------------- * clip1: Clips value between [0..1] *-------------------------------------------------------------------*/ float clip1 (float value) { if (value < 0) return 0; else if (value >= 1) return 1; else return value; } /*------------------------------------------------------------------- * clip2: Clips value between [-1..1] *-------------------------------------------------------------------*/ float clip2 (float value) { if (value < -1) return -1; else if (value > 1) return 1; else return value; } /*------------------------------------------------------------------- * clip2: Clips value between [-limit..limit] *-------------------------------------------------------------------*/ float clip2 (float value, float limit) { if (value < -limit) return -limit; else if (value > limit) return limit; else return value; } /*------------------------------------------------------------------- * map: Maps a value across a predefined spec range *-------------------------------------------------------------------*/ float map (String map_name, float value) { if (map_index.containsKey(map_name)) { Spec spec = (Spec) map_index.get(map_name); return spec.map(value); } else { return 0; } } /*------------------------------------------------------------------- * linlin: Map linear range onto linear range *-------------------------------------------------------------------*/ float linlin (float x, float a, float b, float c, float d) { if (x <= a) return c; if (x >= b) return d; return (x - a) / (b - a) * (d - c) + c; } /*------------------------------------------------------------------- * linexp: Map linear range onto exponential range *-------------------------------------------------------------------*/ float linexp (float x, float a, float b, float c, float d) { if (x <= a) return c; if (x >= b) return d; return pow(d/c, (x-a)/(b-a)) * c; } /*------------------------------------------------------------------- * explin: Map exponential range onto linear range *-------------------------------------------------------------------*/ float explin (float x, float a, float b, float c, float d) { if (x <= a) return c; if (x >= b) return d; return (log(x / a)) / (log(b / a)) * (d - c) + c; } /*------------------------------------------------------------------- * expexp: Map exponential range onto exponential range *-------------------------------------------------------------------*/ float expexp (float x, float a, float b, float c, float d) { if (x <= a) return c; if (x >= b) return d; return pow(d / c, log(x / a) / log(b / a)) * c; } /*------------------------------------------------------------------- * map1: Linear map from [0..1] to [low..high] *-------------------------------------------------------------------*/ float map1 (float value, float low, float high) { return low + value * (high - low); } /*------------------------------------------------------------------- * map_register: Registers a named Spec in the global hashtable, * which can be subsequently used with new Spec("name"); *-------------------------------------------------------------------*/ void map_register (String map_name, float v_min, float v_max, int v_type) { map_index.put(map_name, new Spec(v_min, v_max, v_type)); } /*------------------------------------------------------------------- * map_init: Populates global spec table with default specs. *-------------------------------------------------------------------*/ void map_init () { map_index.put("unipolar", new Spec("unipolar", 0, 1, MAP_LIN)); map_index.put("freq", new Spec("freq", 20, 20000, MAP_EXP)); map_index.put("amp", new Spec("amp", 0, 1, MAP_AMP)); map_index.put("wet", new Spec("wet", 0, 1, MAP_LIN)); map_index.put("pan", new Spec("pan", -1, 1, MAP_LIN)); } /*------------------------------------------------------------------- * spec: Returns the global spec object associated with a given name * (analogous to SC's \amp.asSpec) *-------------------------------------------------------------------*/ Spec spec (String name) { return (Spec) map_index.get(name); } /*------------------------------------------------------------------- * class Spec: Analogous to SC's ControlSpec class. *-------------------------------------------------------------------*/ class Spec { String name; float min, max; int type; Spec(float v_min, float v_max, int v_type) { name = ""; min = v_min; max = v_max; type = v_type; } Spec(String v_name, float v_min, float v_max, int v_type) { name = v_name; min = v_min; max = v_max; type = v_type; } Spec (String v_name) { Spec peer = (Spec) map_index.get(v_name); if (peer != null) { this.name = peer.name; this.min = peer.min; this.max = peer.max; this.type = peer.type; } else { println("** Specification not found: " + v_name); } } float map (float value) { value = clip(value, 0.0, 1.0); float mapped = 0.0; switch (type) { case MAP_LIN: mapped = min + ((max - min) * value); break; case MAP_EXP: mapped = min * pow(max / min, value); break; case MAP_AMP: mapped = min + ((max - min) * sq(value)); break; } return mapped; } float unmap (float value) { float unmapped = 0.0; switch (type) { case MAP_LIN: unmapped = (value - min) / (max - min); break; case MAP_EXP: unmapped = log(value / min) / log(max / min); break; case MAP_AMP: unmapped = (value - min) / (max - min); if (unmapped > 0) unmapped = sqrt(unmapped); break; } return clip(unmapped, 0.0, 1.0); } }