A vocoder singing Happy Birthday
Forked from: Vocoder Puccini
Log in to post a comment.
// Forked from "Vocoder Puccini" by athibaul // https://dittytoy.net/ditty/6f30b0885d function dbamp(dB) { return 10 ** (dB / 20); } const TWOPI = 2*Math.PI; class Smoother { // We feed values to this object, and it smoothes them out constructor(v, dur=0.07) { this.v = v; this.a0 = clamp01(ditty.dt / dur); } update(target) { // should be called once per sample return this.v += this.a0 * (target - this.v); } } const formantSynth = synth.def(class { constructor(options) { this.phase = 0; let freq = midi_to_hz(options.note); this.freqsmoo = new Smoother(freq); let ffreq = options.ffreq; this.ffreqsmoo = new Smoother(ffreq); this.ampsmoo = new Smoother(dbamp(options.ampdb)); this.bwsmoo = new Smoother(options.bw); let formratio = options.ffreq / freq; this.k = Math.floor(formratio); this.q = formratio - this.k; } process(note, env, tick, options) { // Movable ring modulation as explained here: // http://msp.ucsd.edu/techniques/latest/book-html/node95.html let freq = midi_to_hz(options.note); freq = this.freqsmoo.update(freq); let ffreq = this.ffreqsmoo.update(options.ffreq); let dphase = TWOPI*freq*ditty.dt; this.phase += dphase; if(this.phase > TWOPI) { this.phase -= TWOPI; // End of one period // We can change the formant ratio now! let formratio = ffreq / freq; this.k = Math.floor(formratio); this.q = formratio - this.k; } let carrier = (1-this.q) * Math.cos(this.k*this.phase) + this.q * Math.cos((this.k+1)*this.phase); let bw = this.bwsmoo.update(options.bw); let b = bw / freq; let a = 0.5*b*b; let modulator = 1/(1 + a * (1-Math.cos(this.phase))); let amp = this.ampsmoo.update(dbamp(options.ampdb)); return carrier * modulator * amp * env.value; } }, {ffreq:650, bw:80, env:one, ampdb:0}); // Formant values taken from // https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html const table = { a: { freq: [650, 1080, 2650, 2900, 3250], amp: [0, -6, -7, -8, -22], bw: [80, 90, 120, 130, 140] }, e: { freq: [400, 1700, 2600, 3200, 3580], amp: [0, -14, -12, -14, -20], bw: [70, 80, 100, 120, 120] }, i: { freq: [290, 1870, 2800, 3250, 3540], amp: [0, -15, -18, -20, -30], bw: [40, 90, 100, 120, 120] }, o: { freq: [400, 800, 2600, 2800, 3000], amp: [0, -10, -12, -12, -26], bw: [40, 80, 100, 120, 120] }, u: { freq: [350, 600, 2700, 2900, 3300], amp: [0, -20, -17, -14, -26], bw: [40, 60, 100, 120, 120] }, r: { freq: [400, 1700, 2600, 3200, 3580], amp: [-60, -60, -60, -60, -60], bw: [70, 80, 100, 120, 120] } }; ditty.bpm = 60; const nn = [ // Happy birthday to you c4, c4, d4, c4, f4, e4, // Happy birthday to you c4, c4, d4, c4, g4, f4, // Happy birthday dear (name) c4, c4, c5, a4, f4, e4, d4, // Happy birthday to you as4, as4, a4, f4, g4, f4 ]; const vowels = [ // Happy birthday to you "a", "a", "a", "a", "o", "u", // Happy birthday to you "a", "a", "a", "a", "o", "u", // Happy birthday dear (name) "a", "a", "a", "a", "e", "o", "u", // Happy birthday to you "a", "a", "a", "a", "o", "u", ]; const durs = [ // Happy birthday to you 0.5, 0.5, 1.0, 1.0, 1.0, 2.0, // Happy birthday to you 0.5, 0.5, 1.0, 1.0, 1.0, 2.0, // Happy birthday dear (name) 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // Happy birthday to you 0.5, 0.5, 1.0, 1.0, 1.0, 2.0, ]; const totaldur = durs.reduce( (a,b) => a+b, 0); loop( (k) => { if(k > 0) {sleep(1); return;} // Tenor part const smoo_note = new Smoother(c3, 0.1); const s = new Array(5); for(let ns = 0; ns < 5; ns++) { s[ns] = formantSynth.play( (tick, options) => smoo_note.update(options.nn) + clamp01(tick-options.lastnote-0.5) * (0.5* Math.sin(4.7*TWOPI*tick)), { env: adsr, attack:0.1, release:0.1, duration:totaldur, nn: nn[0], ffreq:table[vowels[0]].freq[ns], bw:table[vowels[0]].bw[ns], ampdb:-60, lastnote: 0, } ); } for(let i=0; i<nn.length; i++) { for(let ns=0; ns<5; ns++) { s[ns].options.nn = nn[i]; if(vowels[i] != "r") { s[ns].options.ffreq = table[vowels[i]].freq[ns]; s[ns].options.bw = table[vowels[i]].bw[ns]; } s[ns].options.ampdb = table[vowels[i]].amp[ns]; if(i>0) { s[ns].options.lastnote += durs[i-1]; } } sleep(durs[i]); } }, {name:"vocoder", amp:0.2});