{
   "name" : "Formant Synthesis Singers GUI Demo",
   "author" : "Bruno Ruviaro",
   "ancestor_list" : [],
   "description" : "Four voice types available: Soprano, Alto, Tenor, Bass (buttons SATB). Two vibrato controls: speed and depth (horizontal sliders). \r\nFundamental frequency control (vertical slider). Vowel control: choose a vowel for each corner of the 2D space (drop down menus).",
   "labels" : [
      "gui",
      "synthesis techniques",
      "formant synthesis",
      "singers",
      "voice"
   ],
   "id" : "1-4Uz",
   "is_private" : null,
   "code" : "// ************************************\r\n// Formant Synthesis Demo (GUI)\r\n// Patch 1 - Singing vowels\r\n// Bruno Ruviaro, 2013-08-29\r\n// ************************************\r\n\r\n/*\r\nFour voice types available:\r\nSoprano, Alto, Tenor, Bass (buttons SATB).\r\n\r\nTwo vibrato controls: speed and depth (horizontal sliders).\r\n\r\nFundamental frequency control (vertical slider).\r\n\r\nVowel control: choose a vowel for each corner of the 2D space (drop down menus).\r\n\r\nHow to start: select all (ctrl + A), then evaluate (ctrl + enter).\r\n(on a Mac, use the command key instead of control)\r\n\r\n*/\r\n\r\ns.waitForBoot({\r\n\r\n\tvar formantTable, win, subwin, slider2D, vowels, menu00, menu01, menu11, menu10, display00, display01, display11, display10, f00, f01, f11, f10, fxy, synth, startButton, buttonsSATB, selectedSATB, updateAll, freqSlider, fundamentalFrequency, vibratoSpeed, vibratoDepth, vibratoSpeedSlider, vibratoDepthSlider;\r\n\r\n\t// This was taken from FormantTable class from SC3plugins.\r\n\t// Hard-coded here so that this patch won't depend on SC3plugins.\r\n\tformantTable = IdentityDictionary.new;\r\n\tformantTable.put(\\sopranoA, [[800, 1150, 2900, 3900, 4950], [0, -6, -32, -20, -50].dbamp, [80, 90, 120, 130, 140]]);\r\n\tformantTable.put(\\sopranoE, [[350, 2000, 2800, 3600, 4950], [0, -20, -15, -40, -56].dbamp, [60, 100, 120, 150, 200]]);\r\n\tformantTable.put(\\sopranoI, [[270, 2140, 2950, 3900, 4950], [0, -12, -26, -26, -44].dbamp, [60, 90, 100, 120, 120]]);\r\n\tformantTable.put(\\sopranoO, [[450, 800, 2830, 3800, 4950], [0, -11, -22, -22, -50].dbamp, [70, 80 ,100, 130, 135]]);\r\n\tformantTable.put(\\sopranoU, [[325, 700, 2700, 3800, 4950], [0, -16, -35, -40, -60].dbamp, [50, 60, 170, 180, 200]]);\r\n\tformantTable.put(\\altoA, [[800, 1150, 2800, 3500, 4950], [0, -4, -20, -36, -60].dbamp, [80, 90, 120, 130, 140]]);\r\n\tformantTable.put(\\altoE, [[400, 1600, 2700, 3300, 4950], [0, -24, -30, -35, -60].dbamp, [60, 80, 120, 150, 200]]);\r\n\tformantTable.put(\\altoI, [[350, 1700, 2700, 3700, 4950], [0, -20, -30, -36, -60].dbamp, [50, 100, 120, 150, 200]]);\r\n\tformantTable.put(\\altoO, [[450, 800, 2830, 3500, 4950], [0, -9, -16, -28, -55].dbamp, [70, 80, 100, 130, 135]]);\r\n\tformantTable.put(\\altoU, [[325, 700, 2530, 3500, 4950], [0, -12, -30, -40, -64].dbamp, [50, 60, 170, 180, 200]]);\r\n\tformantTable.put(\\counterTenorA, [[660, 1120, 2750, 3000, 3350], [0, -6, -23, -24, -38].dbamp, [80, 90, 120, 130, 140]]);\r\n\tformantTable.put(\\counterTenorE, [[440, 1800, 2700, 3000, 3300], [0, -14, -18, -20, -20].dbamp, [70, 80, 100, 120, 120]]);\r\n\tformantTable.put(\\counterTenorI, [[270, 1850, 2900, 3350, 3590], [0, -24, -24, -36, -36].dbamp, [40, 90, 100, 120, 120]]);\r\n\tformantTable.put(\\counterTenorO, [[430, 820, 2700, 3000, 3300], [0, -10, -26, -22, -34].dbamp, [40, 80, 100, 120, 120]]);\r\n\tformantTable.put(\\counterTenorU, [[370, 630, 2750, 3000, 3400], [0, -20, -23, -30, -34].dbamp, [40, 60, 100, 120, 120]]);\r\n\tformantTable.put(\\tenorA, [[650, 1080, 2650, 2900, 3250], [0, -6, -7, -8, -22].dbamp, [80, 90, 120, 130, 140]]);\r\n\tformantTable.put(\\tenorE, [[400, 1700, 2600, 3200, 3580], [0, -14, -12, -14, -20].dbamp, [70, 80, 100, 120, 120]]);\r\n\tformantTable.put(\\tenorI, [[290, 1870, 2800, 3250, 3540], [0, -15, -18, -20, -30].dbamp, [40, 90, 100, 120, 120]]);\r\n\tformantTable.put(\\tenorO, [[400, 800, 2600, 2800, 3000], [0, -10, -12, -12, -26].dbamp, [40, 80, 100, 120, 120]]);\r\n\tformantTable.put(\\tenorU, [[350, 600, 2700, 2900, 3300], [0, -20, -17, -14, -26].dbamp, [40, 60, 100, 120, 120]]);\r\n\tformantTable.put(\\bassA, [[600, 1040, 2250, 2450, 2750], [0, -7, -9, -9, -20].dbamp, [60, 70, 110, 120, 130]]);\r\n\tformantTable.put(\\bassE, [[400, 1620, 2400, 2800, 3100], [0, -12, -9, -12, -18].dbamp, [40, 80, 100, 120, 120]]);\r\n\tformantTable.put(\\bassI, [[250, 1750, 2600, 3050, 3340], [0, -30, -16, -22, -28].dbamp, [60, 90, 100, 120, 120]]);\r\n\tformantTable.put(\\bassO, [[400, 750, 2400, 2600, 2900], [0, -11, -21, -20, -40].dbamp, [40, 80, 100, 120, 120]]);\r\n\tformantTable.put(\\bassU, [[350, 600, 2400, 2675, 2950], [0, -20, -32, -28, -36].dbamp, [40, 80, 100, 120, 120]]);\r\n\r\n\t// convert bandwidth to 1/q\r\n\tformantTable.keysDo({ arg key;\r\n\t\tformantTable[key][0].do({ arg center, i;\r\n\t\t\tformantTable[key][2][i] = formantTable[key][2][i] / center;\r\n\t\t});\r\n\t});\r\n\r\n\r\n\tvowels = [\"I\", \"E\", \"A\", \"O\", \"U\"];\r\n\tfundamentalFrequency = 100;\r\n\tvibratoSpeed = 6;\r\n\tvibratoDepth = 7;\r\n\r\n\t// Interpolation between corners\r\n\tfxy = {arg x, y;\r\n\r\n\t\t3.collect({arg e;\r\n\t\t\t5.collect({arg i;\r\n\t\t\t\t( f00[e][i] * (1-x) * (1-y) ) +\r\n\t\t\t\t( f10[e][i] * x * (1-y) ) +\r\n\t\t\t\t( f01[e][i] * (1-x) * y ) +\r\n\t\t\t\t( f11[e][i] * x * y );\r\n\t\t})});\r\n\t};\r\n\r\n\t// Main Window\r\n\tWindow.closeAll;\r\n\twin = Window(\"Formant Synthesis\", Rect(150, 150, 500, 480)).front;\r\n\twin.onClose = {\"done\".postln};\r\n\t// win.background = Color.gray(0.87, 0.95);\r\n\twin.background = Color.new255(193, 98, 90, 235);\r\n\r\n\t// Slider2D\r\n\tslider2D = Slider2D(win, Rect(50, 40, 400, 400))\r\n\t.x_(0.5) // initial location of x\r\n\t.y_(0.5) // initial location of y\r\n\t.action_({arg sl;\r\n\t\tvar interpol = fxy.value(sl.x, sl.y);\r\n\t\tif(synth.isNil.not,\r\n\t\t\t{ synth.set(\r\n\t\t\t\t\\freqs, interpol[0],\r\n\t\t\t\t\\amps, interpol[1],\r\n\t\t\t\t\\qs, interpol[2]) },\r\n\t\t\t{ \"not playing\".postln });\r\n\t});\r\n\r\n\t// Vowel display (inside Slider2D)\r\n\tdisplay00 = StaticText(win, Rect(65, 355, 50, 80))\r\n\t.font_(Font(size: 80))\r\n\t.stringColor_(Color.gray(0.8))\r\n\t.align_(\\center)\r\n\t.string_(\"i\");\r\n\r\n\tdisplay01 = StaticText(win, Rect(60, 40, 60, 75))\r\n\t.font_(Font(size: 80))\r\n\t.stringColor_(Color.gray(0.8))\r\n\t.align_(\\center)\r\n\t.string_(\"i\");\r\n\r\n\tdisplay11 = StaticText(win, Rect(380, 40, 50, 75))\r\n\t.font_(Font(size: 80))\r\n\t.stringColor_(Color.gray(0.8))\r\n\t.align_(\\center)\r\n\t.string_(\"i\");\r\n\r\n\tdisplay10 = StaticText(win, Rect(380, 355, 50, 80))\r\n\t.font_(Font(size: 80))\r\n\t.stringColor_(Color.gray(0.8))\r\n\t.align_(\\center)\r\n\t.string_(\"i\");\r\n\r\n\t// Menus\r\n\tmenu00 = PopUpMenu(win, Rect(10, 440, 40, 30))\r\n\t.items_(vowels)\r\n\t.action_({ |v|\r\n\t\tvar key = (selectedSATB ++ v.item).asSymbol;\r\n\t\tf00 = formantTable.at(key);\r\n\t\tkey.postln;\r\n\t\tf00.postln;\r\n\t\tdisplay00.string = v.item.toLower;\r\n\t});\r\n\r\n\tmenu01 = PopUpMenu(win, Rect(10, 10, 40, 30))\r\n\t.items_(vowels)\r\n\t.action_({ |v|\r\n\t\tvar key = (selectedSATB ++ v.item).asSymbol;\r\n\t\tf01 = formantTable.at(key);\r\n\t\tkey.postln;\r\n\t\tf01.postln;\r\n\t\tdisplay01.string = v.item.toLower;\r\n\t});\r\n\r\n\tmenu11 = PopUpMenu(win, Rect(450, 10, 40, 30))\r\n\t.items_(vowels)\r\n\t.action_({ |v|\r\n\t\tvar key = (selectedSATB ++ v.item).asSymbol;\r\n\t\tf11 = formantTable.at(key);\r\n\t\tkey.postln;\r\n\t\tf11.postln;\r\n\t\tdisplay11.string = v.item.toLower;\r\n\t});\r\n\r\n\tmenu10 = PopUpMenu(win, Rect(450, 440, 40, 30))\r\n\t.items_(vowels)\r\n\t.action_({ |v|\r\n\t\tvar key = (selectedSATB ++ v.item).asSymbol;\r\n\t\tf10 = formantTable.at(key);\r\n\t\tkey.postln;\r\n\t\tf10.postln;\r\n\t\tdisplay10.string = v.item.toLower;\r\n\t});\r\n\r\n\r\n\t// Fundamental Frequency Slider\r\n\tfreqSlider = EZSlider(\r\n\t\tparent: win,\r\n\t\tbounds: Rect(457, 100, 35, 290),\r\n\t\tlabel: \"freq\",\r\n\t\tcontrolSpec: ControlSpec(90, 880, 'exp', 1),\r\n\t\taction: {arg slider;\r\n\t\t\tfundamentalFrequency = slider.value;\r\n\t\t\tif(synth.isNil.not,\r\n\t\t\t\t{synth.set(\\freq, fundamentalFrequency)})\r\n\t\t},\r\n\t\tinitVal: fundamentalFrequency,\r\n\t\tlayout: 'vert');\r\n\r\n\t// Vibrato Speed Slider\r\n\tvibratoSpeedSlider = EZSlider(\r\n\t\tparent: win,\r\n\t\tbounds: Rect(50, 10, 232, 20),\r\n\t\tlabel: \"vibrato speed\",\r\n\t\tcontrolSpec: ControlSpec(2, 9, 'lin', 1),\r\n\t\taction: {arg slider;\r\n\t\t\tvibratoSpeed = slider.value;\r\n\t\t\tif(synth.isNil.not,\r\n\t\t\t\t{synth.set(\\vibratoSpeed, vibratoSpeed)})\r\n\t\t},\r\n\t\tinitVal: vibratoSpeed,\r\n\t\tlabelWidth: 100,\r\n\t\tnumberWidth: 30);\r\n\r\n\t// Vibrato Depth Slider\r\n\tvibratoDepthSlider = EZSlider(\r\n\t\tparent: win,\r\n\t\tbounds: Rect(270, 10, 170, 20),\r\n\t\tlabel: \"depth\",\r\n\t\tcontrolSpec: ControlSpec(1, 12, 'lin', 1),\r\n\t\taction: {arg slider;\r\n\t\t\tvibratoDepth = slider.value;\r\n\t\t\tif(synth.isNil.not,\r\n\t\t\t\t{synth.set(\\vibratoDepth, vibratoDepth)})\r\n\t\t},\r\n\t\tnumberWidth: 30,\r\n\t\tinitVal: vibratoDepth);\r\n\r\n\r\n\t// Function to use when a new SATB button\r\n\t// is clicked: update all four corners\r\n\r\n\tupdateAll = {\r\n\t\tvar f00key, f01key, f11key, f10key;\r\n\t\tf00key = (selectedSATB ++ menu00.item).asSymbol;\r\n\t\tf01key = (selectedSATB ++ menu01.item).asSymbol;\r\n\t\tf11key = (selectedSATB ++ menu11.item).asSymbol;\r\n\t\tf10key = (selectedSATB ++ menu10.item).asSymbol;\r\n\t\tf00 = formantTable.at(f00key);\r\n\t\tf01 = formantTable.at(f01key);\r\n\t\tf11 = formantTable.at(f11key);\r\n\t\tf10 = formantTable.at(f10key);\r\n\t\t[f00key, f01key, f11key, f10key].postln;\r\n\t};\r\n\r\n\t// Singer Buttons (will go inside a subview)\r\n\tsubwin = CompositeView(win, Rect(8, 100, 40, 280));\r\n\tsubwin.addFlowLayout(5@5, 5@5);\r\n\r\n\tbuttonsSATB = Array.fill(4, {arg i;\r\n\t\tvar satb = [\"S\", \"A\", \"T\", \"B\"];\r\n\t\tButton(subwin, 25@65)\r\n\t\t.states_([[satb[i]], [satb[i], Color.black, Color.gray]])\r\n\t\t.font_(Font(size: 20))\r\n\t\t.action_({arg button;\r\n\t\t\tif(button.value==1,\r\n\t\t\t\t{\r\n\t\t\t\t\tcase\r\n\t\t\t\t\t{i==0} {\r\n\t\t\t\t\t\tselectedSATB = \"soprano\";\r\n\t\t\t\t\t\tbuttonsSATB[[1,2,3]].do{|bt| bt.value=0};\r\n\t\t\t\t\t\tupdateAll.value;\r\n\t\t\t\t\t\tfreqSlider.valueAction_(580);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t{i==1} {\r\n\t\t\t\t\t\tselectedSATB = \"alto\";\r\n\t\t\t\t\t\tbuttonsSATB[[0,2,3]].do{|bt| bt.value=0};\r\n\t\t\t\t\t\tupdateAll.value;\r\n\t\t\t\t\t\tfreqSlider.valueAction_(380);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t{i==2} {\r\n\t\t\t\t\t\tselectedSATB = \"tenor\";\r\n\t\t\t\t\t\tbuttonsSATB[[0,1,3]].do{|bt| bt.value=0};\r\n\t\t\t\t\t\tupdateAll.value;\r\n\t\t\t\t\t\tfreqSlider.valueAction_(280);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t{i==3} {\r\n\t\t\t\t\t\tselectedSATB = \"bass\";\r\n\t\t\t\t\t\tbuttonsSATB[[0,1,2]].do{|bt| bt.value=0};\r\n\t\t\t\t\t\tupdateAll.value;\r\n\t\t\t\t\t\tfreqSlider.valueAction_(100);\r\n\t\t\t\t\t};\r\n\r\n\t\t\t\t\tselectedSATB.postln;\r\n\t\t\t\t},\r\n\t\t\t\t{(\"button \" ++ i ++ \" says goodbye\").postln});\r\n\r\n\t\t});\r\n\t});\r\n\r\n\t// Button initialization\r\n\tbuttonsSATB[3].valueAction = 1; // bass\r\n\t// Menus initialization\r\n\tmenu00.valueAction = 0; // bassI\r\n\tmenu01.valueAction = 1; // bassE\r\n\tmenu11.valueAction = 2; // bassA\r\n\tmenu10.valueAction = 3; // bassO\r\n\r\n\t// Start Button\r\n\tstartButton = Button(win, Rect(200, 445, 100, 25))\r\n\t.states_([[\"start\", Color.white, Color.green(0.6, 0.6)], [\"stop\", Color.white, Color.red(0.4, 0.3)]])\r\n\t.action_({arg button;\r\n\t\tif(button.value==1,\r\n\t\t\t{synth = Synth(\"formantVoice\", [\r\n\t\t\t\t\\freq, fundamentalFrequency,\r\n\t\t\t\t\\vibratoSpeed, vibratoSpeed,\r\n\t\t\t\t\\vibratoDepth, vibratoDepth])},\r\n\t\t\t{synth.release; synth = nil})\r\n\t});\r\n\r\n\r\n\t// SynthDef\r\n\tSynthDef(\"formantVoice\", { arg\r\n\t\tfreqs = #[ 400, 750, 2400, 2600, 2900 ],\r\n\t\tamps = #[ 1, 0.28, 0.08, 0.1, 0.01 ],\r\n\t\tqs = #[ 0.1, 0.1, 0.04, 0.04, 0.04 ],\r\n\t\tlag = 0.5,\r\n\t\tvibratoSpeed = 6,\r\n\t\tvibratoDepth = 4,\r\n\t\tfreq = 220,\r\n\t\tgate = 1;\r\n\r\n\t\tvar vibrato, in, env, snd;\r\n\r\n\t\tvibrato = SinOsc.kr(vibratoSpeed, mul: vibratoDepth);\r\n\t\tin = Saw.ar(Lag.kr(freq + vibrato, 0.2));\r\n\t\tenv = EnvGen.kr(Env.asr(1), gate, doneAction: 2);\r\n\t\tsnd = Mix.new(BBandPass.ar(in, Lag.kr(freqs, lag), Lag.kr(qs, lag)) * Lag.kr(amps, lag)).dup;\r\n\r\n\t\tOut.ar(0, snd * env);\r\n\r\n\t}).add;\r\n\r\n\r\n}); // end of block;"
}
