// our patch 22.5/100.0 => float head_width; 0.8 => float hrtf_xy; // this needs to be a table based on alpha/phi 0.333 => float hrtf_z; // this needs to be a table based on alpha/phi 331.3 => float sound_speed_k; // m/sec 300.0 => float default_max_distance; // in meters (should be function of sound source) 20.5 => float temperature; // in C; query this real-time sound_speed_k * Math.sqrt(1 + temperature / 273.15) => float sound_speed; class Point { float x, y, z; 0.0 => x => y => z; public void set(float dx, float dy, float dz) { dx => x; dy => y; dz => z; } public void set(Point p) { p.x => x; p.y => y; p.z => z; } public void move(float dx, float dy, float dz) { x + dx => x; y + dy => y; z + dz => z; } public float distance(float dx, float dy, float dz) { Math.sqrt(Math.pow(x - dx, 2) + Math.pow(y - dy, 2) + Math.pow(z - dz, 2)) => float d; return d; } } class BinAural { Delay d_left, d_right; Gain g_left, g_right; Point loc; .6 => float bin_gain => g_left.gain => g_right.gain; d_left.max(5::second); d_right.max(5::second); public float src_gain() { return bin_gain; } public void connect(UGen l, UGen r) { d_left => g_left => l; d_right => g_right => r; MoveXYZ(0, head_width, 0); } public void set_gain(float g) { g => bin_gain; } // distance from implied origin of (0, 0, 0) fun float distance(float dx, float dy, float dz) { Math.sqrt(dx * dx + dy * dy + dz * dz) => float d; return d; } // farthest relative distance from origin the sound is perceptible (override me) fun float max_distance() { return bin_gain * default_max_distance; } fun float dissipation(float theta, float phi, float g, float d) { max_distance() => float max_distance; Math.min(d, max_distance) => d; g - g * (Math.pow(d,1/2.0) / Math.pow(max_distance,1/2.0)) => float new_g; Math.max(new_g, 0.00) => new_g; 0.0 => float xyhk; Math.sin(theta) => float sin_theta; Math.cos(theta) => float cos_theta; (cos_theta + 1.0) / 2.0 => xyhk; // behind head if (sin_theta < 0) { new_g * .667 => new_g; } Math.cos(phi) => float zhk; // sound above the plane of the ears if (zhk >= 0) { zhk / 2.0 => zhk; // sound below the plane of the ears } else { zhk / -1.0 => zhk; } // <<< "dis", theta, xyhk >>>; new_g - new_g * hrtf_xy * xyhk - new_g * hrtf_z * zhk=> new_g; return Math.max(new_g, 0.0); } public void MoveXYZ(Point p) { MoveXYZ(p.x, p.y, p.z); } public void MoveXYZ(float dx, float dy, float dz) { // compute radius from head-center to location distance(dx, dy, dz) => float r; // <<< "distance", dx, dy, dz, r >>>; pi / 2.0 => float theta; pi / 2.0 => float phi; if (r > 0) { Math.acos(dz / r) => phi; Math.acos(dx / r) => theta; if (dy < 0) { 2 * pi - theta => theta; } } distance(dx + head_width / 2, dy, dz) => float left; distance(dx - head_width / 2, dy, dz) => float right; Math.max(left, .01) => left; Math.max(right, .01) => right; dissipation(theta, phi, src_gain(), left) => g_left.gain; dissipation(pi - theta, phi, src_gain(), right) => g_right.gain; (left / sound_speed)::second => d_left.delay; (right / sound_speed)::second => d_right.delay; // <<< dx, dy, theta >>>; // <<< g_left.gain(), g_right.gain(), left / sound_speed, right / sound_speed >>>; loc.set(dx, dy, dz); } public void Circle(float o_x, float o_y, float phi, float theta, float radius, int n, float total_time) { radius * Math.cos(phi) => float dz; (2 * pi) / n => float chunk; total_time / n => float time_chunk; for (0 => int i; i < n; i++) { o_x + (radius * Math.cos(theta) * Math.sin(phi)) => float dx; o_y + (radius * Math.sin(theta) * Math.sin(phi)) => float dy; MoveXYZ(dx, dy, dz); theta + chunk => theta; // <<< "circle t", dx, dy, theta / pi, Math.cos(theta) >>>; time_chunk::ms => now; total_time - time_chunk => total_time; } total_time::ms => now; } // linear motion public void MoveTo(float x1, float y1, float z1, int iterations, float time_amount) { Math.fabs(x1 - loc.x) => float dx; Math.fabs(y1 - loc.y) => float dy; Math.fabs(z1 - loc.z) => float dz; distance(dx, dy, dz) => float delta; if (delta <= 0) { MoveXYZ(x1, y1, z1); time_amount::ms => now; return; } (x1 - loc.x) / iterations => dx; (y1 - loc.y) / iterations => dy; (z1 - loc.z) / iterations => dz; time_amount / iterations => float time_chunk; Point p; p.set(loc); for (; iterations > 0; iterations--) { MoveXYZ(p); p.move(dx, dy, dz); time_chunk::ms => now; time_amount - time_chunk => time_amount; } MoveXYZ(x1, y1, z1); time_amount::ms => now; } } class BinAuralBuffer extends BinAural { Gain g; SndBuf snd; 1.5 => float target_gain; public void connect(string s, UGen left, UGen right) { 1.8 => g.gain; s => snd.read; 1.0 => snd.gain; snd => g; g => d_left => g_left => left; g => d_right => g_right => right; end(); } public void play(dur length) { 0 => snd.pos; length => now; } public void play() { 0 => snd.pos; } public void end() { snd.samples() => snd.pos; } public void continuous_play() { while (true) { 0 => float time_chunk; if (Std.rand2f(0.0, 1.0) > .85) { 7.4 => time_chunk; } else { 18.59 => time_chunk; } <<< "tc", time_chunk >>>; spork ~play(time_chunk::second); (time_chunk - .1)::second => now; } } } class BeeBuffer extends BinAuralBuffer { .8 => bin_gain; // 20 meter max distance fun float max_distance() { return bin_gain * 20.0; } } // mandated by the society for the prevention of deafness in computer generated sound Dyno dl => dac.left; Dyno dr => dac.right; dl.limit(); dr.limit(); <<< "--------------" >>>; // load buffers up front BeeBuffer bee; BinAuralBuffer rain, thunder, beast; rain.connect("data/light_rain_forest.wav", dl, dr); thunder.connect("data/thunderclap.wav", dl, dr); beast.connect("data/animal_4f.wav", dl, dr); bee.connect("data/BumbleBee.wav", dl, dr); spork ~bee.play(18.4::second); // bring in bee from front, right bee.MoveXYZ(8.5, 7.0, 2.5); 2::second => now; bee.MoveTo(5.0, 7.0, 2.0, 10, 3000); 3::second => now; bee.MoveTo(1.5, 5.0, 1.5, 10, 3000); 1.8::second => now; bee.MoveTo(1.0, 1.5, 0.5, 10, 2000); // bring in thunder from front, left spork ~thunder.play(); thunder.MoveXYZ(-30, 110, 30); spork ~thunder.MoveTo(-10, 60, 25, 50, 20000); 3::second => now; spork ~bee.play(18.4::second); bee.MoveXYZ(1.0, 1.5, 0.4); 1::second => now; bee.MoveTo(-1.5, 1.5, 0.3, 10, 2000); 2::second => now; bee.MoveTo(-0.4, 0.5, 0.1, 20, 3000); 2::second => now; // rain also front, left, but closer spork ~rain.play(); rain.MoveXYZ(-10, 80, 10); spork ~rain.MoveTo(-10, 50, 10, 20, 32000); // bee circles and then flies away bee.Circle(-0.2, 0, pi/2, pi, 0.3, 64, 4000); bee.MoveTo(-10.0, -8.0, 3.0, 20, 3000); 10::second => now;