// a couple classes to allow for multi-channel (4 in this case) output in chuck // sample code on use of classes included below // multi-channel layout // we have an x,y plane defined with the four corners defined as follows: // left,front: -1, -1 // right,front: 1, -1 // right,back: 1, 1 // left,back -1, 1 4 => int num_channels; -1 => int channel_left; 1 => int channel_right; -1 => int channel_front; 1 => int channel_back; // // this class, later a global variable, streams audio to four wave files // representing our different channels class FourChannelWave { true => int write_to_file; true => int debug_on_dac; WvOut ch[4]; "a_fl.wav" => string ch1_name; "a_fr.wav" => string ch2_name; "a_bl.wav" => string ch3_name; "a_br.wav" => string ch4_name; public void init(int write_to_wav_file) { if (!write_to_wav_file && dac.channels() != num_channels) { <<< "number of channels doesn't match dac:", num_channels, dac.channels() >>>; me.exit(); } write_to_wav_file => write_to_file; // == !write_to_dac if (!write_to_file) return; ch1_name => ch[0].wavFilename; ch2_name => ch[1].wavFilename; ch3_name => ch[2].wavFilename; ch4_name => ch[3].wavFilename; } public void done() { if (!write_to_file) return; ch1_name => ch[0].closeFile; ch2_name => ch[1].closeFile; ch3_name => ch[2].closeFile; ch4_name => ch[3].closeFile; } public void connect(Gain g[]) { for (0 => int i; i < ch.cap(); i++) { if (write_to_file) { g[i] => ch[i]; // debug on two-channel dac while writing wav files if (debug_on_dac) { if (i == 0 || i == 4) { ch[i] => dac.right; } else { ch[i] => dac.left; } } else { ch[i] => blackhole; } } else { g[i] => dac.chan(i); } } } } // // this class sets up an inheritance framework so sub-classes can write // multi-channel output. Importantly, as the constants above define, the // FourChannelPan operates across a plane defined above as: // left, front, right, back (-1, -1, 1, 1) // class FourChannelPan { // define a set of gains for each channel Gain ch_gn[4]; 0 => int fl; 1 => int fr; 2 => int br; 3 => int bl; // hyp (dist) will define the amplitude of the gains. // At 2.0, a sound in the corner yields 1.0 for that channel and zero for all others, // and a sound in the middle would yield a gain of .28 for all; // a hyp of sqrt(8.0) would allow for a continuous spectrum of sound across the // distance of the room, yet larger gains for all channels (and risks of clipping // unless you know what you are doing) 2.0 => float hyp; // initialize each gain to zero for (0 => int i; i < ch_gn.cap(); i++) { ch_gn[i].gain(.0); } // connect our sound source 'in' to our class and sound output 'out' public void c_connect(float gn, UGen in, FourChannelWave out) { for (0 => int i; i < ch_gn.cap(); i++) { ch_gn[i].gain(gn); in => ch_gn[i]; } out.connect(ch_gn); } public void c_connect(UGen in, FourChannelWave out) { // default to 1.0 c_connect(1.0, in, out); } // set each channel to a specific value public void C_Set(float left_front, float right_front, float left_back, float right_back) { ch_gn[fl].gain(left_front); ch_gn[fr].gain(right_front); ch_gn[bl].gain(left_back); ch_gn[br].gain(right_back); } // set left/right (stereo); note that this would over-ride front/back (if you want both use PanXY) public void C_SetLR(float left, float right) { ch_gn[fl].gain(left / 2); ch_gn[fr].gain(right / 2); ch_gn[bl].gain(left / 2); ch_gn[br].gain(right / 2); } // set front/back (fade); note that this would over-ride left/right (if you want both use PanXY) public void C_SetFB(float front, float back) { ch_gn[fl].gain(front / 2); ch_gn[fr].gain(front / 2); ch_gn[bl].gain(back / 2); ch_gn[br].gain(back / 2); } private float distance(float dx, float dy) { return Math.sqrt((dx * dx) + (dy * dy)); } // set the sound source to a specific location in the room (plane) as defined above // the function will not allow a channel.gain to exceed 1.0 nor be less than 0.0 public void C_PanXY(float dx /* -1 = left, 1 = right */ , float dy /* -1 = front, 1 = back */) { if (dx > 1.0 || dx < -1.0 || dy > 1.0 || dy < -1.0) { <<< "panxy error: ", dx, dy >>>; return; } (hyp - distance(channel_left - dx, channel_front - dy)) / hyp => float flg; (hyp - distance(channel_right - dx, channel_front - dy)) / hyp => float frg; (hyp - distance(channel_left - dx, channel_back - dy)) / hyp => float blg; (hyp - distance(channel_right - dx, channel_back - dy)) / hyp => float brg; Math.min(1.0, Math.max(0, flg)) => ch_gn[fl].gain; Math.min(1.0, Math.max(0, frg)) => ch_gn[fr].gain; Math.min(1.0, Math.max(0, blg)) => ch_gn[bl].gain; Math.min(1.0, Math.max(0, brg)) => ch_gn[br].gain; // for debugging only // <<< "Pan_XY", ch_gn[fl].gain(), ch_gn[fr].gain(), ch_gn[bl].gain(), ch_gn[br].gain() >>>; } } // set up an example of multi-channel output class APitch extends FourChannelPan { SinOsc s; public void Set(float freq, FourChannelWave out) { freq => s.freq; // using multiple sound sources, so keep this low .2 => s.gain; // connect our sound source to the channel output class c_connect(s, out); } } FourChannelWave fcw; APitch p1, p2, p3; // init our fourchannelwave class; true -> write_to_file; false -> write_to_dac fcw.init(true); // set up three sound sources and attach to our fcw Std.mtof(41) => float freq; p1.Set(freq, fcw); p2.Set(freq * 2, fcw); p3.Set(freq * 3, fcw); // put all sounds in middle of room p1.C_PanXY(0, 0); p2.C_PanXY(0, 0); p3.C_PanXY(0, 0); 1::second => now; // put 1 on ch1, 2 on ch2, 4 on ch4, none on ch3 p1.C_Set(1, 0, 0, 0); p2.C_Set(0, 1, 0, 0); p3.C_Set(0, 0, 0, 1); 1::second => now; // put 1 & 3 on left, 2 or right p1.C_SetLR(1, 0); p2.C_SetLR(0, 1); p3.C_SetLR(1, 0); 1::second => now; // put 1 in front left, 2 in middle, 3 in back right p1.C_PanXY(channel_left, channel_front); p2.C_PanXY(0, 0); p3.C_PanXY(channel_right, channel_back); 1::second => now; // move p2 from front left to right back 10 => int iterations; for (0 => int i; i < iterations; i++) { channel_left => float x; channel_front => float y; p2.C_PanXY(x, y); 1::second/iterations => now; x + ((channel_right - channel_left) / iterations) => x; y + ((channel_back - channel_front) / iterations) => y; } // have each sound source follow the others (in phase) in a circle of radius // half room distance (1.0) around the middle of the room (origin (0, 0)) // we'll go 1000 ms, and we'll iterate 1000/50 times. 1000 => float time_amount; (time_amount / 50) $ int => iterations; time_amount / iterations => float time_chunk; // set up circle origin and radius 0.0 => float origin_x; 0.0 => float origin_y; 1.0 => float radius; // we'll have each sound source follow by phase pi/2 0.0 => float phase_1; pi / 2.0 => float phase_2; pi => float phase_3; for (0 => int i; i < iterations; i++) { float x, y; ((i * 1.0) / iterations) * 2 * pi => float t; origin_x + radius * Math.cos(t + phase_1) => x; origin_y + radius * Math.sin(t + phase_1) => y; p1.C_PanXY(x, y); origin_x + radius * Math.cos(t + phase_2) => x; origin_y + radius * Math.sin(t + phase_2) => y; p2.C_PanXY(x, y); origin_x + radius * Math.cos(t + phase_3) => x; origin_y + radius * Math.sin(t + phase_3) => y; p3.C_PanXY(x, y); time_chunk::ms => now; time_amount - time_chunk => time_amount; } fcw.done();