//-------------------------------------------------------------------- // name: twilight-redux-kb.ck // desc: twilight instrument redux, keyboard edition // // author: Ge Wang (ge@ccrma.stanford.edu) // date: initial instrument: Spring 2013 // redux: Summer 2014 // kb: Winter 2015 // https://vimeo.com/100624271 // // usage: chuck twilight-redux-kb:SOUND.AIFF (or .WAV) // // keyboard control (also see kb() below): // UP/DOWN -- grain position // LEFT/DOWN -- grain rate / tuning // ','/'.' -- grain size // '-'/'=' -- grain position random amount //-------------------------------------------------------------------- // default filename (can be overwritten via input argument) "twilight-source.aiff" => string FILENAME; // get file name, if one specified as input x0argument if( me.args() > 0 ) me.arg(0) => FILENAME; // overall volume 1 => float MAIN_VOLUME; // grain duration base 50::ms => dur GRAIN_LENGTH; // factor relating grain duration to ramp up/down time .5 => float GRAIN_RAMP_FACTOR; // playback rate 1 => float GRAIN_PLAY_RATE; // grain position (0 start; 1 end) 0 => float GRAIN_POSITION; // grain position goal (for interp) 0 => float GRAIN_POSITION_GOAL; // grain position randomization .001 => float GRAIN_POSITION_RANDOM; // grain jitter (0 == periodic fire rate) 1 => float GRAIN_FIRE_RANDOM; // max lisa voices 30 => int LISA_MAX_VOICES; // load file into a LiSa (use one LiSa per sound) load( FILENAME ) @=> LiSa @ lisa; // patch it PoleZero blocker => NRev reverb => dac; // connect lisa.chan(0) => blocker; // reverb mix .05 => reverb.mix; // pole location to block DC and ultra low frequencies .99 => blocker.blockZero; // HID objects Hid hi; HidMsg msg; // which joystick 0 => int device; // get from command line if( me.args() ) me.arg(0) => Std.atoi => device; // open joystick 0, exit on fail if( !hi.openKeyboard( device ) ) me.exit(); // log <<< "keyboard '" + hi.name() + "' ready", "" >>>; // keys 45 => int KEY_DASH; 46 => int KEY_EQUAL; 54 => int KEY_COMMA; 55 => int KEY_PERIOD; 79 => int KEY_RIGHT; 80 => int KEY_LEFT; 81 => int KEY_DOWN; 82 => int KEY_UP; // spork it spork ~ print(); spork ~ kb(); spork ~ interp( .025, 5::ms ); // main loop while( true ) { // fire a grain fireGrain(); // amount here naturally controls amount of overlap between grains GRAIN_LENGTH / 8 + Math.random2f(0,GRAIN_FIRE_RANDOM)::ms => now; } // fire! fun void fireGrain() { // grain length GRAIN_LENGTH => dur grainLen; // ramp time GRAIN_LENGTH * GRAIN_RAMP_FACTOR => dur rampTime; // play pos GRAIN_POSITION + Math.random2f(0,GRAIN_POSITION_RANDOM) => float pos; // a grain if( lisa != null && pos >= 0 ) spork ~ grain( lisa, pos * lisa.duration(), grainLen, rampTime, rampTime, GRAIN_PLAY_RATE ); } // interp fun void interp( float slew, dur RATE ) { while( true ) { // interp grain position (GRAIN_POSITION_GOAL - GRAIN_POSITION)*slew + GRAIN_POSITION => GRAIN_POSITION; // interp rate RATE => now; } } // grain sporkee fun void grain( LiSa @ lisa, dur pos, dur grainLen, dur rampUp, dur rampDown, float rate ) { // get a voice to use lisa.getVoice() => int voice; // if available if( voice > -1 ) { // set rate lisa.rate( voice, rate ); // set playhead lisa.playPos( voice, pos ); // ramp up lisa.rampUp( voice, rampUp ); // wait (grainLen - rampUp) => now; // ramp down lisa.rampDown( voice, rampDown ); // wait rampDown => now; } } // print fun void print() { // time loop while( true ) { // values <<< "pos:", GRAIN_POSITION, "random:", GRAIN_POSITION_RANDOM, "rate:", GRAIN_PLAY_RATE, "size:", GRAIN_LENGTH/second >>>; // advance time 100::ms => now; } } // keyboard fun void kb() { // infinite event loop while( true ) { // wait on HidIn as event hi => now; // messages received while( hi.recv( msg ) ) { // button donw if( msg.isButtonDown() ) { if( msg.which == KEY_LEFT ) { .005 -=> GRAIN_PLAY_RATE; if( GRAIN_PLAY_RATE < 0 ) 0 => GRAIN_PLAY_RATE; } else if( msg.which == KEY_RIGHT ) { .005 +=> GRAIN_PLAY_RATE; if( GRAIN_PLAY_RATE > 2 ) 2 => GRAIN_PLAY_RATE; } else if( msg.which == KEY_DOWN ) { .01 -=> GRAIN_POSITION_GOAL; if( GRAIN_POSITION_GOAL < 0 ) 0 => GRAIN_POSITION_GOAL; } else if( msg.which == KEY_UP ) { .01 +=> GRAIN_POSITION_GOAL; if( GRAIN_POSITION_GOAL > 1 ) 1 => GRAIN_POSITION_GOAL; } else if( msg.which == KEY_COMMA ) { .95 *=> GRAIN_LENGTH; if( GRAIN_LENGTH < 1::ms ) 1::ms => GRAIN_LENGTH; } else if( msg.which == KEY_PERIOD ) { 1.05 *=> GRAIN_LENGTH; if( GRAIN_LENGTH > 1::second ) 1::second => GRAIN_LENGTH; } else if( msg.which == KEY_DASH ) { .9 *=> GRAIN_POSITION_RANDOM; if( GRAIN_POSITION_RANDOM < .000001 ) .000001 => GRAIN_POSITION_RANDOM; } else if( msg.which == KEY_EQUAL ) { 1.1 *=> GRAIN_POSITION_RANDOM; if( GRAIN_POSITION_RANDOM > 1 ) 1 => GRAIN_POSITION_RANDOM; } } } } } // load file into a LiSa fun LiSa load( string filename ) { // sound buffer SndBuf buffy; // load it filename => buffy.read; // new LiSa LiSa lisa; // set duration buffy.samples()::samp => lisa.duration; // transfer values from SndBuf to LiSa for( 0 => int i; i < buffy.samples(); i++ ) { // args are sample value and sample index // (dur must be integral in samples) lisa.valueAt( buffy.valueAt(i), i::samp ); } // set LiSa parameters lisa.play( false ); lisa.loop( false ); lisa.maxVoices( LISA_MAX_VOICES ); return lisa; }