// name: gran.ck // by Michael Svolos // modified from my hw3.ck // which was modified from starter code "twiliight-redux-kb.ck" by Ge Wang // contains chubgraph for granular synthesis of one note // for MUSIC 220b final project "glacial sky" public class Gran extends Chubgraph { // default filename "piccolo/84_C6_piccolo.wav" => string FILENAME; // overall volume 1 => float MAIN_VOLUME; // grain duration base 200::ms => dur GRAIN_LENGTH; // factor relating grain duration to ramp up/down time .1 => float GRAIN_RAMP_FACTOR; // playback rate 1 => float GRAIN_PLAY_RATE; // grain position (0 start; 1 end) 0 => float GRAIN_POSITION; // grain position randomization .001 => float GRAIN_POSITION_RANDOM; // grain jitter (0 == periodic fire rate) 0.5 => float GRAIN_FIRE_RANDOM; // grain pan randomness determiner 0 => float GRAIN_PAN_LEVEL; // array holding parameters controlled by mouse movement [0.1, 0.001, 0.5, 0.4, 0.0] @=> float paramScales[]; //pos, random, rate, size, pan // parameters: // GRAIN_LENGTH - 50ms - 1::second (longer?) // GRAIN_PLAY_RATE - 0 to 2 - playback rate // GRAIN_POSITION - 0 to 1 - start to end // GRAIN_POSITION_RANDOM - 0 - 1 // GRAIN_PAN_LEVEL - 0 to 1 // hardcoded times that can be jumped to by num row [0., 0.0981, 0.17, 0.345, 0.375, 0.39, 0.678] @=> float posHards[]; // index of paramScales that is controlled by mouse at any time 0 => int mouseMode; 0 => int doPlay; //0 => int stopFlag; // max lisa voices 30 => int LISA_MAX_VOICES; // load file into a LiSa (use one LiSa per sound) LiSa @ lisa; // patch it PoleZero blocker => Gain g => Envelope e => outlet;//ADSR adsr => outlet; // pole location to block DC and ultra low frequencies .99 => blocker.blockZero; // HID objects (unused) Hid hiKey, hiMouse; HidMsg msgKey, msgMouse; // which joystick 0 => int device; // get from command line if( me.args() ) me.arg(0) => Std.atoi => device; // sets gain for a Gran fun void setGain(float gainn) { gainn => g.gain; } // initializes granulator for a file fun void setFile(string filename) { filename => FILENAME; load( FILENAME ) @=> lisa; lisa => blocker; //spork ~play(); } // plays sound file // params: duration to play (dur), // whether to let note play at end from sound file or cutoff with envelope (int), // ramp up time (dur), ramp down time (dur) // if doLongCutoff is 0, make sure rampUp and rampDown aren't too low // if doLongCutoff is 1, then only rampUp should matter fun void play(dur duration, int doLongCutoff, dur rampUp, dur rampDown) { set(); // main loop now + duration => time stop; now + duration - rampDown => time startRamp; e.duration(rampUp); e.value(0); e.keyOn(); if (!doLongCutoff) spork ~rampOffAtTime(startRamp, rampDown); //this makes sure ramp down happens at proper time while( now < stop ) { // fire a grain fireGrain(); // amount here naturally controls amount of overlap between grains GRAIN_LENGTH / 8 => now; } lisa.rampDown(GRAIN_LENGTH * GRAIN_RAMP_FACTOR); if (!doLongCutoff) lisa.duration() => now; } //ramps off at time startRamp with ramp down duration rampDown fun void rampOffAtTime(time startRamp, dur rampDown) { e.duration(rampDown); startRamp - now => now; e.keyOff(); rampDown => 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 ); } // 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, "pan:", GRAIN_PAN_LEVEL >>>; // advance time 100::ms => now; } } //updates global variables if paramScales is modified fun void set() { for (0 => int i; i < 5; i + 1 => i) { if (paramScales[i] > 1) 1 => paramScales[i]; if (paramScales[i] < 0) 0 => paramScales[i]; } paramScales[0] => GRAIN_POSITION; paramScales[1] => GRAIN_POSITION_RANDOM; paramScales[2] * 2 => GRAIN_PLAY_RATE; paramScales[3]::second + 50::ms => GRAIN_LENGTH; paramScales[4] => GRAIN_PAN_LEVEL; } // load file into a LiSa fun LiSa load( string filename ) { // sound buffer SndBuf buffy; // load it filename => buffy.read; if (!buffy.samples()) { me.exit(); } else <<< "opened file " + filename >>>; // 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( 0 ); lisa.loop( 0 ); for (0 => int i; i < LISA_MAX_VOICES; 1 +=> i) { lisa.play(i, 0); lisa.loop(i, 0); } lisa.maxVoices( LISA_MAX_VOICES ); return lisa; } }