// 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 filename 0 => int mouse_device; // mouse device number 0 => int keyboard_device; // keyboard device number // variables Event note_detected; // fires when a note is detected 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 int reverb_status; // the on/off status of the reverb effect float reverb_value; // the value of the reverb effect // instantiate the class objects Note_Detector ND; Note_Printer NP; Keyboard_Interface KI; Mouse_Interface MI; //HID_Interface HI; // the patch WvIn wvin => ND.in; // input guitar signal, from file //ND.out => dac; // sinusoid at fundamental wvin => PitShift pitshift; // the bass guitar pitch shifter pitshift => Gain adder => Gain buffer; // the feedforward patch buffer => Gain feedback => adder; // the feedback effect buffer => NRev reverb; // the reverb effect reverb => dac; // output sound to DAC // 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 reverb.mix (0.0); // turn off reverb // play the wave file wvin.path (filename); // loop forever while (true) { second => now; } // 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); <<<"clean guitar", "">>>; } 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", "">>>; } } } } } } // Interface to mouse class Mouse_Interface { // variables 1000 => float x_size; // scales x size 1000 => float y_size; // scales y size // parameters // 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 () { while (true) { hid => now; while (hid.recv (hidmsg)) { if (hidmsg.isButtonDown()) { // put something fun here later } if (hidmsg.isButtonUp ()) { // put something fun here later } if( hidmsg.isMouseMotion() ) { if (feedback_status) { // if feedback is enabled hidmsg.deltaX / 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); } if (reverb_status) { // if reverb is enabled hidmsg.deltaY / y_size -=> reverb_value; if (reverb_value < 0) { // check lower limit on reverb 0 => reverb_value; } if (feedback_value > 1) { // check upper limit on reverb 1 => reverb_value; } reverb.mix (reverb_value); } } } } } } // 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.25 => 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; <<>>; } } }