//----------------------------------------------------------------------------- // name: sndpeek.ck // desc: sndpeek in ChuGL! // // author: Ge Wang (https://ccrma.stanford.edu/~ge/) // Andrew Zhu Aday (https://ccrma.stanford.edu/~azaday/) // date: Fall 2023 //----------------------------------------------------------------------------- fun int[] pickRandomPositions(float array[], int N) { // Create an array to store the random positions int randomPositions[N] @=> int result[]; // Generate and store random positions for (0 => int i; i < N; i++) { Math.random2(0, array.cap()-1) @=> int randomIndex; result << randomIndex; } // Return the array of random positions return result; } fun int isElementInArray(int array[], int element) { // Iterate through the array and check if the element is present for (auto item : array) { if (item == element) { // Return true if the element is found return 0; } } // Return false if the element is not found return 1; } // ----------------- // Stars & Planets // ----------------- 5 => int BOUNDS_X; 5 => int BOUNDS_Y; 5 => int BOUNDS_Z; class LightBulb extends GGen { // GGen network. a light + sphere at the same position FlatMaterial mat; GPointLight light --> GSphere bulb --> this; // set up sphere to be a flat color bulb.mat(mat); mat.color(@(1, 1, 1)); @(0.1, 0.1, 0.1) => bulb.sca; // set light falloff light.falloff(0.14, 0.7); // falloff chart: https://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation vec3 lightCol; Math.random2f(0.5, 1.5) => float pulseRate; // randomize pulse rate for fading in/out fun void color(float r, float g, float b) { @(r, g, b) => lightCol; // save the set color mat.color(@(r, g, b)); // set material color light.diffuse(@(r, g, b)); // set light diffuse color } // this is called automatically every frame but ChuGL // IF the GGen or one of its parents is connected to GG.scene() fun void update(float dt) { // fluctuate intensity 0.5 + 0.5 * Math.sin((now/second) * pulseRate) => light.intensity; // range [0, 1] // // fluctuate material color light.intensity() * lightCol => mat.color; 0.1 => float move_max; Math.random2f(-1 * move_max, move_max) => float move_x; Math.random2f(-1 * move_max, move_max) => float move_y; // Math.random2f(-1 * move_max, move_max) => float move_z; -1 => float move_z; if (this.posZ() < -2) { Math.random2f(-1 * BOUNDS_X, BOUNDS_X) => this.posX; -3 + Math.random2f(-1 * BOUNDS_Y, BOUNDS_Y) => this.posY; Math.random2f(-1 * BOUNDS_Z, BOUNDS_Z) => this.posZ; // Math.random2f(0, 1) => float r; // Math.random2f(0, 1) => float g; // Math.random2f(0, 1) => float b; // this.color(r, g, b); } this.translate(@(move_x, move_y, move_z)); } } class WaveformFall extends GGen { // GGen network. a light + sphere at the same position FlatMaterial mat; GPointLight light --> GSphere bulb --> this; // set up sphere to be a flat color bulb.mat(mat); mat.color(@(1, 1, 1)); @(0.1, 0.1, 0.1) => bulb.sca; // set light falloff light.falloff(0.14, 0.7); // falloff chart: https://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation vec3 lightCol; Math.random2f(0.5, 1.5) => float pulseRate; // randomize pulse rate for fading in/out float total_time; fun void color(float r, float g, float b) { @(r, g, b) => lightCol; // save the set color mat.color(@(r, g, b)); // set material color light.diffuse(@(r, g, b)); // set light diffuse color } // this is called automatically every frame but ChuGL // IF the GGen or one of its parents is connected to GG.scene() fun void update(float dt) { // fluctuate intensity // 0.5 + 0.5 * Math.sin((now/second) * pulseRate) => light.intensity; // range [0, 1] // // fluctuate material color // light.intensity() * lightCol => mat.color; 0.1 => float move_max; Math.random2f(-1 * move_max, move_max) => float move_x; Math.random2f(-1 * move_max, move_max) => float move_y; Math.random2f(-1 * move_max, move_max) => float move_z; this.translate(@(move_x, move_y, move_z)); // if (total_time >= 3) { // this --< GG.scene(); // } // total_time + dt => total_time; } } 25 => int NUM_STARS; LightBulb stars[NUM_STARS]; for( LightBulb star : stars ) { star --> GG.scene(); Math.random2f(-1 * BOUNDS_X, BOUNDS_X) => star.posX; -3 + Math.random2f(-1 * BOUNDS_Y, BOUNDS_Y) => star.posY; Math.random2f(-1 * BOUNDS_Z, BOUNDS_Z) => star.posZ; star.color(1, 1, 1); } 15 => int NUM_PLANETS; LightBulb planets[NUM_PLANETS]; for( LightBulb planet : planets ) { planet --> GG.scene(); Math.random2f(-1 * BOUNDS_X, BOUNDS_X) => planet.posX; 4 + Math.random2f(-1 * BOUNDS_Y, BOUNDS_Y) => planet.posY; Math.random2f(-1 * BOUNDS_Z, BOUNDS_Z) => planet.posZ; Math.random2f(0, 1) => float r; Math.random2f(0, 1) => float g; Math.random2f(0, 1) => float b; planet.color(r, g, b); } // window size // 1024 => int WINDOW_SIZE; 32 => int WINDOW_SIZE; // y position of waveform 8 => float WAVEFORM_Y; // y position of spectrum -3 => float SPECTRUM_Y; // width of waveform and spectrum display 10 => float DISPLAY_WIDTH; // waterfall depth 128 => int WATERFALL_DEPTH; // uncomment to fullscreen GG.fullscreen(); // put camera on a dolly GG.camera() --> GGen dolly --> GG.scene(); // position GG.camera().posZ(5); GG.camera().posY(SPECTRUM_Y); // set clipping plane GG.camera().clip( .05, WATERFALL_DEPTH * 10 ); // set bg color GG.scene().backgroundColor( @(0.3,0.2,0.4) ); GG.scene() @=> GScene @ scene; GCube main_component --> scene; main_component.posZ(-9); main_component.sca( @(0.5, 0.5, 0.5)); @(.2, .2, .2) => main_component.mat().color; GCube left_part --> main_component; left_part.posX(-0.5); // left_part.posZ(-9); left_part.sca( @(0.25, 0.25, 0.25)); @(.4, .4, .4) => left_part.mat().color; GCube right_part --> main_component; right_part.posX(0.5); right_part.sca( @(0.25, 0.25, 0.25)); @(.4, .4, .4) => right_part.mat().color; GCube botom_part --> main_component; // botom_part.posZ(-9); botom_part.posY(-0.75); botom_part.sca( @(1, 0.5, 1)); @(.95, .95, .05) => botom_part.mat().color; GCube left_leg --> main_component; // left_leg.posZ(-9); left_leg.posX(-0.5); left_leg.posY(-1.2); left_leg.sca( @(0.25, 1, 0.25)); @(.95, .95, .05) => left_leg.mat().color; GCube right_leg --> main_component; // right_leg.posZ(-9); right_leg.posX(0.5); right_leg.posY(-1.2); right_leg.sca( @(0.25, 1, 0.25)); @(.95, .95, .05) => right_leg.mat().color; main_component.translate(@(0.1, SPECTRUM_Y - 0.1, 0)); main_component.rotX(-160); // waveform renderer GLines waveform --> GG.scene(); // GLines waveform --> GG.scene(); waveform.mat().lineWidth(1.0); // translate up waveform.posY( WAVEFORM_Y); // color0 // waveform.mat().color( @(0.91, .4, 0.4)*1.5 ); // make a waterfall Waterfall waterfall --> GG.scene(); // translate down waterfall.posY( SPECTRUM_Y + 3 ); // which input? adc => Gain input; // SinOsc sine => Gain input => dac; .15 => sine.gain; // accumulate samples from mic input => Flip accum => blackhole; // take the FFT input => PoleZero dcbloke => FFT fft => blackhole; // set DC blocker .95 => dcbloke.blockZero; // set size of flip WINDOW_SIZE => accum.size; // set window type and size Windowing.hann(WINDOW_SIZE) => fft.window; // set FFT size (will automatically zero pad) WINDOW_SIZE*2 => fft.size; // get a reference for our window for visual tapering of the waveform Windowing.hann(WINDOW_SIZE) @=> float window[]; // sample array float samples[0]; // FFT response complex response[0]; // a vector to hold positions vec3 positions[WINDOW_SIZE]; // custom GGen to render waterfall class Waterfall extends GGen { // waterfall playhead 0 => int playhead; // lines GLines wfl[WATERFALL_DEPTH]; 1 => int rotate_dir; 0 => float rotate_time; // color // Math.random2f(0, 1) => float rand; // @(rand, rand, rand) => vec3 color; // iterate over line GGens for( GLines w : wfl ) { // aww yea, connect as a child of this GGen w --> this; // color // w.mat().color( @(1, .0, .0) ); } // copy fun void latest( vec3 positions[] ) { // set into positions => wfl[playhead].geo().positions; // advance playhead playhead++; // wrap it WATERFALL_DEPTH %=> playhead; } // update fun void update( float dt ) { dt + rotate_time => rotate_time; if (rotate_time > Math.random2f(0.3, 1.2)) { -1 * rotate_dir => rotate_dir; 0 => rotate_time; } this.rotateOnLocalAxis(@(0, 0, 1), 0.01 * rotate_dir); Math.random2f(0.4, 0.5) => float rand; @(rand, rand, rand) => vec3 color; // position playhead => int pos; // for color WATERFALL_DEPTH/2.5 => float thresh; // depth WATERFALL_DEPTH - thresh => float fadeChunk; // so good for( int i; i < wfl.size(); i++ ) { // start with playhead-1 and go backwards pos--; if( pos < 0 ) WATERFALL_DEPTH-1 => pos; // offset Z wfl[pos].posZ( -i ); if( i > thresh ) { wfl[pos].mat().color( ((fadeChunk-(i-thresh))/fadeChunk) * color ); } else { wfl[pos].mat().color( color ); } } } } WaveformFall wf1[WINDOW_SIZE]; WaveformFall wf2[WINDOW_SIZE]; WaveformFall wf3[WINDOW_SIZE]; Math.random2f(0.8, 1) => float rand; for (auto wf_i : wf1) { wf_i.color(0.1, rand, 0.1); wf_i --> GG.scene(); } for (auto wf_i : wf2) { wf_i.color(rand, 0.1, 0.1); wf_i --> GG.scene(); } for (auto wf_i : wf3) { wf_i.color(0.1, 0.1, rand); wf_i --> GG.scene(); } 0 => float center_x; SPECTRUM_Y => float center_y; 2 => float radius1; 1 => float radius2; 2.5 => float radius3; WINDOW_SIZE => int num_points; // map audio buffer to 3D positions fun void map2waveform( float in[], vec3 out[] ) { if( in.size() != out.size() ) { <<< "size mismatch in map2waveform()", "" >>>; return; } // mapping to xyz coordinate int i; DISPLAY_WIDTH => float width; // 5 @=> int num_pos; // int draw_positions[num_pos]; // pickRandomPositions(in, num_pos) @=> draw_positions; for( auto s : in ) { // space evenly in X -width/2 + width/WINDOW_SIZE*i => out[i].x; // map y, using window function to taper the ends s*6 * window[i] - 4 => out[i].y; // a constant Z of 0 0 => out[i].z; // increment // if (isElementInArray(draw_positions, i)){ // WaveformFall wf; -10 => out[i].z; (out[i].x + Math.random2f(-0.01, 0.01) + 4) => wf1[i].posX; (out[i].y) => wf1[i].posY; (out[i].z + Math.random2f(-0.01, 0.01)) - 5 => wf1[i].posZ; 2 * Math.pi * i / num_points => float angle; center_x + radius1 * Math.cos(angle) => wf1[i].posX; center_y + radius1 * Math.sin(angle) + wf1[i].posY() * Math.sin(angle) => wf1[i].posY; -10 => out[i].z; (out[i].x + Math.random2f(-0.01, 0.01)) => wf2[i].posX; (out[i].y) => wf2[i].posY; (out[i].z + Math.random2f(-0.01, 0.01)) - 5 => wf2[i].posZ; center_x + radius2 * Math.cos(angle) => wf2[i].posX; center_y + radius2 * Math.sin(angle) + wf2[i].posY() * Math.sin(angle) => wf2[i].posY; -10 => out[i].z; (out[i].x + Math.random2f(-0.01, 0.01)) => wf3[i].posX; (out[i].y + Math.random2f(-0.01, 0.01)) => wf3[i].posY; (out[i].z + Math.random2f(-0.01, 0.01)) - 5 => wf3[i].posZ; center_x + radius3 * Math.cos(angle) => wf3[i].posX; center_y + radius3 * Math.sin(angle) + wf3[i].posY() * Math.sin(angle) => wf3[i].posY; //} i++; } } // map FFT output to 3D positions fun void map2spectrum( complex in[], vec3 out[] ) { if( in.size() != out.size() ) { <<< "size mismatch in map2spectrum()", "" >>>; return; } // center, radius, num_points 0 => center_x; SPECTRUM_Y => center_y; 4 => float radius; WINDOW_SIZE => num_points; // mapping to xyz coordinate int i; DISPLAY_WIDTH => float width; for( auto s : in ) { // space evenly in X -width/2 + width/WINDOW_SIZE*i => out[i].x; // map frequency bin magnitide in Y 5 * Math.sqrt( (s$polar).mag * 25 ) * 2 => out[i].y; // constant 0 for Z 0 => out[i].z; // convert to circle 2 * Math.pi * i / num_points => float angle; center_x + radius * Math.cos(angle) => out[i].x; center_y + radius * Math.sin(angle) + out[i].y * Math.sin(angle) => out[i].y; // increment i++; } waterfall.latest( out ); } // do audio stuff fun void doAudio() { while( true ) { // upchuck to process accum accum.upchuck(); // get the last window size samples (waveform) accum.output( samples ); // upchuck to take FFT, get magnitude reposne fft.upchuck(); // get spectrum (as complex values) fft.spectrum( response ); // jump by samples WINDOW_SIZE::samp/2 => now; } } spork ~ doAudio(); // fps printer fun void printFPS( dur howOften ) { while( true ) { <<< "fps:", GG.fps() >>>; howOften => now; } } spork ~ printFPS(.25::second); // SinOsc sine; // fun void controlSine( Osc s ) // { // while( true ) // { // 100 + (Math.sin(now/second*5)+1)/2*20000 => s.freq; // 10::ms => now; // } // } // spork ~ controlSine( sine ); // if you want to look up by number ( 0 - 9, *, # ) fun int key2col( int key ) { if( !key ) return 1; return (key - 1) % 3; } fun int key2row( int key ) { if( !key ) return 3; return (key - 1) / 3; } // patch Mandolin m => JCRev r2 => dac; .75 => r2.gain; .025 => r2.mix; fun void generateRandomSoundPattern() { 0 => float time_elapsed; // patch Sitar sit => PRCRev r1 => dac; .05 => r1.mix; // time loop while( true ) { if (time_elapsed > 15) { break; } // freq Math.random2(1, 1 ) => float winner; Std.mtof( 57 + Math.random2(0,3) * 12 + winner ) => sit.freq; // pluck! Math.random2f( 0.4, 0.9 ) => sit.noteOn; // advance time // note: Math.randomf() returns value between 0 and 1 if( Math.randomf() > .5 ) { .5::second => now; time_elapsed + 0.5 => time_elapsed; } else { 0.25::second => now; time_elapsed + 0.25 => time_elapsed; } } // time loop while( true ) { if (time_elapsed > 40) { break; } // freq Math.random2(1, 1 ) => float winner; Std.mtof( 57 + Math.random2(0,3) * 12 + winner ) => sit.freq; // pluck! Math.random2f( 0.4, 0.9 ) => sit.noteOn; // advance time // note: Math.randomf() returns value between 0 and 1 if( Math.randomf() > .5 ) { .5::second => now; time_elapsed + 0.5 => time_elapsed; } else { 0.25::second => now; time_elapsed + 0.25 => time_elapsed; } } // time loop while( true ) { if (time_elapsed > 50) { break; } // freq Math.random2(4, 4 ) => float winner; Std.mtof( 57 + Math.random2(0,3) * 12 + winner ) => sit.freq; // pluck! Math.random2f( 0.4, 0.9 ) => sit.noteOn; // advance time // note: Math.randomf() returns value between 0 and 1 if( Math.randomf() > .5 ) { .5::second => now; time_elapsed + 0.5 => time_elapsed; } else { 0.25::second => now; time_elapsed + 0.25 => time_elapsed; } } // time loop while( true ) { if (time_elapsed > 80) { break; } // freq Math.random2(6, 7 ) => float winner; Std.mtof( 57 + Math.random2(0,3) * 12 + winner ) => sit.freq; // pluck! Math.random2f( 0.4, 0.9 ) => sit.noteOn; // advance time // note: Math.randomf() returns value between 0 and 1 if( Math.randomf() > .5 ) { .5::second => now; time_elapsed + 0.5 => time_elapsed; } else { 0.25::second => now; time_elapsed + 0.25 => time_elapsed; } } SinOsc row => dac; SinOsc col => dac; // frequencies [1209.0, 1336.0, 1477.0] @=> float cols[]; [697.0, 770.0, 852.0, 941.0] @=> float rows[]; 0 => int i; int r,c,n; // go! while (i < 50) { .5 => row.gain; .5 => col.gain; Math.random2(0,3) => r; Math.random2(0,2) => c; 1 + r * 3 + c => n; if (n==11) 0 => n; if (n==10) { <<< r , c, "*" >>>; } else if (n==12) { <<< r , c, "#" >>>; } else <<< r , c, n >>>; rows[r] => row.freq; cols[c] => col.freq; 0.1 :: second => now; 0.0 => row.gain; 0.0 => col.gain; 0.05 :: second => now; i + 1 => i; } // our notes [ 20, 30, 40, 50, 60, 70, 80, 90 ] @=> int notes[]; // infinite time-loop while( true ) { if (time_elapsed > 110) { break; } // set Math.random2f( 0, 1 ) => m.bodySize; Math.random2f( 0, 1 ) => m.pluckPos; // Math.random2f( 0, 1 ) => m.stringDamping; // Math.random2f( 0, 1 ) => m.stringDetune; // print <<< "---", "" >>>; <<< "body size:", m.bodySize() >>>; <<< "pluck position:", m.pluckPos() >>>; <<< "string damping:", m.stringDamping() >>>; <<< "string detune:", m.stringDetune() >>>; // factor Math.random2f( 1, 4 ) => float factor; for( int i; i < notes.size(); i++ ) { play( Math.random2(0,2)*12 + notes[i], Math.random2f( .6, .9 ) ); 100::ms * factor => now; time_elapsed + 0.1 => time_elapsed; } } // our notes [10, 18, 26, 34, 42, 50, 58, 66 ] @=> notes; // infinite time-loop while( true ) { if (time_elapsed > 120) { break; } // set Math.random2f( 0, 1 ) => m.bodySize; Math.random2f( 0, 1 ) => m.pluckPos; // Math.random2f( 0, 1 ) => m.stringDamping; // Math.random2f( 0, 1 ) => m.stringDetune; // print <<< "---", "" >>>; <<< "body size:", m.bodySize() >>>; <<< "pluck position:", m.pluckPos() >>>; <<< "string damping:", m.stringDamping() >>>; <<< "string detune:", m.stringDetune() >>>; // factor Math.random2f( 1, 4 ) => float factor; for( int i; i < notes.size(); i++ ) { play( Math.random2(0,2)*12 + notes[i], Math.random2f( .6, .9 ) ); 100::ms * factor => now; time_elapsed + 0.1 => time_elapsed; } } // our notes [ 53, 50, 54, 60, 44, 48, 65, 63 ] @=> notes; // infinite time-loop while( true ) { // set Math.random2f( 0, 1 ) => m.bodySize; Math.random2f( 0, 1 ) => m.pluckPos; // Math.random2f( 0, 1 ) => m.stringDamping; // Math.random2f( 0, 1 ) => m.stringDetune; // print <<< "---", "" >>>; <<< "body size:", m.bodySize() >>>; <<< "pluck position:", m.pluckPos() >>>; <<< "string damping:", m.stringDamping() >>>; <<< "string detune:", m.stringDetune() >>>; // factor Math.random2f( 1, 4 ) => float factor; for( int i; i < notes.size(); i++ ) { play( Math.random2(0,2)*12 + notes[i], Math.random2f( .6, .9 ) ); 100::ms * factor => now; } } } // basic play function (add more arguments as needed) fun void play( float note, float velocity ) { // start the note Std.mtof( note ) => m.freq; velocity => m.pluck; } // Call the function to generate the sound spork ~ generateRandomSoundPattern(); // graphics render loop 1 => int move_dir; 0 => int iter; while( true ) { for( LightBulb star : stars ) { 0.01 => float MOVE_LIM; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => float move_x; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => float move_y; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => float move_z; star.translate(@(move_x, move_y, move_z)); } 0.01 => float MOVE_LIM; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => float move_x; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => float move_y; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => float move_z; main_component.translate(@(move_x, move_y, move_z)); 0.03 => MOVE_LIM; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => move_x; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => move_y; Math.random2f(-1 * MOVE_LIM, MOVE_LIM) => move_z; waterfall.translate(@(move_x, move_y, move_z)); if (iter % 100 == 0) { -1 * move_dir => move_dir; } // map to interleaved format map2waveform( samples, positions ); // set the mesh position // waveform.geo().positions( positions ); // map to spectrum display map2spectrum( response, positions ); // next graphics frame GG.nextFrame() => now; iter + 1 => iter; }