// Guitar Tracker, tracks guitar and creates special effects // Copyright 2008 Les Hall // This software is protected by the GNU General Public License // parameters // variables Event note_detected; // fires when a note is detected // instantiate the class objects Note_Detector ND; Note_Printer NP; // the patch WvIn wvin => ND.in; //ND.out => dac; wvin => dac; // play the wave file wvin.path ("Guitar_Tracker.wav"); // loop forever while (true) { second => 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; <<>>; } } } // Detect notes played by an audio source class Note_Detector { // parameters 2 * 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) 0.9 => float tempo_tau; // time constant for calculating tempo 0.75 => 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 =^ 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_freqs) => 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 > 1.5 * 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); sinosc.gain (note_amplitude); 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 / 2)::samp => now; } } }