// @title amplitudeFrequencyTracker.ck // @author Chris Chafe (cc@ccrma), Hongchan Choi (hongchan@ccrma) // @desc A starter code for homework 5, Music220a-2012 // @note amplitude/frequency tracking using UAna ugens // @version chuck-1.3.1.3 / ma-0.2.2c // @revision 1 // MODIFICATIONS by ddolben: // This file has been modified to use a simple FM synthesizer // to generate sound based on a frequency and amplitude tracker // IMPORTANT NOTE: this patch is designed to use microphone. // If you're using speakers and your microphone at the same time, // you might experience serious feedback. Make sure to use // the headphone or earbuds to avoid it. // pipe input into analysis audio graph: // track amplitude for gain of an FM patch // frequency will be max bin amplitude from the spectrum SndBuf riff => FFT fft =^ RMS rms => blackhole; me.sourceDir() + "/Chill Riff.wav" => riff.read; riff => dac.left; // setup FFT: choose high-quality transform parameters 4096 => fft.size; Windowing.hann(fft.size() / 2) => fft.window; 20 => int overlap; 0 => int ctr; second / samp => float srate; // actual audio graph and parameter setting // NOTE: gain 'g' prevents direct connection bug adc => Gain g => dac.left; FMFS fm; fm.out => Gain gOut => dac.right; // set initial frequency fm.setPitch(440); fm.attack(1000::ms); // instantiate a smoother to smooth tracker results (see below) Smooth sma, smf; // set time constant: shorter time constant gives faster // response but more jittery values sma.setTimeConstant((fft.size() * 2)::samp); smf.setTimeConstant((fft.size() / 4)::samp); // setGainAndFreq() spork ~ setGainAndFreq(); fun void setGainAndFreq() { while (true) { // Set pitch according to the frequency tracker fm.setPitch(smf.getLast()); // Set the FM ratio according to the amplitude tracker fm.setRatio(sma.getLast() * 2.0); 1::samp => now; } } // main inf-loop while(true) { // hop in time by overlap amount (fft.size() / overlap)::samp => now; // then we've gotten our first bufferful if (ctr > overlap) { // compute the FFT and RMS analyses rms.upchuck(); rms.fval(0) => float a; Math.rmstodb(a) => float db; // boost the sensitity 30 + db * 15 => db; // but clip at maximum Math.min(100, db) => db; sma.setNext(Math.dbtorms(db)); 0 => float max; 0 => int where; // look for a frequency peak in the spectrum // half of spectrum to save work for(0 => int i; i < fft.size()/4; ++i) { if(fft.fval(i) > max) { fft.fval(i) => max; i => where; } } // get frequency of peak (where $ float) / fft.size() * srate => float f; // then convert it to MIDI pitch f => Math.ftom => float p; // set lower boundary: prevents note too low Math.max(20, p) => p; // new freq if not noise if(db > 10.0) { smf.setNext(Math.mtof(p)); } } ctr++; } // @class Smooth // @desc contral signal generator for smooth transition class Smooth { // audio graph Step in => Gain out => blackhole; Gain fb => out; out => fb; // init: smoothing coefficient, default no smoothing 0.0 => float coef; initGains(); // initGains() fun void initGains() { in.gain(1.0 - coef); fb.gain(coef); } // setNext(): set target value fun void setNext(float value) { in.next(value); } // getLast(): return current interpolated value fun float getLast() { 1::samp => now; return out.last(); } // setExpo(): set smoothing directly from exponent fun void setExpo(float value) { value => coef; initGains(); } // setTimeConstant(): set smoothing duration fun void setTimeConstant(dur duration) { Math.exp(-1.0 / (duration / samp)) => coef; initGains(); } } // END OF CLASS: Smooth // @class FMFS fm implementation from scratch with envelopes // @author Chris Chafe (cc@ccrma) class FMFS { // modulator with index envelope and carrier with // amplitude envelope SinOsc mod => Gain ind => ADSR indEnv => SinOsc car; car => ADSR ampEnv => Gain out => blackhole; // generate detailed pitch contour with skew, periodic // vibrato and random jitter // unity constant for center pitch of event Step unity => Gain pit; // add in skew offset controlled by simple Envelope Step skew => Envelope skewEnv => pit; // add in vibrato controlled by ADSR Envelope SinOsc perVib => ADSR vibEnv => pit; // add in some low-frequency randomness Noise ranVib => ResonZ lpf => pit; // apply pitch everywhere that depends on it pit => car; pit => Gain rat => mod; pit => ind; // configure modes of above UG's // config oscillator for fm input (see SinOsc class) 2 => car.sync; // freq is controlled by input only 0.0 => car.freq; // config oscillator for fm input 2 => mod.sync; // freq is controlled by input only 0.0 => mod.freq; // config gain to multiply inputs (see UG class) 3 => ind.op; // initial values setPitch(440.0); setIndex(4.0); setRatio(3.0); // set A, D, S, and R all at once ampEnv.set (10::ms, 10::ms, 0.5, 100::ms); indEnv.set (50::ms, 50::ms, 1, 100::ms); skewEnv.duration (100::ms); vibEnv.set (50::ms, 500::ms, 0.4, 100::ms); // skew in semitones setSkew(0.0); // vibrato frequency, excursion in semitones setVib(6.5, 1.0); // randomness frequency, excursion in semitones setJit(0, 0); // setPitch() fun void setPitch(float pitch) { pit.gain(pitch); } // setIndex() fun void setIndex(float index) { mod.gain(index); } // setRatio() fun void setRatio(float ratio) { rat.gain(ratio); } // setSkew() fun void setSkew(float semitones) { // units are equal-tempered semitones (Math.pow(2.0, semitones/12.0) - 1.0) => float skewAmt; skew.next( skewAmt ); } // setVib() fun void setVib(float f, float semitones) { perVib.freq(f); // scale it to equal-tempered quartertones // in each direction Math.pow(2.0, semitones/24.0) - 1.0 => float jitAmt; perVib.gain( jitAmt ); } // setJit() fun void setJit(float f, float semitones) { lpf.freq(f); // bandwidth of low-frequency filter resonance, // ok as a constant lpf.Q(1.0); // scale it to equal-tempered quartertones // in each direction Math.pow(2.0, semitones/24.0) - 1.0 => float jitAmt; // empirically scaled up to where it's noticeable 12.0 *=> jitAmt; ranVib.gain(jitAmt); } // attack(): sculpt a note using envelopes fun void attack(dur attack) { // rise time of ADSR ampEnv.attackTime(attack); indEnv.attackTime(attack); vibEnv.attackTime(attack); // duration of simple Envelope skewEnv.duration(attack); // trigger ADSR ampEnv.keyOn(); indEnv.keyOn(); vibEnv.keyOn(); // kind of counterintuitive, but skew from skewAmt // to 0 for attack skewEnv.keyOff(); } // release() fun void release(dur release) { // release time of ADSR ampEnv.releaseTime(release); indEnv.releaseTime(release); vibEnv.releaseTime(release); // duration of simple Envelope skewEnv.duration(release); // trigger ADSR release ampEnv.keyOff(); indEnv.keyOff(); vibEnv.keyOff(); // also counterintuitive, but skew from 0 // to skewAmt during note off skewEnv.keyOn(); } } // END OF CLASS: FMFS