My final project for Music 220A looks to incorporate rhythmic oscillation into an ambient vocoder. Inspired by the synthetic “chunkiness” of songs such as Daft Punk & The Weekend’s I Feel it Coming, I wanted to both create my own autonomous vocoder and emulate an intense musical pulse throughout it.
The vocoder inputs external audio and MIDI signals and reconstructs the audio signal to match the pitches of the MIDI signal. Using ChucK, the program runs both the audio and a MIDI-informed synthesizer through a Fast Fourier Transform and deposits the ensuing data into two distinct spectrums. The spectrum from the audio input is then mapped to that of the synthesizer input, at which point the resulting array is run through an Inverse Fast Fourier Transform, which converts the sound signal back to audio that we recognize as audio mapped to MIDI pitches.
The four ChucK files shown below are able to run the Oscillating Vocoder from any computer affixed to external MIDI and Audio input. vocoder.ck runs the vocoder without arpeggiation, oscillatingVocoder.ck runs the vocoder with random octave shifts every .3 seconds, and oscillatingSynthesizer.ck adds a layer of synthesized arpeggiation in sync with oscillatingVocoder.ck. Initiate.ck maps each of the these ChucK files to be triggered by user-specified MIDI notes.
-- Kyle Laviana
/* name: initialize.ck (Music 220A Final Project File #1) desc: switch between ChucK files via specified MIDI keynums by: Kyle Laviana Code Resources: --gomidi.ck */ //--------------------------------------------------------- // Midi Set-up // device to open (see: chuck --probe) 0 => int device; // get from command line if( me.args() ) me.arg(0) => Std.atoi => device; MidiIn min; MidiMsg msg; // try to open MIDI port (see chuck --probe for available devices) if(!min.open(device)) me.exit(); // print out device that was opened <<< "MIDI device:", min.num(), " -> ", min.name() >>>; //--------------------------------------------------------- //create string for file 1 me.dir() + "/fp1.ck" => string path1; //create string for file 2 me.dir() + "/fp2.ck" => string path2; //create string for file 2 me.dir() + "/fp3.ck" => string path3; int id; int id2; //begin with file 1 Machine.add(path1) => id; <<< "Commence file 1" >>>; while(true) { // wait on midi event min => now; // get the midimsg while(min.recv(msg)) { //start file 1 if (msg.data2 == 36) { <<< "Commence file 1" >>>; Machine.remove(id); Machine.remove(id2); Machine.add(path1) => id; } //start file 2 if (msg.data2 == 37) { <<< "Commence file 2" >>>; Machine.remove(id); Machine.remove(id2); Machine.add(path2) => id; } //start file 3 if (msg.data2 == 38) { <<< "Commence file 3" >>>; Machine.remove(id); Machine.remove(id2); Machine.add(path2) => id; Machine.add(path3) => id2; } 10::ms => now; } }
/* name: vocoder.ck (Music 220A Final Project File #2) desc: Ambient Midi Vocoder by: Kyle Laviana Code Resources: --Ananya Misra and Ge Wang: polyfony.ck --Eduard Aylon: vocoder.ck --Gio Jacuzzi: voxbox.ck */ // Input // synth Gain line_synth => FFT fft_synth => blackhole; // mic adc.left => PoleZero dcblock_mic => FFT fft_mic => blackhole; // Output // processed audio IFFT ifft_output => PoleZero dcblock_output => PitShift shift => Chorus chorus => LPF filter_lpf => HPF filter_hpf => JCRev reverb => Gain vocoderOutput => dac; // unprocessed audio adc => Gain adcOutput => dac; // Midi Set-up //--------------------------------------------------------------------- // device to open (see: chuck --probe) 0 => int device; // get from command line if( me.args() ) me.arg(0) => Std.atoi => device; MidiIn min; MidiMsg msg; // try to open MIDI port (see chuck --probe for available devices) if(!min.open(device)) me.exit(); // print out device that was opened <<< "MIDI device:", min.num(), " -> ", min.name() >>>; //--------------------------------------------------------------------- // Unit Generator Initial Values 0.1 => vocoderOutput.gain; 0.9 => adcOutput.gain; 0.1 => line_synth.gain; filter_lpf.freq(0.999 * 10000); filter_hpf.freq(0.001 * 10000); // Effect Values shift.mix(1.0); shift.shift(1.0); reverb.mix(0.1); chorus.mix(0.2); chorus.modDepth(0.0); // Remove Zero Frequency Components to Reduce Distortion 0.999 => dcblock_mic.blockZero; 0.999 => dcblock_output.blockZero; // Fast Fourier Transform Constants 600 => int FFT_SIZE => fft_synth.size => fft_mic.size => ifft_output.size; FFT_SIZE => int WIN_SIZE; FFT_SIZE/32 => int HOP_SIZE; // Define Hann Window for FFT Windowing.hann(WIN_SIZE) => fft_mic.window => fft_synth.window => ifft_output.window; // Define Spectrum Arrays // spectrum array for synth transform complex spectrum_synth[WIN_SIZE/2]; // spectrum array for mic transform complex spectrum_mic[WIN_SIZE/2]; // temp variables for complex to polar conversion polar temp_polar_mic, temp_polar_synth; // Define NoteEvent class NoteEvent extends Event { float note; } NoteEvent on; NoteEvent off; // FFT Implementation //-------------------------------------------------------------------- fun void vocode_filter() { while(true) { // take mic fft fft_mic.upchuck(); // take synth fft fft_synth.upchuck(); // retrieve results of mic transform fft_mic.spectrum(spectrum_mic); // retrieve results of synth transform fft_synth.spectrum(spectrum_synth); // for each value in the mic transform result, convert it from complex to // polar, apply it to the synth transform, and convert it back to complex: for( 0 => int i; i < spectrum_mic.cap(); i++ ) { spectrum_mic[i]$polar => temp_polar_mic; spectrum_synth[i]$polar => temp_polar_synth; // apply magnitude of mic to synth temp_polar_mic.mag => temp_polar_synth.mag; // store result in altered synth transform temp_polar_synth$complex => spectrum_synth[i]; } // take inverse transform of our new altered synth transform ifft_output.transform(spectrum_synth); HOP_SIZE::samp => now; } } spork ~vocode_filter(); //-------------------------------------------------------------------- // Synthesizer Implementation //-------------------------------------------------------------------- fun void synthvoice() { // don't connect to dac until we need it SqrOsc voice; Event off; float note; while (true) { on => now; <<< "NoteOn:", msg.data1, msg.data2 >>>; on.note => note; note => voice.freq; 0.1 => voice.gain; voice => line_synth; while (msg.data1 == 144) { 0.3::second => now; if (msg.data1 == 128) break; } <<< "NoteOff:", msg.data1, msg.data2 >>>; 0.0 => voice.gain; voice =< line_synth; } } // Run the Specified Iterations of the Synth for (0 => int i; i < 4; i++) spork ~synthvoice(); //-------------------------------------------------------------------- while(true) { // wait on midi event min => now; // get the midimsg while(min.recv(msg)) { // print out midi data <<< msg.data1, msg.data2, msg.data3 >>>; // catch only noteon if (msg.data1 == 144) { // store midi note number Std.mtof(msg.data2) => on.note; // signal the event on.signal(); // yield without advancing time to allow shred to run me.yield(); } else { off.signal(); me.yield(); } } }
/* name: oscillatingVocoder.ck (Music 220A Final Project File #3) desc: Oscillating Ambient Midi Vocoder by: Kyle Laviana Code Resources: --Ananya Misra and Ge Wang: polyfony.ck --Eduard Aylon: vocoder.ck --Gio Jacuzzi: voxbox.ck */ // Input // synth Gain line_synth => FFT fft_synth => blackhole; // mic adc.left => PoleZero dcblock_mic => FFT fft_mic => blackhole; // Output // processed audio IFFT ifft_output => PoleZero dcblock_output => PitShift shift => Chorus chorus => LPF filter_lpf => HPF filter_hpf => JCRev reverb => Gain vocoderOutput => dac; // unprocessed audio adc => Gain adcOutput => dac; // Midi Set-up //--------------------------------------------------------------------- // device to open (see: chuck --probe) 0 => int device; // get from command line if( me.args() ) me.arg(0) => Std.atoi => device; MidiIn min; MidiMsg msg; // try to open MIDI port (see chuck --probe for available devices) if(!min.open(device)) me.exit(); // print out device that was opened <<< "MIDI device:", min.num(), " -> ", min.name() >>>; //--------------------------------------------------------------------- // Unit Generator Initial Values 0.15 => vocoderOutput.gain; 0.9 => adcOutput.gain; 0.1 => line_synth.gain; filter_lpf.freq(0.999 * 10000); filter_hpf.freq(0.001 * 10000); // Effect Values shift.mix(1.0); shift.shift(1.0); reverb.mix(0.1); chorus.mix(0.2); chorus.modDepth(0.0); // Remove Zero Frequency Components to Reduce Distortion 0.999 => dcblock_mic.blockZero; 0.999 => dcblock_output.blockZero; // Fast Fourier Transform Constants 600 => int FFT_SIZE => fft_synth.size => fft_mic.size => ifft_output.size; FFT_SIZE => int WIN_SIZE; FFT_SIZE/32 => int HOP_SIZE; // Define Hann Window for FFT Windowing.hann(WIN_SIZE) => fft_mic.window => fft_synth.window => ifft_output.window; // Define Spectrum Arrays // spectrum array for synth transform complex spectrum_synth[WIN_SIZE/2]; // spectrum array for mic transform complex spectrum_mic[WIN_SIZE/2]; // temp variables for complex to polar conversion polar temp_polar_mic, temp_polar_synth; // Define NoteEvent class NoteEvent extends Event { float note; } NoteEvent on; NoteEvent off; // FFT Implementation //-------------------------------------------------------------------- fun void vocode_filter() { while(true) { // take mic fft fft_mic.upchuck(); // take synth fft fft_synth.upchuck(); // retrieve results of mic transform fft_mic.spectrum(spectrum_mic); // retrieve results of synth transform fft_synth.spectrum(spectrum_synth); // for each value in the mic transform result, convert it from complex to // polar, apply it to the synth transform, and convert it back to complex: for( 0 => int i; i < spectrum_mic.cap(); i++ ) { spectrum_mic[i]$polar => temp_polar_mic; spectrum_synth[i]$polar => temp_polar_synth; // apply magnitude of mic to synth temp_polar_mic.mag => temp_polar_synth.mag; // store result in altered synth transform temp_polar_synth$complex => spectrum_synth[i]; } // take inverse transform of our new altered synth transform ifft_output.transform(spectrum_synth); HOP_SIZE::samp => now; } } spork ~vocode_filter(); //-------------------------------------------------------------------- // Synthesizer Implementation //-------------------------------------------------------------------- fun void synthvoice() { // don't connect to dac until we need it SqrOsc voice; Event off; float note; while (true) { on => now; <<< "NoteOn:", msg.data1, msg.data2 >>>; on.note => note; note => voice.freq; 0.1 => voice.gain; voice => line_synth; while (msg.data1 == 144) { 0.3::second => now; if (msg.data1 == 128) break; 1 => float variance; if (Math.random2(-1, 1) == -1) { 0.5 => variance; } else if (Math.random2(-1, 1) == 1) { 2 => variance; } note*variance => voice.freq; } <<< "NoteOff:", msg.data1, msg.data2 >>>; 0.0 => voice.gain; voice =< line_synth; } } // Run the Specified Iterations of the Synth for (0 => int i; i < 6; i++) spork ~synthvoice(); //-------------------------------------------------------------------- while(true) { // wait on midi event min => now; // get the midimsg while(min.recv(msg)) { // print out midi data <<< msg.data1, msg.data2, msg.data3 >>>; // catch only noteon if (msg.data1 == 144) { // store midi note number Std.mtof(msg.data2) => on.note; // signal the event on.signal(); // yield without advancing time to allow shred to run me.yield(); } else { off.signal(); me.yield(); } } }
/* name: oscillatingSynthesizer.ck (Music 220A Final Project File #4) desc: Oscillating Midi Synthesizer by: Kyle Laviana Code Resources: --Ananya Misra and Ge Wang: polyfony.ck */ // Midi Set-up //--------------------------------------------------------------------- // device to open (see: chuck --probe) 0 => int device; // get from command line if( me.args() ) me.arg(0) => Std.atoi => device; MidiIn min; MidiMsg msg; // try to open MIDI port (see chuck --probe for available devices) if(!min.open(device)) me.exit(); // print out device that was opened <<< "MIDI device:", min.num(), " -> ", min.name() >>>; //--------------------------------------------------------------------- // make our own event class NoteEvent extends Event { int note; int velocity; } // the event NoteEvent on; NoteEvent off; // array of ugen's handling each note Event @ us[128]; // the base patch Gain g => JCRev r => dac; .05 => g.gain; .2 => r.mix; // variable defining number of keys beind held int keysOn; // variable defining number of keys released before another one is pressed int keysOff; // Synthesizer Implementation //-------------------------------------------------------------------- fun void handler() { // don't connect to dac until we need it Clarinet c; Event off; int note; // inifinite time loop while( true ) { on => now; <<< "NoteOn:", msg.data1, msg.data2 >>>; on.note => note; // dynamically repatch c => g; Std.mtof(note) => c.freq; .4 + on.velocity / 128.0 * .6 => c.startBlowing; while (msg.data1 == 144) { 0.3::second => now; 1 => float variance; if (Math.random2(-1, 1) == -1) { .5 => variance; } else if (Math.random2(-1, 1) == 1) { 2 => variance; } Std.mtof(note)*variance => c.freq; } if (keysOn == 0) { <<< "NoteOff:", msg.data1, msg.data2, "fourth" >>>; c =< g; continue; } else if (keysOff >= 2) { off @=> us[note]; <<< "NoteOff:", msg.data1, msg.data2, "third" >>>; c.stopBlowing( 10.0 ); null @=> us[note]; 100::ms => now; c =< g; } else if (msg.data2 == note) { off @=> us[note]; <<< "NoteOff:", msg.data1, msg.data2, "first" >>>; } else { off @=> us[note]; off => now; <<< "NoteOff:", msg.data1, msg.data2, "second" >>>; } <<< keysOff >>>; c.stopBlowing( 10.0 ); 100::ms => now; c =< g; } } // spork handlers, one for each voice for( 0 => int i; i < 20; i++ ) { spork ~ handler(); } //-------------------------------------------------------------------- // infinite time loop while(true) { // wait on midi event min => now; // get the midimsg while(min.recv(msg)) { // print out midi data <<< msg.data1, msg.data2, msg.data3 >>>; // catch only noteon if(msg.data1 == 144) { 1 +=> keysOn; 0 => keysOff; // store midi note number msg.data2 => on.note; // store velocity msg.data3 => on.velocity; // signal the event on.signal(); // yield without advancing time to allow shred to run me.yield(); } else { 1 -=> keysOn; 1 +=> keysOff; if(us[msg.data2] != null) us[msg.data2].signal(); } } }
Hide and Seek by Imogen Heap
Audio routed through Ableton Live 9, Scarlett Focusrite 2i4, & Akai Miniak