//A midi controlled FFT cross synthetisizer //By: Pyry Pakkanen ( 2007 ) // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . false => int HIGH_QUALITY; //Set to true if you got a powerfull beast. I think they have Linux for rhinoceros now. 1 => int LQ; if(!HIGH_QUALITY) 2 => LQ; //The low quality mode is a bit chunky but that's sometimes the price to pay for realtime capability. 0.0 => float a; //More voice emphasis //0.5 => float a; //Somewhere inbetween. The volume compensations in the fft code have not yet been fully worked out. //1.0 => float a; //More synth emphasis Gain output => dac; 1.0 => output.gain; //The synth plays each note with full 1.0 gain. Setting 0.2 for the global gain would be better but we can compensate for the big amplitudes in the FFT code. //MIDI related global arrays and variables: int notes[128]; //The noteon array 0.0 => float pitch_bend; 0.0 => float modulation; 1.0 => float modulation_depth; //The depth of vibrato in semitones. 7.0 => float modulation_freq; 2.0*pi*modulation_freq => float modulation_omega; 0.0 => float global_synth_volume; //We could get this from the RMS of the magnitude spectrum but letting instruments directly modify this improves attack response. spork~midi_input(); //Handle midi and spork oscillators when a key is pressed. spork~xsynth(); //Take control of the synth output and cross adc with it. me.yield(); while(true){ //Run forever second => now; } //The Sporking functions fun void midi_input(){ MidiIn min; //connect to port 0 if( !min.open(0) ) { <<<"No", "midi!">>>; me.exit(); } MidiMsg msg; while(true){ // Use the MIDI Event from MidiIn min => now; while( min.recv(msg) ){ //<<>>; //Print the message if (msg.data1 == 144){ msg.data2 => int the_pitch; spork~saw_osc_note( the_pitch,msg.data3/128.0 ); true => notes[the_pitch]; } if (msg.data1 == 128){ false => notes[msg.data2]; } //Pitch bend and modulation: if (msg.data1 == 224){ (msg.data3/32.0 - 2.0) => pitch_bend; } if (msg.data1 == 176){ modulation_depth * (msg.data3/128.0) => modulation; } } } } fun void xsynth(){ //Cross synth output and adc // our patch /* //We could also use an external file for the voice SndBuf sndbuf => FFT X => blackhole; sndbuf.read("sound.wav"); sndbuf.loop(true);*/ adc => FFT X => blackhole; output =< dac; //Plug out output => FFT Y => blackhole; //Plug in // synthesis IFFT ifft => Gain g => dac; //Modify and plug back to dac 0.25*LQ => g.gain; //The overall gain differences have been assimilated to the FFT modification code // set FFT size 4096/LQ => X.size => Y.size => int FFT_SIZE; // desired hop size FFT_SIZE / 16 * LQ => int HOP_SIZE; // set window and window size Windowing.hann(1024/LQ) => X.window; Windowing.hann(1024/LQ) => Y.window; Windowing.hann(1024/LQ) => ifft.window; // use this to hold contents of the FFT complex Z[FFT_SIZE/2]; float noise_profile[FFT_SIZE/2]; adc => dac;<<<"Getting","noise profile...","Do not touch the michrophone">>>; FFT_SIZE::samp => now; //Fill the FFT buffer; X.upchuck(); //Take FFT. for( int i; i < X.size()/2; i++ ){ (X.cval(i)$polar).mag => noise_profile[i]; //Get the noise magnitude spectrum } adc =< dac;<<<"Done."," ">>>; // control loop while( true ) { // take fft X.upchuck(); Y.upchuck(); // cross for( int i; i < X.size()/2/LQ; i++ ) //5.0*Math.sqrt((X.cval(i)$polar).mag) * Y.cval(i) => Z[i]; //More emphasis on the synth //0.3*global_synth_volume*( %((X.cval(i)$polar).mag, (Y.cval(i)$polar).phase ) $ complex ) => Z[i]; //More emphasis on the voice. //(%( Math.max(0.0, (X.cval(i)$polar).mag - noise_profile[i]*1.0 ), (X.cval(i)$polar).phase ) $ complex ) => Z[i]; <-- Noise removed from the source //5.0*Math.sqrt( ( Math.max(0.0, (X.cval(i)$polar).mag - noise_profile[i]*1.0 ) ) )* Y.cval(i) => Z[i]; //More emphasis on the synth //0.3*global_synth_volume*( %(Math.max(0.0, (X.cval(i)$polar).mag - noise_profile[i]*1.0 ), (Y.cval(i)$polar).phase ) $ complex ) => Z[i]; //More emphasis on the voice. //The parameter 'a' controls the emphasis 0.0 => voice, 1.0 => synth 0.3 * global_synth_volume * (1.0 + a * a * a * 4.0) * ( %( Math.pow( Math.max(0.0, (X.cval(i)$polar).mag - noise_profile[i] * 1.0 ), 1.0 - 0.5 * a ) * ( (1.0 - a) + a * a * (Y.cval(i)$polar).mag ), (Y.cval(i)$polar).phase ) $ complex ) => Z[i]; // take ifft ifft.transform( Z ); // advance time HOP_SIZE::samp => now; } } //The Instruments: //Simple triangle wave fun void tri_osc_note(int pitch, float velocity) { 10::ms => dur update_time; 0.2::second => dur falloff_time; Math.pow(0.01,update_time/falloff_time) => float k; //The exponential falloff coefficient //0.0 => float t; //update_time/second => float delta_t; TriOsc tri => Gain g => output; //BlitSaw bsaw => g; //Add some even harmonics in to the mix //9 => bsaw.harmonics; //0.8 => tri.gain; //0.2 => bsaw.gain; velocity => g.gain; velocity +=> global_synth_volume; while (notes[pitch]){ Std.mtof(pitch + pitch_bend + modulation*Math.sin(now/second*modulation_omega) ) => tri.freq; //tri.freq() => bsaw.freq; update_time => now; //delta_t +=> t; } now + falloff_time => time later; while (now < later){ Std.mtof(pitch + pitch_bend + modulation*Math.sin(now/second*modulation_omega) ) => tri.freq; //tri.freq() => bsaw.freq; //Decrease volume exponentially g.gain()*(1.0 - k) -=> global_synth_volume; //This may cause some accuracy issues... g.gain()*k => g.gain; update_time => now; //delta_t +=> t; } 0.01 * velocity -=> global_synth_volume; //Repeated multiplication with k gets us down to 0.01 //At least set global_synth_volume to zero if there is nothing playing. (Clean up floating point errors) false => int any_on; for(0 => int i; i < 128; i++) ( notes[i] || any_on ) => any_on; if(!any_on) 0.0 => global_synth_volume; } //Detuned saw_wave. fun void saw_osc_note(int pitch, float velocity) { 1.0 => float beat; //Frequency separation 10::ms => dur update_time; 0.2::second => dur falloff_time; Math.pow(0.01,update_time/falloff_time) => float k; //0.0 => float t; //update_time/second => float delta_t; SawOsc saw => Gain g => output; SawOsc saw_d1 => g; SawOsc saw_d2 => g; 1.0/3.0 => saw.gain => saw_d1.gain => saw_d2.gain; velocity => g.gain; velocity +=> global_synth_volume; while (notes[pitch]){ Std.mtof(pitch + pitch_bend + modulation*Math.sin(now/second*modulation_omega) ) => saw.freq; saw.freq() + beat => saw_d1.freq; saw.freq() - beat => saw_d2.freq; update_time => now; //delta_t +=> t; } now + falloff_time => time later; while (now < later){ Std.mtof(pitch + pitch_bend + modulation*Math.sin(now/second*modulation_omega) ) => saw.freq; saw.freq() + beat => saw_d1.freq; saw.freq() - beat => saw_d2.freq; g.gain()*(1.0 - k) -=> global_synth_volume; //This may cause some accuracy issues... g.gain()*k => g.gain; //Decrease volume exponentially; update_time => now; //delta_t +=> t; } 0.01 * velocity -=> global_synth_volume; //Repeated multiplication with k gets us down to 0.01 //At least set global_synth_volume to zero if there is nothing playing. (Clean up floating point errors) false => int any_on; for(0 => int i; i < 128; i++) ( notes[i] || any_on ) => any_on; if(!any_on) 0.0 => global_synth_volume; } fun void blitsaw_osc_note(int pitch, float velocity) { 10::ms => dur update_time; 0.2::second => dur falloff_time; Math.pow(0.01,update_time/falloff_time) => float k; //0.0 => float t; //update_time/second => float delta_t; BlitSaw bsaw => Gain g => output; 14 => bsaw.harmonics; velocity => g.gain; velocity +=> global_synth_volume; while (notes[pitch]){ Std.mtof(pitch + pitch_bend + modulation*Math.sin(now/second*modulation_omega) ) => bsaw.freq; update_time => now; //delta_t +=> t; } now + falloff_time => time later; while (now < later){ Std.mtof(pitch + pitch_bend + modulation*Math.sin(now/second*modulation_omega) ) => bsaw.freq; //Decrease volume exponentially g.gain()*(1.0 - k) -=> global_synth_volume; //This may cause some accuracy issues... g.gain()*k => g.gain; update_time => now; //delta_t +=> t; } 0.01 * velocity -=> global_synth_volume; //Repeated multiplication with k gets us down to 0.01 //At least set global_synth_volume to zero if there is nothing playing. (Clean up floating point errors) false => int any_on; for(0 => int i; i < 128; i++) ( notes[i] || any_on ) => any_on; if(!any_on) 0.0 => global_synth_volume; }