// chesstones.ck, June, 2008, Dale Parson (Acoustic Interloper), // acknowledgements to Les Hall (Inventor) and Kassen for getting // me into my first ChucK program. Thanks! // Launch as a sound generator for chessgame.py as of // 18 June 2008. This OSC type format is patch specific. // There are 2 banks of ugens with 32 ugens each. // Bank 0 generates sines and bank 1 triangles in the // initial test. Incoming OSC fields are: // bank(i) 0 or 1 for sine or triangle bank respectively // osc(i) oscillator numer 0..31 in that bank // freq(f) frequency or -1.0 to shut it off // phase(f) 0.0 .. 1.0 or -1.0 to shut it off (redundant) // leftampl left channel amplitude 0.0 to 1.0 // rightampl right channel amplitude 0.0 to 1.0 // The amplitudes are 0.0 when freq is -1.0 for shut off. // The output of all ACTIVE oscillators should be summed and // normalized so that running many oscillators does not exceed // some threshold. This entails some scaling. // The structure of these classes mirrors the structure of the Max/MSP patch. class OscBlock { // a single ramped oscillator Osc @ gen ; // SinOsc or TriOsc, set after construction Envelope ramp ; Gain lgain ; // left channel gain Gain rgain ; // right channel gain UGen @ leftdest ; UGen @ rightdest ; Event cleanup ; int isdead ; // set to 1 when oscillator is off 1 => isdead ; // lampl and rampl record levels for output scaling 0.0 => float lampl ; 0.0 => float rampl ; 50::ms => dur gaindelay ; // duration of a change in amplitude ramp => lgain ; ramp => rgain ; // Invoke init() one time after constructing this object, passing its // generator and left and right outputs. fun void init(Osc newgen, UGen leftout, UGen rightout) { newgen @=> gen ; 1.0 => gen.gain ; gen => ramp ; leftout @=> leftdest ; rightout @=> rightdest ; spork ~ dropcnxn(); // Each oscillator gets a garbage collector. } // Set up the oscillator path but do not advance time. fun void play(float freq, float phase, float left, float right) { left => lampl ; right => rampl ; isdead => int wasdead ; if (left == 0.0 && right == 0.0) { 0 => ramp.target ; 1 => isdead ; if (! wasdead) { cleanup.signal(); // Schedule disconenction after ramp down. } } else if (freq > -1.0) { freq => gen.freq ; phase => gen.phase ; left => lgain.gain ; right => rgain.gain ; 1 => ramp.target ; 0 => isdead ; if (wasdead) { lgain => leftdest ; rgain => rightdest ; } } gaindelay => ramp.duration ; } // Disconnect a dead oscillator after ramp time expires. // Each OscBlock object has a shred whose sole job it is to do this. fun void dropcnxn() { while (1) { // Wait until awakened and then until ramp down is done. cleanup => now ; // AWAKENED if (isdead != 0) { gaindelay => now ; // RAMP DOWN COMPLETED lgain =< leftdest ; rgain =< rightdest ; } } } } 32 => int OscillatorsPerBank ; 2 => int Banks ; OscillatorsPerBank * Banks => int NumberOscillators ; OscBlock blocks[NumberOscillators] ; Gain leftscale => dac.left ; Gain rightscale => dac.right ; 1.0 => leftscale.gain ; 1.0 => rightscale.gain ; 0.0 => float leftsum ; 0.0 => float rightsum ; 0 => int osccount ; // Create and wire the OscBlocks to the gain scalers. for (0 => int i ; i < NumberOscillators ; i++) { Osc @ nextgen ; if (i < OscillatorsPerBank) { new SinOsc @=> nextgen ; } else if (i < (2 * OscillatorsPerBank)) { new TriOsc @=> nextgen ; } // Other cases would go here blocks[i].init(nextgen, leftscale, rightscale) ; } // Create our OSC (Open Sound Control) receiver. OscRecv recv; // port 7401 used by the chess program 57120 => recv.port; // start listening (launch thread) recv.listen(); // create an address in the receiver, store in new variable recv.event( "list, i i f f f f" ) @=> OscEvent @ oe; second => now ; // infinite event loop while( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. while( oe.nextMsg() ) { int bank, osc ; float freq, phase, leftampl, rightampl ; // getFloat fetches the expected float (as indicated by "i f") oe.getInt() => bank ; oe.getInt() => osc ; oe.getFloat() => freq ; oe.getFloat() => phase ; oe.getFloat() => leftampl ; oe.getFloat() => rightampl ; // <<< "OSC:", bank, osc, freq, phase, leftampl, rightampl >>>; (OscillatorsPerBank * bank) + osc => int oscix ; if (oscix >= NumberOscillators) { continue ; } if (! blocks[oscix].isdead) { blocks[oscix].lampl -=> leftsum ; blocks[oscix].rampl -=> rightsum ; osccount-- ; } blocks[oscix].play(freq, phase, leftampl, rightampl); if (! blocks[oscix].isdead) { blocks[oscix].lampl +=> leftsum ; blocks[oscix].rampl +=> rightsum ; osccount++ ; } // <<< "SUMS", leftsum, rightsum >>>; if (leftsum > 1.0 || rightsum > 1.0) { 1.0 / leftsum => leftscale.gain ; 1.0 / rightsum => rightscale.gain ; } else { 1.0 => leftscale.gain ; 1.0 => rightscale.gain ; } } }