//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;
}