// Guitar Tracker, tracks guitar and creates special effects // Copyright 2008 Les Hall // This software is protected by the GNU General Public License // parameters "Guitar_Tracker.wav" => string filename; // the input song path and filename 32::second => dur song_duration; // approximate duration of input song 0 => int mouse_device; // mouse device number 0 => int keyboard_device; // keyboard device number // variables Event note_detected; // broadcasts when a note is detected Event d_key_pressed; // broadcasts when the d key is pressed (for drums) int drums_status; // the on/off status of the drums effect int pitshift_status; // the on/off status of the pitch shifter effect float pitshift_value; // the value of the pitch shifter effect int feedback_status; // the on/off status of the feedback effect float feedback_value; // the value of the feedback effect float feedback_delay_value; // the value of the feedback delay int reverb_status; // the on/off status of the reverb effect float reverb_value; // the value of the reverb effect float filter_status; // the on/off status of the filter effect float filter_x_value; // the x value of the filter effect float filter_y_value; // the y value of the filter effect // instantiate the class objects Note_Detector ND; Note_Printer NP; Keyboard_Interface KI; Mouse_Interface MI; //HID_Interface HI; DelaylineSnare01 Snare; kjzBD101 Bass_Drum; Drum_Player Drums; // the patch WvIn wvin => ND.in; // input guitar signal, from file wvin => PitShift pitshift; // the bass guitar pitch shifter pitshift => Gain adder => Dyno dyno => Gain buffer; // the feedforward patch buffer => DelayA feedback => adder; // the feedback effect buffer => NRev reverb; // the reverb effect reverb => Gain volume; // output processed guitar sound Drums.out => volume; // output drum sound volume => LiSa lisa_bass_guitar; // send output signal to the bass guitar sampler volume => LiSa lisa_drums; // send output signal to drum sampler lisa_bass_guitar => dac; // output guitar sampler lisa_drums => dac; // output drum sampler volume => dac; // output current sound wvin => LPF lpf_x => Gain f_sum => volume; wvin => BPF bpf_x => f_sum; wvin => HPF hpf_y => f_sum; wvin => BPF bpf_y => f_sum; // initialize the patch parameters pitshift.mix (1.0); // only one bass guitar image pitshift.shift (1.0); // turn off bass guitar feedback.gain (0.0); // turn off feedback feedback.max (second); // set maximum feedback delay feedback.delay (100::ms); // set initial feedback delay dyno.limit (); // make the dyno be a limiter (with presets) reverb.mix (0.0); // turn off reverb lpf_x.Q (4); // set Q of x bpf bpf_x.Q (4); // set Q of x bpf hpf_y.Q (4); // set Q of y bpf bpf_y.Q (4); // set Q of y bpf f_sum.gain (0.0); // turn off filter effect // loop forever while (true) { // play the wave file wvin.path (filename); // wait duration of wvin song song_duration => now; } // Class to play the drums class Drum_Player { // parameters 100.0 => float bass_drum_freq_max; // maximum frequency for bass drum 100.0 => float snare_freq_min; // minimum frequency for snare drum // variables int play_drums_shred_id; // id of the play_drums shred float frequency; // frequency of detected note // the patch Gain out; // the patch paramters out.gain (1.0); // launch key watcher shred spork ~ enable_disable (); // shred to respond to "d" key presses fun void enable_disable () { while (true) { d_key_pressed => now; 1 - drums_status => drums_status; if (drums_status) { Bass_Drum.output => out; Snare.output => out; reverb =< volume; spork ~ play_drums (); <<<"Drums On", "">>>; } else { Bass_Drum.output =< out; Snare.output =< out; reverb => volume; Machine.remove (play_drums_shred_id); <<<"Drums Off", "">>>; } } } // shred to play drums fun void play_drums () { me.id () => play_drums_shred_id; // record shred id while (true) { note_detected => now; ND.get_note_frequency () => frequency; if (frequency < bass_drum_freq_max) { // play the drum Bass_Drum.hit (1.0); } else { if (frequency > snare_freq_min) { // loop thru the delay model parameter for(0 => int j; j < 4; j++) { // play the note Snare.hit(j + 1 + 10); } } } } } } // simple analog-sounding bass drum with pitch and amp decay and sine overdrive class kjzBD101 { Impulse i; // the attack i => Gain g1 => Gain g1_fb => g1 => LPF g1_f => Gain BDFreq; // BD pitch envelope i => Gain g2 => Gain g2_fb => g2 => LPF g2_f; // BD amp envelope // drum sound oscillator to amp envelope to overdrive to LPF to output BDFreq => SinOsc s => Gain ampenv => SinOsc s_ws => LPF s_f => Gain output; g2_f => ampenv; // amp envelope of the drum sound 3 => ampenv.op; // set ampenv a multiplier 1 => s_ws.sync; // prepare the SinOsc to be used as a waveshaper for overdrive // set default 80.0 => BDFreq.gain; // BD initial pitch: 80 hz 1.0 - 1.0 / 2000 => g1_fb.gain; // BD pitch decay g1_f.set(100, 1); // set BD pitch attack 1.0 - 1.0 / 4000 => g2_fb.gain; // BD amp decay g2_f.set(50, 1); // set BD amp attack .75 => ampenv.gain; // overdrive gain s_f.set(600, 1); // set BD lowpass filter fun void hit(float v) { v => i.next; } fun void setFreq(float f) { f => BDFreq.gain; } fun void setPitchDecay(float f) { f => g1_fb.gain; } fun void setPitchAttack(float f) { f => g1_f.freq; } fun void setDecay(float f) { f => g2_fb.gain; } fun void setAttack(float f) { f => g2_f.freq; } fun void setDriveGain(float g) { g => ampenv.gain; } fun void setFilter(float f) { f => s_f.freq; } } // Simple Delayline Snare 01 version 0.3 testing // by kijjaz // simple delaylines snare drum design: still a testing prototype // licence: Attribution-Share Alike 3.0 // You are free: // * to Share to copy, distribute and transmit the work // * to Remix to adapt the work // Under the following conditions: // * Attribution. You must attribute the work in the manner specified by the author or licensor // (but not in any way that suggests that they endorse you or your use of the work). // * Share Alike. If you alter, transform, or build upon this work, // you may distribute the resulting work only under the same, similar or a compatible license. // news: version 0.2 is never released (0.3 is developed in a different direction) // now includes a way to improve sound and flexibility from version 0.1 // and now is a class for easy integration with other programs // note: there are 4 output for you to chuck out: outputTop, outputBottom, outputTopDrive, output // output = sum of the first three outputs. warning! can be very loud // use (or set) outputLimit if you need limiting class DelaylineSnare01 { // constructing a snare drum: create top head, body, and bottom head // connect them by top -> body -> bottom -> body -> then back to top DelayA drumTop => Gain g1 => DelayA drumBody1 => Gain g2 => DelayA drumBottom => Gain g3 => DelayA drumBody2 => Gain g4 => drumTop; // prepare maximum delay time for the parts // (one second is like 315 meters in the air, i hope a snare should not be that big haha!) second => drumTop.max => drumBody1.max => drumBottom.max => drumBody2.max; // make drum top head sustain by applying feedback drumTop => Gain g5 => drumTop; // make drum bottom head sustain and attach noisy snares by AM with Noise drumBottom => Gain g6 => drumBottom; Noise snare => LPF snare_f => g6; g6.op(3); // prepare a stick and attach to Drum Top Head Impulse stickImp => LPF stickImp_f => SinOsc stickDrive => drumTop; stickDrive.sync(1); // prepare overdrive for the stick impulse Gain input => stickDrive; // chuck to input for external hitting! // prepare seperate outputs: outputTop, outputBottom, outputTopDrive drumTop => Gain outputTop; drumBottom => Gain outputBottom; drumTop => Gain drumTop_driveGain => SinOsc drumTop_drive => Gain outputTopDrive; drumTop_drive.sync(1); // prepare one master output: output, outputLimit (output with a Dyno limiter) drumTop => Gain outputTopMix => Gain output; drumBottom => Gain outputBottomMix => output; drumTop_drive => Gain outputTopDriveMix => output; output => Dyno outputLimit; outputLimit.limit(); // initialization to default sound (by using loadPreset function) loadAllValues( [200.0, 600, 1000, .3, .4, .5, .5, .6, 5, 9000, .5, 5, 180, 5, .5, .5, 1, .5], false); fun void loadAllValues(float values[], int stickDrive) { values[0] => topFreq; values[1] => bottomFreq; values[2] => bodyFreq; values[3] => topDecay; values[4] => bottomDecay; values[5] => topGain; values[6] => bottomGain; values[7] => bodyGain; values[8] => snareGain; values[9] => snareFreq; values[10] => snareQ; values[11] => stickGain; values[12] => stickFreq; values[13] => stickQ; values[14] => topDriveGain; values[15] => topMix; values[16] => bottomMix; values[17] => topDriveMix; if (stickDrive) { stickDriveOn(); } else { stickDriveOff(); } } // - - - functions - - - // drum part delay time (set by supplying frequency) fun void topFreq(float f) { second / f => drumTop.delay; } fun void bottomFreq(float f) { second / f => drumBottom.delay; } fun void bodyFreq(float f) { second / f => drumBody1.delay => drumBody1.delay; } // top and bottom decay rate fun void topDecay(float rate) { rate => g5.gain; } fun void bottomDecay(float rate) { rate => g6.gain; } // gain from top to body fun void topGain(float g) { g => g1.gain; } // gain from top to body fun void bottomGain(float g) { g => g3.gain; } // gain from body to top and bottom fun void bodyGain(float g) { g => g2.gain => g4.gain; } // snare & bottom set up fun void snareGain(float g) { g => snare.gain; } fun void snareFreq(float f) { f => snare_f.freq; } fun void snareQ(float Q) { Q => snare_f.Q; } // stick fun void stickGain(float g) { g => stickImp.gain; } fun void hit(float velocity) { velocity => stickImp.next; } fun void stickFreq(float f) { f => stickImp_f.freq; } fun void stickQ(float Q) { Q => stickImp_f.Q; } // compute Sine overdrive fun void stickDriveOn() { stickDrive.op(1); } // bypass the drive unit fun void stickDriveOff() { stickDrive.op(-1); } // set gain for drum top overdrive fun void topDriveGain(float g) { g * .5 => drumTop_driveGain.gain; } // set balance mix of each sound into the main output (and also the outputLimit) fun void topMix(float g) { g => outputTopMix.gain; } fun void bottomMix(float g) { g => outputBottomMix.gain; } fun void topDriveMix(float g) { g => outputTopDriveMix.gain; } } // Interface to the keyboard class Keyboard_Interface { // parameters 10::ms => dur kbd_env_dur; // keyboard envelope duration // variables int kbd_value; // value of key pressed // hid initialization Hid hid; HidMsg hidmsg; if (!hid.openKeyboard (keyboard_device)) { me.exit(); } // launch the time loop spork ~ time_loop (); // time loop fun void time_loop () { while (true) { hid => now; // wait for a key press while (hid.recv (hidmsg)) { // while new keys are in queue if (hidmsg.isButtonDown ()) { // check for button down message hidmsg.which => kbd_value; // save button value if (kbd_value == 6) { // if "c" for "clean guitar" is pressed // turn off pitch shifter 0 => pitshift_status; 1 => pitshift_value; pitshift.shift (pitshift_value); // turn off feedback 0 => feedback_status; 0 => feedback_value; feedback.gain (feedback_value); // turn off reverb 0 => reverb_status; 0 => reverb_value; reverb.mix (reverb_value); reverb.gain (1.0); // turn off filter 0 => filter_status; 0 => filter_x_value; 0 => filter_y_value; lpf_x.freq (filter_x_value * 2000); bpf_x.freq (filter_x_value * 2000); bpf_y.freq (filter_y_value * 2000); hpf_y.freq (filter_y_value * 2000); f_sum.gain (0.0); // turn off filter effect <<<"clean guitar", "">>>; } if (kbd_value == 7) { // if "d" for "drums" is pressed // toggle drums on/off d_key_pressed.broadcast (); } if (kbd_value == 19) { // if "p" for "pitch shift" is pressed // turn on pitch shifting 1 => pitshift_status; 0.5 => pitshift_value; pitshift.shift (pitshift_value); <<<"pitch shifter on", "">>>; } if (kbd_value == 9) { // if "f" for "feedback" is pressed // turn on feedback 1 => feedback_status; 0 => feedback_value; feedback.gain (feedback_value); <<<"feedback on", "">>>; } if (kbd_value == 21) { // if "r" for "reverb" is pressed // turn on reverb 1 => reverb_status; 0 => reverb_value; reverb.mix (reverb_value); <<<"reverb on", "">>>; } if (kbd_value == 5) { // if "b" for "filter" is pressed // turn on filter 1 => filter_status; 0 => filter_x_value; 0 => filter_y_value; f_sum.gain (1.0); // turn on filter effect reverb.gain (0.0); // trun off other effects <<<"filter on", "">>>; } } } } } } // Interface to mouse class Mouse_Interface { // variables 250 => float fb_x_size; // scales x size 500 => float fb_y_size; // scales y size 250 => float bp_x_size; // scales x size 500 => float bp_y_size; // scales y size // parameters int lisa_drums_status; // status of lisa drums module, 0 = off, 1 = recording, 2 = playback int lisa_bass_status; // status of lisa bass guitar module, 0 = off, 1 = recording, 2 = playback time lisa_drums_start_time; // start time of lisa drums time lisa_bass_start_time; // start time of lisa bass dur lisa_drums_duration; // duration of drum loop dur lisa_bass_duration; // duration of bass guitar loop // hid initialization Hid hid; HidMsg hidmsg; if (!hid.openMouse (mouse_device)) { me.exit(); } // launch the time loop spork ~ time_loop (); // time loop fun void time_loop () { 0 => lisa_drums_status; 0 => lisa_bass_status; lisa_drums.duration (60::second); lisa_bass_guitar.duration (60::second); while (true) { hid => now; while (hid.recv (hidmsg)) { if (hidmsg.isButtonDown() & (drums_status | pitshift_status) ) { if (drums_status) { if (lisa_drums_status == 0) { lisa_drums.recPos (0::second); lisa_drums.record (1); now => lisa_drums_start_time; <<<"recording drum loop", "">>>; } if (lisa_drums_status == 1) { now - lisa_drums_start_time => lisa_drums_duration; lisa_drums.record (0); lisa_drums.loop (1); lisa_drums.loopStart (0::second); lisa_drums.loopEnd (lisa_drums_duration); lisa_drums.play (1); <<<"playing drum loop", "">>>; } if (lisa_drums_status == 2) { lisa_drums.play (0); <<<"drum loop off", "">>>; } (lisa_drums_status + 1) % 3 => lisa_drums_status; } else { if (pitshift_status) { if (lisa_bass_status == 0) { lisa_bass_guitar.recPos (0::second); lisa_bass_guitar.record (1); now => lisa_bass_start_time; <<<"recording bass guitar loop", "">>>; } if (lisa_bass_status == 1) { now - lisa_bass_start_time => lisa_bass_duration; lisa_bass_guitar.record (0); lisa_bass_guitar.loop (1); lisa_bass_guitar.loopStart (0::second); lisa_bass_guitar.loopEnd (lisa_bass_duration); lisa_bass_guitar.play (1); <<<"playing bass guitar loop", "">>>; } if (lisa_bass_status == 2) { lisa_bass_guitar.play (0); <<<"bass guitar loop off", "">>>; } (lisa_bass_status + 1) % 3 => lisa_bass_status; } } } if (hidmsg.isButtonUp ()) { // put something fhere later } if ( hidmsg.isMouseMotion() ) { if (feedback_status) { // if feedback is enabled // do X adjustment hidmsg.deltaX / fb_x_size +=> feedback_value; if (feedback_value < -1) { // check lower limit on feedback -1 => feedback_value; } if (feedback_value > 1) { // check upper limit on feedback 1 => feedback_value; } feedback.gain (feedback_value); // do Y adjustment hidmsg.deltaY / fb_y_size -=> feedback_delay_value; if (feedback_delay_value < 0.001) { // check lower limit on feedback delay 0.001 => feedback_delay_value; } if (feedback_delay_value > 1) { // check upper limit on feedback delay 1 => feedback_delay_value; } feedback.delay ((feedback_delay_value / 10.0)::second); // 100::ms max } } if (filter_status) { // if filter is enabled // do X adjustment hidmsg.deltaX / bp_x_size +=> filter_x_value; if (filter_x_value < 0.1) { // check lower limit on filter 0.1 => filter_x_value; } if (filter_x_value > 1) { // check upper limit on filter 1 => filter_x_value; } lpf_x.freq (filter_x_value * 2000); bpf_x.freq (filter_x_value * 2000); // do Y adjustment hidmsg.deltaY / bp_y_size +=> filter_y_value; if (filter_y_value < 0.1) { // check lower limit on filter 0.1 => filter_y_value; } if (filter_y_value > 1) { // check upper limit on filter 1 => filter_y_value; } hpf_y.freq (filter_y_value * 2000); bpf_y.freq (filter_y_value * 2000); } } } } } // Detect notes played by an audio source class Note_Detector { // parameters 8 * 1024 => int num_samples; // number of samples per FFT, must be a power of two 0 => int separation; // tolerance of tracked note index 0.010 => float noise_threshold; // below this is noise (normalized) 0.500 => float pick_threshold; // larger than this is a pick (normalized) 1.10 => float rms_threshold; // larger than this is RMS change (normalized) 0.90 => float tempo_tau; // time constant for calculating tempo 0.90 => float rms_tau; // time constant for calculating RMS 1000 => float f_max; // maximum frequency to look for peaks // variables num_samples / 2 => int num_freqs; // number of frequencies in FFT complex spectrum[num_freqs]; // spectrum of the input signal float magnitude[num_freqs]; // magnitude of spectrum of the input signal (second / samp) / num_samples => float f_bin; // the frequency of each bin (f_max / f_bin) $ int => int i_max; // maximum index to look for peaks i_max => int num_peaks; // number of peaks to detect, must be 1 or more int index[num_peaks]; // frequency index (bin) of the detected peaks float frequency[num_peaks]; // frequencies of the detected peaks float amplitude[num_peaks]; // amplitudes of the detected peaks int prev_index; // the previous selected index float prev_frequency; // the previous selected frequency float prev_amplitude; // the previous selected amplitude int note_index; // index of the last detected note float note_frequency; // frequency of the latest detected note float note_amplitude; // amplitude of the latest detected note float note_tempo; // the tempo of the notes, in notes per second now => time note_time; // the time of the latest detected note now => time prev_note_time; // the time of the previous detected note float temp; // temporary variable int fresh_note; // 1 if previous note has cleared int num_peaks_detected; // the number of progressively larger peaks int detected_peak; // this is the one we found from the most recent fft int peak; // temporary index variable float rms_value; // current RMS value float prev_rms_value; // previous RMS value float rms_average; // average RMS value // the patch Gain in => LPF lpf => FFT fft => blackhole; in => FFT fft2 =^ RMS rms => blackhole; SinOsc sinosc => Gain out; // the patch parameters lpf.freq (f_max / 10); lpf.Q (4); num_samples => fft.size; Windowing.rectangle (num_samples) => fft.window; // launch the time loop spork ~ time_loop (); // get latest detected note information fun int get_note_index () { return note_index; } fun float get_note_frequency () { return note_frequency; } fun float get_note_amplitude () { return note_amplitude; } fun float get_note_tempo () { return note_tempo; } // the time loop fun void time_loop () { while (true) { // remember previous data index[detected_peak] => prev_index; frequency[detected_peak] => prev_frequency; amplitude[detected_peak] => prev_amplitude; rms_value => prev_rms_value; // get the fft data fft.upchuck ().cvals () @=> spectrum; // get the rms data rms.upchuck () @=> UAnaBlob blob; blob.fval (0) => rms_value; // track rms average rms_tau * rms_average + (1.0 - rms_tau) * rms_value => rms_average; // calculate spectrum magnitude for (1 => int f; f < i_max; f++) { (spectrum[f] $ polar).mag => magnitude[f]; } // find the largest peak for normalization 0 => temp; for (1 => int f; f < i_max; f++) { if (magnitude[f] > temp) { magnitude[f] => temp; } } // detect all the peaks 0 => peak; for (1 => int f; f < i_max; f++) { if ( (magnitude[f] > magnitude[f-1]) & (magnitude[f] > magnitude[f+1]) ) { if (magnitude[f] > pick_threshold * temp) { f => index[peak]; f * f_bin => frequency[peak]; magnitude[f] => amplitude[peak]; peak++; } } } peak => num_peaks_detected; // eliminate inaudible frequencies and harmonics for (0 => int i; i < num_peaks_detected - 1; i++) { if (frequency[i] < 20.0) { 0 => amplitude[i]; } else { for (i + 1 => int j; j < num_peaks_detected; j++) { (index[j] $ float) / (index[i] $ float) => temp; Std.fabs (Math.round (temp) - temp) => temp; if (temp < 0.2) { 0 => amplitude[j]; } } } } // find largest remaining peak 0 => temp; 0 => detected_peak; for (0 => int i; i < num_peaks_detected; i++) { if (amplitude[i] > temp) { amplitude[i] => temp; i => detected_peak; } } // see if note is cleared (fresh) if ( magnitude[note_index] < (pick_threshold * note_amplitude) ) { 1 => fresh_note; } // detect the most recently plucked note if (frequency[detected_peak] >= 20.0) { if ( (Std.abs (index[detected_peak] - note_index) > separation) | fresh_note) { if (rms_value > rms_threshold * prev_rms_value) { index[detected_peak] => note_index; frequency[detected_peak] => note_frequency; amplitude[detected_peak] => note_amplitude; note_detected.broadcast (); 0 => fresh_note; sinosc.freq (note_frequency); note_time => prev_note_time; now => note_time; second / (note_time - prev_note_time) => temp; tempo_tau * note_tempo + (1.0 - tempo_tau) * temp => note_tempo; } } } // wait for one FFT cycle (num_samples / 4)::samp => now; } } } // Print detected notes class Note_Printer { // spork the note monitor shred spork ~ note_monitor (); // shred to monitor note detections fun void note_monitor () { while (true) { note_detected => now; <<>>; } } }