/*
* 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);
}
}