{
   "code" : "(\r\n// ======================================\r\n//\r\n// Wavetable Builder with GUI\r\n//\r\n// SuperCollider app for building and saving single-cycle waveforms and wavetables\r\n// Uses either a mix of sine waves or an envelope of points connected by curves\r\n//\r\n// To run, select all code and evaluate it (press Shift and Enter)\r\n//\r\n// Created by Paul Miller (palemoonrising.co.uk)\r\n//\r\n// GNU GPL 3 license as per SuperCollider\r\n//\r\n// ======================================\r\n//\r\n// init\r\nvar d, s;\r\nvar guiData;\r\nd = ();   // data event\r\nguiData = ();\r\n\r\n// ============  Settings (initial values):  ============\r\n\r\nd.audioOutBus = 0;\r\nd.outLevel = 0.2;  // initial level\r\n\r\n// d.alwaysOnTop = true;  // keeps the window in front\r\nd.alwaysOnTop = false;\r\n\r\nd.envMode = false;  // Sine Waves - initial mode\r\n// d.envMode = true;  // Envelope\r\n\r\n// buffer length in samples\r\nd.arrBufLengthPresets = [   // must be power of 2\r\n\t[\"128\",  128],\r\n\t[\"512\",  512],\r\n\t[\"1024\",  1024],\r\n\t[\"2048\",  2048],\r\n\t[\"4096\", 4096],\r\n\t[\"8192\", 8192],\r\n];\r\nd.defaultBufLength = 2048;  // initial val\r\n// d.defaultBufLength = 4096;\r\n// d.defaultBufLength = 1024;\r\n\r\nd.addBufLengthToFilename = true;  // e.g. myWavetable_2048.wav\r\n// d.addBufLengthToFilename = false;  //  e.g. myWavetable.wav\r\n\r\n// save file formats\r\nd.arrSaveFormats = [  // name, headerFormat, sampleFormat\r\n\t[\"Wave, 16-bit Int\",  \"wav\", \"int16\"],\r\n\t[\"Wave, 24-bit Int\",  \"wav\", \"int24\"],\r\n\t[\"Wave, 32-bit Int\",  \"wav\", \"int32\"],\r\n\t[\"Wave, Float\", \"wav\", \"float\"],\r\n\t[\"Aiff, 16-bit Int\",  \"aiff\", \"int16\"],\r\n\t[\"Aiff, 24-bit Int\",  \"aiff\", \"int24\"],\r\n\t[\"Aiff, 32-bit Int\",  \"aiff\", \"int32\"],\r\n\t[\"Aiff, Float\", \"aiff\", \"float\"],\r\n];\r\nd.defaultSaveFormatIndex = 3;  // initial val\r\n\r\nd.numSines = 16;   // initial val\r\n// d.numSines = 24;\r\n// d.numSines = 32;\r\n\r\nd.maxSines = 32;  // must be >= d.numSines\r\n// d.maxSines = 48;\r\n// d.maxSines = 64;\r\n\r\nd.minSineFreq = 0.1;  // must be between 0-1\r\n// d.minSineFreq = 1;\r\n\r\nd.maxSineFreq = 64;  // must be >= d.maxSines\r\n// d.maxSineFreq = 32;\r\n// d.maxSineFreq = 48;\r\n// d.maxSineFreq = 128;\r\n\r\nd.pulsing = true;   // synth with changing level\r\n// d.pulsing = false;  // synth with fixed level\r\n\r\n// frequency of note played\r\nd.playFreq = 220;  // must be between d.minPlayFreq and d.maxPlayFreq)\r\nd.minPlayFreq = 20;\r\nd.maxPlayFreq = 4000;\r\n\r\nd.windowWidth = 1000;  // or wider for >16 sines\r\n// d.windowWidth = 1200;\r\n\r\nd.windowHeight = 660;\r\n// d.windowHeight = 900;\r\n\r\nd.useMultiSlider = true;   // use multiSlider for sines\r\n// d.useMultiSlider = false;  // use separate sliders\r\n\r\n// colours\r\nd.colWindow = Color(0.72, 0.77, 0.78);  // grey-green\r\nd.colButton = Color(0.21, 0.48, 0.73);  // blue\r\nd.colLabel = Color(0.97, 0.99, 1);  // pale blue\r\nd.colLabelString = Color(0.03, 0.28, 0.43);  // dark blue\r\nd.colHighlight = Color(1, 0.98, 0.67);  // pale yellow\r\nd.colPlayActive = Color(0.47, 0.8, 0.17);  // bright green\r\nd.colSemiGrey = Color.grey(0.85);\r\nd.moreColors = true;  // or false\r\nif (d.moreColors.not, {\r\n\td.colPlay = d.colButton;\r\n\td.colStop = d.colButton;\r\n\td.colSave = d.colButton;\r\n\td.colDeleteAll = d.colButton;\r\n\td.colHelp = d.colButton;\r\n}, {\r\n\td.colPlay = Color(0.26, 0.54, 0.26);  // dark green\r\n\td.colStop = Color(0.72, 0.34, 0.34);  // red\r\n\td.colSave = Color(0.5, 0.37, 0.62);  // purple\r\n\td.colDeleteAll = Color(0.47, 0.47, 0.54);  // grey-blue\r\n\td.colHelp = Color(0.8, 0.39, 0);  // orange\r\n});\r\n\r\n// ============  End of Settings  ============\r\n\r\n// catch errors & init\r\nd.maxSines = d.maxSines.max(d.numSines);\r\nd.minSineFreq = d.minSineFreq.clip(0, 1);\r\nd.maxSineFreq = d.maxSineFreq.max(d.maxSines);\r\nd.sineFreqSpec = ControlSpec(d.minSineFreq, d.maxSineFreq, 'lin');\r\nd.outFreqSpec = ControlSpec(d.minPlayFreq, d.maxPlayFreq, 'exp');\r\nd.bufLength = d.defaultBufLength;\r\nd.saveFormatIndex = d.defaultSaveFormatIndex;\r\nd.tablePos = 0;\r\nd.tableCount = 0;\r\nd.envInfoString = \"Envelope\";\r\n// clipboards\r\nd.clipboards = ();\r\nd.clipboardIndex = 0;\r\n// env points\r\nd.minPoints = 3;\r\nd.maxPoints = 16;\r\nd.lastPointEqualsFirst = true;\r\nd.hidePoints = false;\r\nd.smoothType = 0;\r\nd.arrCurveTypeOptions = [\r\n\t[\"Sine\", \\sine],\r\n\t[\"Curve\", \\curve],\r\n\t[\"Linear\", \\linear],\r\n\t[\"Step\", \\step],\r\n\t[\"Hold\", \\hold],\r\n\t[\"Exponential\", \\exp],\r\n\t[\"Welch\", \\welch],\r\n];\r\nd.curveSpec = ControlSpec(-20, 20, step: 0.01);\r\n// presets\r\nd.presetIndex = 0;\r\nd.presets = [  // all presets use Mode: Sines\r\n\t[\"- Select a preset to load -\", {}],\r\n\t[\"Sine wave (default)\", {\r\n\t\td.arrFreqs = d.maxSines.collect({arg i; i + 1});\r\n\t\td.arrLevels = [1] ++ (0 ! (d.maxSines - 1));\r\n\t\td.arrPhases = 0 ! d.maxSines;\r\n\t}],\r\n\t[\"Sawtooth wave using Sines\", {\r\n\t\td.arrFreqs = d.maxSines.collect({arg i; i + 1});\r\n\t\td.arrLevels = d.maxSines.collect({arg i; (i + 1).reciprocal});\r\n\t\td.arrPhases = 0 ! d.maxSines;\r\n\t}],\r\n\t[\"Square wave using Sines\", {\r\n\t\td.arrFreqs = d.maxSines.collect({arg i; i + 1});\r\n\t\td.arrLevels = d.maxSines.collect({arg i;\r\n\t\t\ti.even.binaryValue * (i + 1).reciprocal});\r\n\t\td.arrPhases = 0 ! d.maxSines;\r\n\t}],\r\n\t[\"Triangle wave using Sines\", {\r\n\t\td.arrFreqs = d.maxSines.collect({arg i; i + 1});\r\n\t\td.arrLevels = d.maxSines.collect({arg i;\r\n\t\t\ti.even.binaryValue * (i + 1).reciprocal.squared});\r\n\t\td.arrPhases = d.maxSines.collect({arg i;\r\n\t\t\t0.5 * ((i % 4) == 2).binaryValue;});\r\n\t}],\r\n\t[\"Impulse wave using Sines\", {\r\n\t\td.arrFreqs = d.maxSines.collect({arg i; i + 1});\r\n\t\td.arrLevels = 1 ! d.maxSines;\r\n\t\td.arrPhases = 0 ! d.maxSines;\r\n\t}],\r\n];\r\n\r\n// ============ create all functions ============\r\n\r\nd.loadPreset = {\r\n\td.envMode = false;  // all presets use Mode: Sines\r\n\td.presets[d.presetIndex][1].value;\r\n\td.updateBufGui;\r\n};\r\nd.setDefaultFormat = {\r\n\td.bufLength = d.defaultBufLength;\r\n\td.saveFormatIndex = d.defaultSaveFormatIndex;\r\n\td.allocUpdateBuffers;\r\n\td.deferMakeGui;\r\n};\r\nd.setDefaultSineArrays = {\r\n\td.arrFreqs = d.maxSines.collect({arg i; i + 1});\r\n\td.arrLevels = [1] ++ (0 ! (d.maxSines - 1));\r\n\td.arrPhases = 0 ! d.maxSines;\r\n};\r\nd.setDefaultPoints = {\r\n\td.arrPoints = [Point(0, 0), Point(0.5, 1), Point(1, 0), ];\r\n\td.arrCurveTypes = 0 ! (d.arrPoints.size - 1);  // default type = Sine\r\n\td.arrCurveVals = 0 ! (d.arrPoints.size - 1);\r\n};\r\nd.setLastPointToFirst = {\r\n\td.arrPoints[d.arrPoints.size - 1].y = d.arrPoints[0].y;\r\n};\r\n// buffers\r\nd.allocateBuffers = {\r\n\tif (d.wavetableBuf.notNil, {d.wavetableBuf.free});\r\n\tif (d.multiWTBufs.notNil, {16.do({arg i; d.multiWTBufs[i].free});});\r\n\tif (d.signalBuf.notNil, {d.signalBuf.free});\r\n\td.wavetableBuf = Buffer.alloc(s, d.bufLength * 2, 1);\r\n\td.multiWTBufs = Buffer.allocConsecutive(16, s, d.bufLength * 2, 1);\r\n\td.signalBuf = Buffer.alloc(s, d.bufLength, 1);\r\n};\r\nd.updateBuffers = {\r\n\tvar points, envSize, envSignal, envWavetable, total, holdArr, width;\r\n\tforkIfNeeded {\r\n\t\ts.sync;\r\n\t\tif (d.envMode, {\r\n\t\t\tif (d.lastPointEqualsFirst, {\r\n\t\t\t\tenvSize = d.bufLength + 1; // last sample is trimmed\r\n\t\t\t}, {\r\n\t\t\t\tenvSize = d.bufLength;\r\n\t\t\t});\r\n\t\t\tpoints = d.arrPoints;\r\n\t\t\tenvSignal = Env(\r\n\t\t\t\tpoints.collect({arg item; item.y}) + 0.001,\r\n\t\t\t\tpoints.collect({arg item; item.x}).differentiate.keep(1 - points.size),\r\n\t\t\t\td.getArrPointCurves,\r\n\t\t\t\toffset: -0.001;\r\n\t\t\t).asSignal(envSize);\r\n\t\t\tenvSignal = envSignal.keep(d.bufLength.asInteger); // trim to buf length\r\n\t\t\t// smoothing\r\n\t\t\tif (d.smoothType > 0, {\r\n\t\t\t\twidth = d.smoothType * 3;\r\n\t\t\t\tholdArr = envSignal.as(Array);\r\n\t\t\t\tholdArr = holdArr.collect({arg item, i;\r\n\t\t\t\t\tholdArr.wrapAt(i + (width.neg..width)).mean;\r\n\t\t\t\t});\r\n\t\t\t\tenvSignal = holdArr.as(Signal);\r\n\t\t\t});\r\n\t\t\tenvSignal.scale(2); // scale and offset to range -1/1\r\n\t\t\tenvSignal.offset(-1);\r\n\t\t\tenvSignal.clip(-1, 1);\r\n\t\t\tenvWavetable = envSignal.asWavetable;\r\n\t\t\t// d.signalBuf.loadCollection(envSignal);\r\n\t\t\td.signalBuf.sendCollection(envSignal);\r\n\t\t\t// d.wavetableBuf.loadCollection(envWavetable);\r\n\t\t\td.wavetableBuf.sendCollection(envWavetable);\r\n\t\t\td.currentWaveformData = envSignal.as(FloatArray);\r\n\t\t},{\r\n\t\t\ttotal = d.numSines;\r\n\t\t\td.wavetableBuf.sine3(d.arrFreqs.keep(total),\r\n\t\t\t\td.arrLevels.keep(total),\r\n\t\t\t\td.arrPhases.keep(total) * 2pi,  // scale phase\r\n\t\t\t\tasWavetable: true);\r\n\t\t\td.signalBuf.sine3(d.arrFreqs.keep(total),\r\n\t\t\t\td.arrLevels.keep(total),\r\n\t\t\t\td.arrPhases.keep(total) * 2pi,\r\n\t\t\t\tasWavetable: false);\r\n\t\t\ts.sync;\r\n\t\t\t// d.signalBuf.loadToFloatArray(action: {arg array;\r\n\t\t\t// \td.currentWaveformData = array;\r\n\t\t\t// });\r\n\t\t\td.signalBuf.getToFloatArray(action: {arg array;\r\n\t\t\t\td.currentWaveformData = array;\r\n\t\t\t});\r\n\t\t});\r\n\t}; // end of forkIfNeeded\r\n};\r\nd.updateMultiWTBufs = {\r\n\tvar outArray, outSoundFile, waveformArr, wavetable, storeInd;\r\n\tforkIfNeeded {\r\n\t\ts.sync;\r\n\t\t16.do({arg i;    // zero all buffers first\r\n\t\t\td.multiWTBufs[i].zero;\r\n\t\t});\r\n\t\ts.sync;\r\n\t\tstoreInd = 0;\r\n\t\t16.do({arg i;\r\n\t\t\tif (d.clipboards[i].notNil, {\r\n\t\t\t\twaveformArr = d.clipboards[i].waveformData;\r\n\t\t\t\tif (waveformArr.size != d.bufLength, {\r\n\t\t\t\t\twaveformArr = waveformArr.resamp1(d.bufLength);\r\n\t\t\t\t});\r\n\t\t\t\twavetable = waveformArr.as(Signal).asWavetable;\r\n\t\t\t\t// d.multiWTBufs[storeInd].loadCollection(wavetable);\r\n\t\t\t\td.multiWTBufs[storeInd].sendCollection(wavetable);\r\n\t\t\t\tstoreInd = storeInd + 1;\r\n\t\t\t});\r\n\t\t});\r\n\t\ts.sync;\r\n\t\tif (d.multiSynthNode.notNil, {\r\n\t\t\tif (d.tableCount > 1, {\r\n\t\t\t\td.rebuildSynth;\r\n\t\t\t}, {\r\n\t\t\t\td.stopSynth;\r\n\t\t\t});\r\n\t\t});\r\n\t}; // end of forkIfNeeded\r\n};\r\nd.allocUpdateBuffers = {\r\n\tforkIfNeeded {\r\n\t\ts.sync;\r\n\t\td.allocateBuffers;\r\n\t\ts.sync;\r\n\t\td.updateBuffers;\r\n\t\td.updateMultiWTBufs;\r\n\t};\r\n};\r\nd.updateBufGui = {\r\n\tforkIfNeeded {\r\n\t\td.updateBuffers;\r\n\t\td.deferMakeGui;\r\n\t};\r\n};\r\nd.updatePlayButtons = {\r\n\t{\r\n\t\tif (guiData.playWaveformBtn.notNil and: {guiData.playWaveformBtn.notClosed}, {\r\n\t\t\tif (d.synthNode.notNil, {\r\n\t\t\t\tguiData.playWaveformBtn.background_(d.colPlayActive);\r\n\t\t\t}, {\r\n\t\t\t\tguiData.playWaveformBtn.background_(d.colPlay);\r\n\t\t\t});\r\n\t\t});\r\n\t\tif (guiData.playWavetableBtn.notNil and: {guiData.playWavetableBtn.notClosed}, {\r\n\t\t\t\tif (d.multiSynthNode.notNil, {\r\n\t\t\t\tguiData.playWavetableBtn.background_(d.colPlayActive);\r\n\t\t\t}, {\r\n\t\t\t\tguiData.playWavetableBtn.background_(d.colPlay);\r\n\t\t\t});\r\n\t\t});\r\n\t}.defer;\r\n};\r\n// clipboards\r\nd.storeClip = {\r\n\tvar clip = ();\r\n\tclip.envMode = d.envMode.copy;\r\n\tclip.waveformData = d.currentWaveformData;\r\n\tif (d.envMode, {\r\n\t\tclip.smoothType = d.smoothType.copy;\r\n\t\tclip.arrPoints = d.arrPoints.deepCopy;\r\n\t\tclip.arrCurveTypes = d.arrCurveTypes.deepCopy;\r\n\t\tclip.arrCurveVals = d.arrCurveVals.deepCopy;\r\n\t}, {\r\n\t\tclip.numSines = d.numSines.copy;\r\n\t\tclip.arrFreqs = d.arrFreqs.deepCopy;\r\n\t\tclip.arrLevels = d.arrLevels.deepCopy;\r\n\t\tclip.arrPhases = d.arrPhases.deepCopy;\r\n\t});\r\n\td.clipboards[d.clipboardIndex] = clip;\r\n\td.tableCount = (d.tableCount + 1).min(16);\r\n\td.updateMultiWTBufs;\r\n\td.deferMakeGui;\r\n};\r\nd.loadClip = {\r\n\tvar clip = d.clipboards[d.clipboardIndex];\r\n\tif (clip.notNil, {\r\n\t\td.envMode = clip.envMode;\r\n\t\tif (d.envMode, {\r\n\t\t\td.smoothType = clip.smoothType.copy;\r\n\t\t\td.arrPoints = clip.arrPoints.deepCopy;\r\n\t\t\td.arrCurveTypes = clip.arrCurveTypes.deepCopy;\r\n\t\t\td.arrCurveVals = clip.arrCurveVals.deepCopy;\r\n\t\t}, {\r\n\t\t\td.numSines = clip.numSines.copy;\r\n\t\t\td.arrFreqs = clip.arrFreqs.deepCopy;\r\n\t\t\td.arrLevels = clip.arrLevels.deepCopy;\r\n\t\t\td.arrPhases = clip.arrPhases.deepCopy;\r\n\t\t});\r\n\t\td.updateBufGui;\r\n\t});\r\n};\r\nd.clearClip = {\r\n\td.clipboards[d.clipboardIndex] = nil;\r\n\td.tableCount = (d.tableCount - 1).max(0);\r\n\td.updateMultiWTBufs;\r\n\td.deferMakeGui;\r\n};\r\nd.clearAllClipboards = {\r\n\td.clipboards = ();\r\n\td.tableCount = 0;\r\n\td.updateMultiWTBufs;\r\n\td.deferMakeGui;\r\n};\r\n// sines waveform plot\r\nd.updatePlot = {\r\n\tif (d.envMode.not, {\r\n\t\tforkIfNeeded {\r\n\t\t\ts.sync;\r\n\t\t\t{\tguiData.plotView.refresh;\r\n\t\t\t\tif (guiData.plotView.notNil, {\r\n\t\t\t\t\tguiData.plotArray = d.currentWaveformData;\r\n\t\t\t\t\tguiData.plotView.refresh;\r\n\t\t\t\t});\r\n\t\t\t}.defer;\r\n\t\t};\r\n\t});\r\n};\r\nd.updateBufPlot = {\r\n\tforkIfNeeded {\r\n\t\ts.sync;\r\n\t\td.updateBuffers;\r\n\t\ts.sync;\r\n\t\td.updatePlot;\r\n\t};\r\n};\r\n// synth funcs\r\nd.playWaveSynth = {\r\n\td.stopSynth;\r\n\td.synthNode = SynthDef(\\BufPlay, {\r\n\t\targ playFreq = 220, outLevel = 0.1, gate = 1;\r\n\t\tOut.ar(d.audioOutBus, ([\r\n\t\t\t// static\r\n\t\t\tEnv.asr(0.5, 0.5, 0.5).kr(2, gate) * outLevel,\r\n\t\t\t// pulsing\r\n\t\t\tEnv.asr(0.5, 0.5, 0.5).kr(2, gate) * outLevel\r\n\t\t\t* LFTri.kr(0.5, 0.0).range(0.1, 1)\r\n\t\t] [d.pulsing.binaryValue]\r\n\t\t* LeakDC.ar(Osc.ar(d.wavetableBuf, playFreq, 0, 0.3)))\r\n\t\t! 2  // stereo\r\n\t)}).play(s, [\\playFreq, d.playFreq, \\outLevel, d.outLevel]);\r\n};\r\nd.playMultiWaveSynth = {\r\n\td.stopSynth;\r\n\tif (d.tableCount > 1, {\r\n\t\td.multiSynthNode = SynthDef(\\MultiBufPlay, {\r\n\t\t\targ playFreq = 220, tablePos = 0, outLevel = 0.1, gate = 1;\r\n\t\t\tvar outTablePos = d.multiWTBufs[0].bufnum + (tablePos * (d.tableCount - 1.01));\r\n\t\t\tOut.ar(d.audioOutBus, ([\r\n\t\t\t\t// static\r\n\t\t\t\tEnv.asr(0.5, 0.5, 0.5).kr(2, gate) * outLevel,\r\n\t\t\t\t// pulsing\r\n\t\t\t\tEnv.asr(0.5, 0.5, 0.5).kr(2, gate) * outLevel\r\n\t\t\t\t* LFTri.kr(0.5, 0.0).range(0.1, 1)\r\n\t\t\t] [d.pulsing.binaryValue]\r\n\t\t\t* LeakDC.ar(VOsc.ar(outTablePos, playFreq, 0, 0.3)))\r\n\t\t\t! 2  // stereo\r\n\t\t)}).play(s, [\\playFreq, d.playFreq, \\tablePos, d.tablePos, \\outLevel, d.outLevel]);\r\n\t});\r\n};\r\nd.stopSynth = {\r\n\tif (d.synthNode.notNil, {d.synthNode.set(\\gate, 0); d.synthNode = nil;});\r\n\tif (d.multiSynthNode.notNil, {d.multiSynthNode.set(\\gate, 0); d.multiSynthNode = nil;});\r\n};\r\nd.rebuildSynth = {\r\n\tif (d.synthNode.notNil, {\r\n\t\td.stopSynth;\r\n\t\t{d.playWaveSynth}.defer(0.1);\r\n\t}, {\r\n\t\tif (d.multiSynthNode.notNil, {\r\n\t\t\td.stopSynth;\r\n\t\t\t{d.playMultiWaveSynth}.defer(0.1);\r\n\t\t});\r\n\t});\r\n};\r\nd.freeAll = {  // free buffs & synths\r\n\tif (guiData.helpWindow.notNil, {guiData.helpWindow.close; guiData.helpWindow = nil});\r\n\tif (d.synthNode.notNil, {d.synthNode.free; d.synthNode = nil});\r\n\tif (d.multiSynthNode.notNil, {d.multiSynthNode.free; d.multiSynthNode = nil});\r\n\tif (d.wavetableBuf.notNil, {d.wavetableBuf.free; d.wavetableBuf = nil});\r\n\tif (d.multiWTBufs.notNil, {16.do({arg i; d.multiWTBufs[i].free}); d.multiWTBufs = nil});\r\n\tif (d.signalBuf.notNil, {d.signalBuf.free; d.signalBuf = nil});\r\n};\r\n// save funcs\r\nd.saveWaveform = {\r\n\tDialog.savePanel({ arg path;\r\n\t\tvar headerFormat = d.arrSaveFormats[d.saveFormatIndex][1];\r\n\t\tvar sampleFormat = d.arrSaveFormats[d.saveFormatIndex][2];\r\n\t\tvar pathName, filename, fileExt, outPath;\r\n\t\tpathName = PathName(path);\r\n\t\tfilename = pathName.fileNameWithoutExtension;\r\n\r\n\t\t[\"pathName.realEndNumber\", pathName.realEndNumber].postcs;\r\n\r\n\r\n\t\tif (d.addBufLengthToFilename\r\n\t\t\tand: {pathName.realEndNumber != d.bufLength}, {\r\n\t\t\t\tfilename = filename ++ \"_\" ++ d.bufLength.asString;\r\n\t\t});\r\n\t\tif (d.saveFormatIndex <= 3, {\r\n\t\t\tfileExt = \".wav\";\r\n\t\t}, {\r\n\t\t\tfileExt = \".aif\";\r\n\t\t});\r\n\t\toutPath = pathName.pathOnly +/+ filename ++ fileExt;\r\n\t\t// save file\r\n\t\td.signalBuf.write(outPath, headerFormat, sampleFormat);\r\n\t\t\"// ==================================\".postln;\r\n\t\t\"// Waveform file saved:\".postln;\r\n\t\t(\"//\" + outPath).postln;\r\n\t\td.printSpecs;\r\n\t});\r\n};\r\nd.saveWavetable = {\r\n\tif (d.tableCount > 1, {\r\n\t\tDialog.savePanel({ arg path;\r\n\t\t\tvar headerFormat = d.arrSaveFormats[d.saveFormatIndex][1];\r\n\t\t\tvar sampleFormat = d.arrSaveFormats[d.saveFormatIndex][2];\r\n\t\t\tvar outArray, outSoundFile, waveform;\r\n\t\t\tvar pathName, filename, fileExt, outPath;\r\n\t\t\tpathName = PathName(path);\r\n\t\t\tfilename = pathName.fileNameWithoutExtension;\r\n\t\t\tif (d.addBufLengthToFilename\r\n\t\t\t\tand: {pathName.realEndNumber != d.bufLength}, {\r\n\t\t\t\t\tfilename = filename ++ \"_\" ++ d.bufLength.asString;\r\n\t\t\t});\r\n\t\t\tif (d.saveFormatIndex <= 3, {\r\n\t\t\t\tfileExt = \".wav\";\r\n\t\t\t}, {\r\n\t\t\t\tfileExt = \".aif\";\r\n\t\t\t});\r\n\t\t\toutPath = pathName.pathOnly +/+ filename ++ fileExt;\r\n\t\t\t// build wavetable\r\n\t\t\toutArray = FloatArray(d.tableCount * d.bufLength);\r\n\t\t\t16.do({arg i;\r\n\t\t\t\tif (d.clipboards[i].notNil, {\r\n\t\t\t\t\twaveform = d.clipboards[i].waveformData;\r\n\t\t\t\t\tif (waveform.size != d.bufLength, {\r\n\t\t\t\t\t\twaveform = waveform.resamp1(d.bufLength);\r\n\t\t\t\t\t});\r\n\t\t\t\t\toutArray = outArray.addAll(waveform);\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t\t// save file\r\n\t\t\toutSoundFile = SoundFile.new.numChannels_(1)\r\n\t\t\t\t.headerFormat_(headerFormat).sampleFormat_(sampleFormat);\r\n\t\t\toutSoundFile.openWrite(outPath);\r\n\t\t\toutSoundFile.writeData(outArray);\r\n\t\t\toutSoundFile.close;\r\n\r\n\t\t\t\"// ==================================\".postln;\r\n\t\t\t(\"// Wavetable file (\" ++ d.tableCount ++ \" waveforms) saved:\").postln;\r\n\t\t\t(\"//\" + outPath).postln;\r\n\t\t\t(\"// File Format:\" + d.arrSaveFormats[d.saveFormatIndex][0]).postln;\r\n\t\t\t\"// ==================================\".postln;\r\n\t\t});\r\n\t}, {\r\n\t\t \"Cannot save Wavetable - at least 2 stored clipboards are needed.\".postln;\r\n\t});\r\n};\r\nd.printSpecs = {\r\n\tvar smoothString;\r\n\t\"// ==================================\".postln;\r\n\t\"// - Single-Cycle Waveform Settings -\".postln;\r\n\t(\"// Waveform Length:\" + d.bufLength + \"samples\").postln;\r\n\t(\"// File Format:\" + d.arrSaveFormats[d.saveFormatIndex][0]).postln;\r\n\tif (d.envMode, {\r\n\t\t(\"// Envelope with\" + d.arrPoints.size + \"points\").postln;\r\n\t\tif (d.smoothType == 0, {\r\n\t\t\tsmoothString = \"Off\";\r\n\t\t}, {\r\n\t\t\tsmoothString = (d.smoothType * 3) + \"samples\";\r\n\t\t});\r\n\t\t(\"// Smoothing:\" + smoothString).postln;\r\n\t\t\"// SC Env Levels: \".postln;\r\n\t\td.arrPoints.collect({arg item; item.y.linlin(0, 1, -1, 1)}).postcs;\r\n\t\t\"// SC Env Times:\".postln;\r\n\t\td.arrPoints.collect({arg item; item.x}).differentiate.keep(1 - d.arrPoints.size).postcs;\r\n\t\t\"// SC Env Curves:\".postln;\r\n\t\td.arrCurveTypes.collect({arg item, i;\r\n\t\t\tvar curve;\r\n\t\t\tcurve = d.arrCurveTypeOptions[item][1];\r\n\t\t\tif (curve == \\curve, {\r\n\t\t\t\tcurve = d.arrCurveVals[i];\r\n\t\t\t});\r\n\t\t\tcurve;\r\n\t\t}).postcs;\r\n\t}, {\r\n\t\t(\"// Using\" + d.numSines + \"Sines\").postln;\r\n\t\t\"// Sine Freqs:\".postln;\r\n\t\td.arrFreqs.keep(d.numSines).postcs;\r\n\t\t\"// Levels: \".postln;\r\n\t\td.arrLevels.keep(d.numSines).postcs;\r\n\t\t\"// Phases: (range 0 : 1)\".postln;\r\n\t\td.arrPhases.keep(d.numSines).postcs;\r\n\t\t\"// Phases: (range 0 : 2pi)\".postln;\r\n\t\t(d.arrPhases.keep(d.numSines) * 2pi).postcs;\r\n\t});\r\n\t\"// ==================================\".postln;\r\n};\r\n// envelope funcs\r\nd.addNewPoint = {\r\n\tvar newIndex = nil;\r\n\tvar times;\r\n\t// only add if enough space\r\n\tif (d.arrPoints.size < d.maxPoints, {\r\n\t\ttimes = d.arrPoints.collect({arg item; item.x});\r\n\t\tnewIndex = times.indexOfGreaterThan(guiData.newPoint.x);\r\n\t\tif (newIndex.notNil, {\r\n\t\t\td.arrPoints = d.arrPoints.insert(newIndex, guiData.newPoint);\r\n\t\t\td.arrCurveTypes = d.arrCurveTypes.insert(newIndex, 0);\r\n\t\t\td.arrCurveVals = d.arrCurveVals.insert(newIndex, 0);\r\n\t\t}, {\r\n\t\t\td.arrPoints = d.arrPoints.add(guiData.newPoint);\r\n\t\t\td.arrCurveTypes = d.arrCurveTypes.add(0);\r\n\t\t\td.arrCurveVals = d.arrCurveVals.add(0);\r\n\t\t\tnewIndex = d.arrPoints.size - 1;\r\n\t\t});\r\n\t});\r\n\tnewIndex; // return index\r\n};\r\nd.deletePoint = {\r\n\tif (d.deletePointIndex.notNil, {\r\n\t\td.arrPoints.removeAt(d.deletePointIndex);\r\n\t\td.arrCurveTypes.removeAt(d.deletePointIndex);\r\n\t\td.arrCurveVals.removeAt(d.deletePointIndex);\r\n\t\td.deletePointIndex = nil;\r\n\t});\r\n};\r\nd.refreshPointsView = {\r\n\tif (guiData.envView.notNil and: {guiData.envView.notClosed}, {\r\n\t\tguiData.envView.refresh;\r\n\t});\r\n};\r\nd.getArrPointCurves = {\r\n\td.arrCurveTypes.collect({arg item, i;\r\n\t\tvar curveType = d.arrCurveTypeOptions[item][1];\r\n\t\tif (curveType == \\curve, {\r\n\t\t\tcurveType = d.arrCurveVals[i];\r\n\t\t});\r\n\t\tcurveType;\r\n\t});\r\n};\r\nd.updateEnvInfoView = {\r\n\tvar point;\r\n\tif (guiData.mouseMode == \\dragPoint, {\r\n\t\tpoint = d.arrPoints[guiData.dragPointInd];\r\n\t\td.envInfoString = \"Pt\" + (guiData.dragPointInd + 1)\r\n\t\t+ \"  X:\" + point.x.round(0.001) + \"  Y:\" + point.y.round(0.001);\r\n\t}, {\r\n\t\td.envInfoString = \"Envelope\";\r\n\t});\r\n\tguiData.envInfoView.string = d.envInfoString;\r\n};\r\n// env actions\r\nd.arrEnvActions = [\r\n\t[\"- Actions -\", {}],\r\n\t[\"Copy type from point 1 to all points\",\r\n\t{d.arrCurveTypes = d.arrCurveTypes[0] ! d.arrCurveTypes.size;}],\r\n\t[\"Copy curve from point 1 to all points\",\r\n\t{d.arrCurveVals = d.arrCurveVals[0] ! d.arrCurveVals.size;}],\r\n\t[\"Disturb points\",\r\n\t{\r\n\t\tvar size = d.arrPoints.size;\r\n\t\tvar order;\r\n\t\td.arrPoints = d.arrPoints.collect({arg item, i;\r\n\t\t\tvar pt;\r\n\t\t\tif (i == 0 or: {i == (size - 1)}, {\r\n\t\t\t\tpt = Point(  // add 6% randomness\r\n\t\t\t\t\titem.x,\r\n\t\t\t\t\t(item.y + 0.03.rand2).clip(0.0, 1.0),\r\n\t\t\t\t)\r\n\t\t\t}, {\r\n\t\t\t\tpt = Point(  // add 6% randomness\r\n\t\t\t\t\t(item.x + 0.03.rand2).clip(0.001, 0.999),\r\n\t\t\t\t\t(item.y + 0.03.rand2).clip(0.0, 1.0),\r\n\t\t\t\t)\r\n\t\t\t});\r\n\t\t\tpt;\r\n\t\t});\r\n\t\t// sort\r\n\t\torder = d.arrPoints.order({ arg a, b; a.x < b.x });\r\n\t\td.arrPoints = d.arrPoints.atAll(order);\r\n\t\td.arrCurveTypes = d.arrCurveTypes.atAll(order.keep(d.arrCurveTypes.size));\r\n\t\td.arrCurveVals = d.arrCurveVals.atAll(order.keep(d.arrCurveVals.size));\r\n\t\tif (d.lastPointEqualsFirst, {d.setLastPointToFirst});\r\n\t}],\r\n\t[\"Normalise envelope\",\r\n\t{\tvar arrLevels = d.arrPoints.collect({arg item, i; item.y}).normalize;\r\n\t\tarrLevels.do({arg item, i; d.arrPoints[i].y = item});\r\n\t}],\r\n\t[\"Quantise curve values\",\r\n\t{d.arrCurveVals = d.arrCurveVals.round(1);}],\r\n\t[\"Random envelope with 8 points\",\r\n\t{\tvar size = 8;\r\n\t\tvar numOptions = d.arrCurveTypeOptions.size;\r\n\t\tvar arrTimes = {rrand(0.001, 0.999)} ! size;\r\n\t\tvar arrLevels = {rrand(0.0, 1.0)} ! size;\r\n\t\tarrTimes[0] = 0;\r\n\t\tarrTimes[size - 1] = 1;\r\n\t\tif (d.lastPointEqualsFirst, {\r\n\t\t\tarrLevels[size - 1] = arrLevels[0];\r\n\t\t});\r\n\t\tarrLevels = arrLevels.normalize;\r\n\t\td.arrPoints = arrTimes.collect({arg item, i; Point(item, arrLevels[i])});\r\n\t\td.arrPoints = d.arrPoints.sort({ arg a, b; a.x < b.x });\r\n\t\td.arrCurveTypes = 0 ! (size - 1);\r\n\t\td.arrCurveVals = 0 ! (size - 1);\r\n\t}],\r\n\t[\"Random envelope with 16 points\",\r\n\t{\tvar size = 16;\r\n\t\tvar numOptions = d.arrCurveTypeOptions.size;\r\n\t\tvar arrTimes = {rrand(0.001, 0.999)} ! size;\r\n\t\tvar arrLevels = {rrand(0.0, 1.0)} ! size;\r\n\t\tarrTimes[0] = 0;\r\n\t\tarrTimes[size - 1] = 1;\r\n\t\tif (d.lastPointEqualsFirst, {\r\n\t\t\tarrLevels[size - 1] = arrLevels[0];\r\n\t\t});\r\n\t\tarrLevels = arrLevels.normalize;\r\n\t\td.arrPoints = arrTimes.collect({arg item, i; Point(item, arrLevels[i])});\r\n\t\td.arrPoints = d.arrPoints.sort({ arg a, b; a.x < b.x });\r\n\t\td.arrCurveTypes = 0 ! (size - 1);\r\n\t\td.arrCurveVals = 0 ! (size - 1);\r\n\t}],\r\n\t[\"Randomise points\",\r\n\t{\r\n\t\tvar size = d.arrPoints.size;\r\n\t\tvar arrTimes = {rrand(0.001, 0.999)} ! size;\r\n\t\tvar arrLevels = {rrand(0.0, 1.0)} ! size;\r\n\t\tarrTimes[0] = 0;\r\n\t\tarrTimes[size - 1] = 1;\r\n\t\tif (d.lastPointEqualsFirst, {\r\n\t\t\tarrLevels[size - 1] = arrLevels[0];\r\n\t\t});\r\n\t\tarrLevels = arrLevels.normalize;\r\n\t\td.arrPoints = arrTimes.collect({arg item, i; Point(item, arrLevels[i])});\r\n\t\td.arrPoints = d.arrPoints.sort({ arg a, b; a.x < b.x });\r\n\t}],\r\n\t[\"Randomise types\",\r\n\t{\tvar size = d.arrCurveTypes.size;\r\n\t\tvar numOptions = d.arrCurveTypeOptions.size;\r\n\t\td.arrCurveTypes = {numOptions.rand} ! size;\r\n\t}],\r\n\t[\"Randomise curves\",\r\n\t{\tvar size = d.arrCurveVals.size;\r\n\t\t// d.arrCurveVals = {rrand(-20.0, 20.0)} ! size;\r\n\t\t// d.arrCurveVals = {20.0.sum3rand} ! size;\r\n\t\td.arrCurveVals = {1.0.rand.squared * [20.0, -20.0].choose} ! size;\r\n\t}],\r\n\t[\"Reset curves to 0\",\r\n\t{d.arrCurveVals = 0 ! d.arrCurveVals.size;}],\r\n\t[\"Reset envelope to Sine wave\",\r\n\t{d.setDefaultPoints}],\r\n\t[\"Reset types to Sine\",\r\n\t{\td.arrCurveTypes = 0 ! d.arrCurveTypes.size;}],\r\n\t[\"Scramble envelope\",\r\n\t{\r\n\t\tvar size = d.arrPoints.size;\r\n\t\tvar order, arrLevels, arrTimeGaps, arrTimes;\r\n\t\tif (size > 3, {\r\n\t\t\t//\tScramble points\r\n\t\t\tarrTimeGaps = d.arrPoints.atAll((1..(size - 1))).collect({arg item; item.x}).differentiate;\r\n\t\t\tarrTimeGaps = arrTimeGaps.scramble;\r\n\t\t\tarrTimes = ([0] ++ arrTimeGaps).integrate;\r\n\t\t\tarrTimes.do({arg item, i; d.arrPoints[i].x = item});\r\n\t\t\t// sort\r\n\t\t\td.arrPoints = d.arrPoints.sort({ arg a, b; a.x < b.x });\r\n\t\t\tif (d.lastPointEqualsFirst, {\r\n\t\t\t\tarrLevels = d.arrPoints.atAll((0..(size - 2))).collect({arg item; item.y})\r\n\t\t\t}, {\r\n\t\t\t\tarrLevels = d.arrPoints.collect({arg item; item.y})\r\n\t\t\t});\r\n\t\t\tarrLevels = arrLevels.scramble;\r\n\t\t\tarrLevels.do({arg item, i; d.arrPoints[i].y = item});\r\n\t\t\t//\tScramble types and curves\r\n\t\t\torder = Array.iota(d.arrCurveTypes.size).scramble;\r\n\t\t\td.arrCurveTypes = d.arrCurveTypes.atAll(order);\r\n\t\t\td.arrCurveVals = d.arrCurveVals.atAll(order);\r\n\t\t\tif (d.lastPointEqualsFirst, {d.setLastPointToFirst});\r\n\t\t});\r\n\t}],\r\n\t[\"Scramble types and curves\",\r\n\t{\tvar order = Array.iota(d.arrCurveTypes.size).scramble;\r\n\t\td.arrCurveTypes = d.arrCurveTypes.atAll(order);\r\n\t\td.arrCurveVals = d.arrCurveVals.atAll(order);\r\n\t}],\r\n];\r\nd.runEnvAction = {\r\n\td.arrEnvActions.flop.slice(1).at(guiData.envActionInd).value;\r\n};\r\n// Freq actions\r\nd.arrFreqActions = [\r\n\t\t[\"- Actions -\",\r\n\t\t{}],\r\n\t\t[\"Reset\",\r\n\t\t{d.arrFreqs = d.maxSines.collect({arg i; i + 1});}],\r\n\t\t[\"Disturb\",\r\n\t\t{d.arrFreqs = (d.arrFreqs + ({0.25.rand2} ! d.maxSines)).clip(0.1, d.maxSineFreq);}],\r\n\t\t[\"Randomise\",\r\n\t\t{d.arrFreqs = {d.minSineFreq.rrand(d.maxSineFreq.asFloat)} ! d.maxSines;}],\r\n\t\t[\"Scramble\",\r\n\t\t{\tvar newArr = d.arrFreqs.keep(d.numSines);\r\n\t\t\tnewArr = newArr.scramble;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrFreqs[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Sort <\",\r\n\t\t{\tvar newArr = d.arrFreqs.keep(d.numSines);\r\n\t\t\tnewArr = newArr.sort;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrFreqs[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Quantise\",\r\n\t\t{\td.arrFreqs = d.arrFreqs.round.max(1);}],\r\n];\r\nd.runFreqAction = {\r\n\td.arrFreqActions.flop.slice(1).at(guiData.holdActionInd).value;\r\n};\r\n// Level actions\r\nd.arrLevelActions = [\r\n\t\t[\"- Actions -\",\r\n\t\t{}],\r\n\t\t[\"Reset\",\r\n\t\t{d.arrLevels = [1] ++ (0 ! (d.maxSines - 1));}],\r\n\t\t[\"Disturb\",\r\n\t\t{d.arrLevels = (d.arrLevels + ({0.05.rand2} ! d.maxSines)).clip(0, 1);}],\r\n\t\t[\"Randomise\",\r\n\t\t{d.arrLevels = {1.0.rand} ! d.maxSines;}],\r\n\t\t[\"Scramble\",\r\n\t\t{\tvar newArr = d.arrLevels.keep(d.numSines);\r\n\t\t\tnewArr = newArr.scramble;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrLevels[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Scramble > 1\",\r\n\t\t{\tvar newArr = d.arrLevels.keep(d.numSines);\r\n\t\t\tnewArr = [newArr[0]] ++  newArr.keep(1 - newArr.size).scramble;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrLevels[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Reverse\",\r\n\t\t{\tvar newArr = d.arrLevels.keep(d.numSines);\r\n\t\t\tnewArr = newArr.reverse;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrLevels[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Invert\",\r\n\t\t{\tvar newArr = d.arrLevels.keep(d.numSines);\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrLevels[i] = 1 - item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Sort  >\",\r\n\t\t{\tvar newArr = d.arrLevels.keep(d.numSines);\r\n\t\t\tnewArr = newArr.sort.reverse;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrLevels[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Bias Up\",\r\n\t\t{d.arrLevels = d.arrLevels.collect({arg item, i; item.lincurve(0, 1, 0, 1, -1)});}],\r\n\t\t[\"Bias Down\",\r\n\t\t{d.arrLevels = d.arrLevels.collect({arg item, i; item.lincurve(0, 1, 0, 1, 1)});}],\r\n\t\t[\"Bias 1/N\",\r\n\t\t{d.arrLevels = d.arrLevels.collect({arg item, i; item.blend(item / (i + 1), 0.5)});}],\r\n\t\t[\"Bias 1/N.squared\",\r\n\t\t{d.arrLevels = d.arrLevels.collect({arg item, i; item.blend(item / (i + 1).squared, 0.5)});}],\r\n\t\t[\"Zero if even\",\r\n\t\t{d.arrLevels = d.arrLevels.collect({arg item, i; if (i.odd, 0, item)});}],\r\n\t\t[\"Zero odd > 1\",\r\n\t\t{d.arrLevels = d.arrLevels.collect({arg item, i; if (i > 0 and: {i.even}, 0, item)});}],\r\n\t\t[\"Copy 1-> all\",\r\n\t\t{d.arrLevels = d.arrLevels[0] ! d.maxSines;}],\r\n\t\t[\"Normalise\",\r\n\t\t{\tvar newArr = d.arrLevels.keep(d.numSines);\r\n\t\t\tnewArr = newArr.normalize;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrLevels[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"1/N\",\r\n\t\t{d.arrLevels = d.arrLevels.size.collect({arg i; (i + 1).reciprocal});}],\r\n\t\t[\"1/N.squared\",\r\n\t\t{d.arrLevels = d.arrLevels.size.collect({arg i; (i + 1).squared.reciprocal});}],\r\n\r\n];\r\nd.runLevelAction = {\r\n\td.arrLevelActions.flop.slice(1).at(guiData.holdActionInd).value;\r\n};\r\n// Phase actions\r\nd.arrPhaseActions = [\r\n\t\t[\"- Actions -\",\r\n\t\t{}],\r\n\t\t[\"Reset\",\r\n\t\t{d.arrPhases = 0 ! d.maxSines;}],\r\n\t\t[\"Disturb\",\r\n\t\t{d.arrPhases = (d.arrPhases + ({0.05.rand2} ! d.maxSines)).clip(0, 1);}],\r\n\t\t[\"Randomise\",\r\n\t\t{d.arrPhases = {1.0.rand} ! d.maxSines;}],\r\n\t\t[\"Scramble\",\r\n\t\t{\tvar newArr = d.arrPhases.keep(d.numSines);\r\n\t\t\tnewArr = newArr.scramble;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrPhases[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Reverse\",\r\n\t\t{\tvar newArr = d.arrPhases.keep(d.numSines);\r\n\t\t\tnewArr = newArr.reverse;\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrPhases[i] = item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Invert\",\r\n\t\t{\tvar newArr = d.arrPhases.keep(d.numSines);\r\n\t\t\tnewArr.do({arg item, i;\r\n\t\t\t\td.arrPhases[i] = 1 - item;\r\n\t\t\t});\r\n\t\t}],\r\n\t\t[\"Phase shift 1/4\",\r\n\t\t{d.arrPhases = (d.arrPhases + 0.25).wrap(0, 1);}],\r\n\t\t[\"Phase shift 1/8\",\r\n\t\t{d.arrPhases = (d.arrPhases + 0.125).wrap(0, 1);}],\r\n\t\t[\"Quantise 1/4\",\r\n\t\t{d.arrPhases = d.arrPhases.round(0.25);}],\r\n\t\t[\"Quantise 1/8\",\r\n\t\t{d.arrPhases = d.arrPhases.round(0.125);}],\r\n\t\t[\"Copy 1-> all\",\r\n\t\t{d.arrPhases = d.arrPhases[0] ! d.maxSines;}],\r\n\t\t[\"Pattern A\",\r\n\t\t{d.arrPhases = d.maxSines.collect({arg i; [0, 0.5].wrapAt(i)});}],\r\n\t\t[\"Pattern B\",\r\n\t\t{d.arrPhases = d.maxSines.collect({arg i; [0, 0, 0.5, 0].wrapAt(i)});}],\r\n\t\t[\"Pattern C\",\r\n\t\t{d.arrPhases = d.maxSines.collect({arg i; [0, 0.25, 0.5, 0.75].wrapAt(i)});}],\r\n];\r\nd.runPhaseAction = {\r\n\td.arrPhaseActions.flop.slice(1).at(guiData.holdActionInd).value;\r\n};\r\n//\r\n// gui --- components are built before assembling layout\r\n//\r\nd.makeGui = {\r\n\tvar topRowViews, leftPanelLayout, w, pointWidth;\r\n\t// init\r\n\tw = guiData.window;  // store window if present\r\n\tguiData = ();  //  clear old data\r\n\tFont.default = Font(\"Helvetica\", 11);\r\n\tguiData.titleFont =  Font(\"Helvetica\", 12);\r\n\tguiData.window = w; // restore window\r\n\t// create window if needed\r\n\tif (guiData.window.isNil, {\r\n\t\tguiData.window = Window(\r\n\t\t\t\"Wavetable Builder\",\r\n\t\t\tRect(0, 0, d.windowWidth,  d.windowHeight)\r\n\t\t).front;\r\n\t\tw = guiData.window;\r\n\t\tw.view.resize_(5);\r\n\t\tw.view.background_(d.colWindow);\r\n\t\tw.alwaysOnTop = d.alwaysOnTop;\r\n\t\tw.onClose_({\r\n\t\t\td.freeAll;\r\n\t\t});\r\n\t}, {\r\n\t\tguiData.window.view.removeAll;\r\n\t});\r\n\t// play freq slider & numbox\r\n\tguiData.playFreqSlider = Slider().thumbSize_(8).orientation_(\\horizontal)\r\n\t.minWidth_(180).maxWidth_(260).minHeight_(20).maxHeight_(20)\r\n\t.background_(Color.white).knobColor_(d.colButton)\r\n\t.action_({arg view;\r\n\t\td.playFreq = d.outFreqSpec.map(view.value);\r\n\t\tguiData.playFreqNumbox.value_(d.playFreq);\r\n\t\td.synthNode.set(\\playFreq, d.playFreq);\r\n\t\td.multiSynthNode.set(\\playFreq, d.playFreq);\r\n\t})\r\n\t.value_(d.outFreqSpec.unmap(d.playFreq));\r\n\tguiData.playFreqNumbox = NumberBox().maxDecimals_(2)\r\n\t.minWidth_(40).maxWidth_(40).minHeight_(20).maxHeight_(20)\r\n\t.action_({arg view;\r\n\t\td.playFreq = view.value;\r\n\t\tguiData.playFreqSlider.value = d.outFreqSpec.unmap(d.playFreq);\r\n\t\td.synthNode.set(\\playFreq, d.playFreq);\r\n\t\td.multiSynthNode.set(\\playFreq, d.playFreq);\r\n\t})\r\n\t.value_(d.playFreq);\r\n\t// mode - sines or envelope\r\n\tguiData.modeViews = HLayout(\r\n\t\t// mode popup\r\n\t\tListView().minWidth_(96).maxWidth_(96).minHeight_(30).maxHeight_(30)\r\n\t\t.items_([\"Mode: Sines\", \"Mode: Envelope\"])\r\n\t\t.stringColor_(d.colLabelString).background_(d.colHighlight)\r\n\t\t.value_(d.envMode.binaryValue)\r\n\t\t.action_({arg view;\r\n\t\t\tvar val = view.value.asBoolean;\r\n\t\t\td.envMode = val;\r\n\t\t\td.hidePoints = false;\r\n\t\t\td.updateBufGui;\r\n\t\t}),\r\n\t);\r\n\tif (d.envMode, {\r\n\t\tguiData.modeViews.add(\r\n\t\t\tView().minWidth_(84).maxWidth_(84).maxHeight_(24);  // spacer\r\n\t\t);\r\n\t}, {\r\n\t\tguiData.modeViews.add(\r\n\t\t\t// num sines popup\r\n\t\t\tPopUpMenu().minWidth_(80).maxWidth_(80).maxHeight_(24)\r\n\t\t\t.items_((4..d.maxSines).collect({ arg i; i.asString + \"Sines\"}))\r\n\t\t\t.stringColor_(d.colLabelString).background_(d.colHighlight)\r\n\t\t\t.value_(d.numSines - 4)\r\n\t\t\t.action_({arg view;\r\n\t\t\t\td.numSines = view.value + 4;\r\n\t\t\t\td.updateBufGui;\r\n\t\t\t}),\r\n\t\t);\r\n\t});\r\n\t// top row\r\n\ttopRowViews = HLayout(\r\n\t\tguiData.modeViews,\r\n\t\t20, // spacer\r\n\t\t// Help button\r\n\t\tStaticText().string_(\"Help\").align_(\\center)\r\n\t\t.minWidth_(50).maxWidth_(50).minHeight_(20).maxHeight_(20)\r\n\t\t.stringColor_(Color.white).background_(d.colHelp)\r\n\t\t.mouseDownAction_({d.showHelpWindow}),\r\n\t\t20, // spacer\r\n\t\t// Print button\r\n\t\tStaticText().string_(\"Print Specs\").align_(\\center)\r\n\t\t.minWidth_(80).maxWidth_(80).minHeight_(20).maxHeight_(20)\r\n\t\t.stringColor_(Color.white).background_(d.colButton)\r\n\t\t.mouseDownAction_({d.printSpecs;}),\r\n\t\t20, // spacer\r\n\t\t// checkbox\r\n\t\tButton().states_([\r\n\t\t\t[\"[ ] Window on top\", d.colButton, Color.white],\r\n\t\t\t[\"[X] Window on top\", Color.white, d.colButton]])\r\n\t\t.minWidth_(114).maxWidth_(114).minHeight_(20).maxHeight_(20)\r\n\t\t.action_({arg view;\r\n\t\t\tvar val = view.value.asBoolean;\r\n\t\t\td.alwaysOnTop = val;\r\n\t\t\tguiData.window.alwaysOnTop = val;\r\n\t\t})\r\n\t\t.value_(d.alwaysOnTop.binaryValue),\r\n\t\tnil, // spacer\r\n\t);\r\n\t// build left panel\r\n\tif (d.envMode, {\r\n\t\t// envelope controls\r\n\t\tpointWidth = (60 - (3 * (d.arrPoints.size - 9).max(0))).max(40);\r\n\t\t// Point labels\r\n\t\tguiData.pointLabels = [\r\n\t\t\t// label\r\n\t\t\tStaticText().minWidth_(50).maxWidth_(50).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.string_(\"Points\").stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t]\r\n\t\t++ d.arrPoints.size.collect({arg i;\r\n\t\t\t// label\r\n\t\t\tStaticText().string_(i + 1).align_(\\center)\r\n\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel)\r\n\t\t})\r\n\t\t++ [nil];\r\n\t\t// Point delete buttons\r\n\t\tguiData.pointDelBtns = [\r\n\t\t\t// label\r\n\t\t\tStaticText().minWidth_(50).maxWidth_(50).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.string_(\"Delete\").stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t]\r\n\t\t++ d.arrPoints.size.collect({arg i;\r\n\t\t\tif (d.minPoints == d.arrPoints.size or: {i == 0} or: {i == (d.arrPoints.size - 1)}, {\r\n\t\t\t\tView().background_(d.colSemiGrey)\r\n\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t}, {\r\n\t\t\t\t// button\r\n\t\t\t\tStaticText().string_(\"x\").align_(\\center)\r\n\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.stringColor_(Color.black).background_(Color.white)\r\n\t\t\t\t.mouseDownAction_({arg view;\r\n\t\t\t\t\td.deletePointIndex = i;\r\n\t\t\t\t\td.deletePoint;\r\n\t\t\t\t\td.updateBufGui;\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t})\r\n\t\t++ [nil];\r\n\t\t// Point curve type popups\r\n\t\tguiData.pointCurveTypeBtns = [\r\n\t\t\t// label\r\n\t\t\tStaticText().minWidth_(50).maxWidth_(50).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.string_(\"Type\").stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t]\r\n\t\t++ d.arrPoints.size.collect({arg i;\r\n\t\t\tif (i == (d.arrPoints.size - 1), {\r\n\t\t\t\tView().background_(Color.clear)\r\n\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t}, {\r\n\t\t\t\t// popup\r\n\t\t\t\tPopUpMenu()\r\n\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.items_(d.arrCurveTypeOptions.collect({ arg i; i[0]}))\r\n\t\t\t\t.stringColor_(Color.black).background_(Color.white)\r\n\t\t\t\t.value_(d.arrCurveTypes[i])\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\td.arrCurveTypes[i] = view.value;\r\n\t\t\t\t\td.updateBufGui;\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t})\r\n\t\t++ [nil];\r\n\t\t// Point curve sliders\r\n\t\tguiData.pointCurveSldrs = [\r\n\t\t\t// label\r\n\t\t\tStaticText().align_(\\center)\r\n\t\t\t.minWidth_(50).maxWidth_(50).minHeight_(200)\r\n\t\t\t.string_(\"Curve\").stringColor_(d.colLabelString).background_(d.colLabel);\r\n\t\t]\r\n\t\t++ d.arrPoints.size.collect({arg i;\r\n\t\t\tif (i == (d.arrPoints.size - 1), {\r\n\t\t\t\tView().background_(Color.clear)\r\n\t\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(200)\r\n\t\t\t}, {\r\n\t\t\t\tif (d.arrCurveTypes[i] != 1, {\r\n\t\t\t\t\tView().background_(d.colSemiGrey)\r\n\t\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(200)\r\n\t\t\t\t}, {\r\n\t\t\t\t\t// slider\r\n\t\t\t\t\tSlider().thumbSize_(8)\r\n\t\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(200)\r\n\t\t\t\t\t.background_(Color.white).knobColor_(d.colButton)\r\n\t\t\t\t\t.action_({arg view;\r\n\t\t\t\t\t\tvar val = d.curveSpec.map(view.value);\r\n\t\t\t\t\t\td.arrCurveVals[i] = val;\r\n\t\t\t\t\t\tguiData.pointCurveNumboxes[i + 1].value = val;\r\n\t\t\t\t\t\td.refreshPointsView;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.mouseUpAction_({\r\n\t\t\t\t\t\td.updateBufPlot;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.value_(d.curveSpec.unmap(d.arrCurveVals[i]))\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t})\r\n\t\t++ [nil];\r\n\t\t// Point curve numboxes\r\n\t\tguiData.pointCurveNumboxes = [\r\n\t\t\t// label\r\n\t\t\tView().minWidth_(50).maxWidth_(50).minHeight_(20).maxHeight_(20),\r\n\t\t]\r\n\t\t++ d.arrPoints.size.collect({arg i;\r\n\t\t\tif (i == (d.arrPoints.size - 1), {\r\n\t\t\t\tView().background_(Color.clear)\r\n\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t}, {\r\n\t\t\t\tif (d.arrCurveTypes[i] != 1, {\r\n\t\t\t\t\tView().minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t}, {\r\n\t\t\t\t\t// button\r\n\t\t\t\t\tNumberBox().maxDecimals_(2)\r\n\t\t\t\t\t.minWidth_(pointWidth).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t\t.font_(Font(\"Ariel\", 9))\r\n\t\t\t\t\t.action_({arg view;\r\n\t\t\t\t\t\tview.value = d.curveSpec.constrain(view.value);\r\n\t\t\t\t\t\td.arrCurveVals[i] = view.value;\r\n\t\t\t\t\t\tguiData.pointCurveSldrs[i + 1].value = d.curveSpec.unmap(view.value);\r\n\t\t\t\t\t\td.updateBufPlot;\r\n\t\t\t\t\t\td.refreshPointsView;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.value_(d.arrCurveVals[i])\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t})\r\n\t\t++ [nil];\r\n\t\t// env view\r\n\t\tguiData.envView = UserView().background_(Color.white)\r\n\t\t.minWidth_(520).minHeight_(250).maxHeight_(600)\r\n\t\t.drawFunc_({arg view;\r\n\t\t\tvar margin = 4;\r\n\t\t\tvar width = view.bounds.width - (margin * 2);\r\n\t\t\tvar height = view.bounds.height - (margin * 2);\r\n\t\t\tvar points = d.arrPoints;\r\n\t\t\tvar envArray, radius, holdArr;\r\n\t\t\tif (d.smoothType == 0, { // no smoothing\r\n\t\t\t\tenvArray = Env(\r\n\t\t\t\t\tpoints.collect({arg item; item.y + 0.001}),\r\n\t\t\t\t\tpoints.collect({arg item; item.x}).differentiate.keep(1 - points.size),\r\n\t\t\t\t\td.getArrPointCurves,\r\n\t\t\t\t\toffset: -0.001,\r\n\t\t\t\t).asSignal(width).as(Array);\r\n\t\t\t}, {\r\n\t\t\t\tenvArray = Env(\r\n\t\t\t\t\tpoints.collect({arg item; item.y + 0.001}),\r\n\t\t\t\t\tpoints.collect({arg item; item.x}).differentiate.keep(1 - points.size),\r\n\t\t\t\t\td.getArrPointCurves,\r\n\t\t\t\t\toffset: -0.001\r\n\t\t\t\t).asSignal(d.bufLength).as(Array);\r\n\t\t\t\t// smoothing\r\n\t\t\t\tradius = d.smoothType * 3;\r\n\t\t\t\tholdArr = envArray.collect({arg item, i;\r\n\t\t\t\t\tenvArray.wrapAt(i + (radius.neg..radius)).mean;\r\n\t\t\t\t});\r\n\t\t\t\t// resize\r\n\t\t\t\tenvArray = holdArr.resamp1(width);\r\n\t\t\t});\r\n\t\t\t// outline\r\n\t\t\tPen.color = Color.grey(0.85);\r\n\t\t\tPen.addRect(Rect(0, 0, view.bounds.width, margin));\r\n\t\t\tPen.addRect(Rect(0, view.bounds.height - margin, view.bounds.width, margin));\r\n\t\t\tPen.addRect(Rect(0, 0, margin, view.bounds.height));\r\n\t\t\tPen.addRect(Rect(view.bounds.width - margin, 0, margin, view.bounds.height));\r\n\t\t\tPen.fill;\r\n\t\t\t// curve\r\n\t\t\tPen.color = Color.grey(0.85).blend(d.colButton);\r\n\t\t\tenvArray.do({arg item, i;\r\n\t\t\t\tPen.moveTo((margin + i) @ (view.bounds.height * 0.5));\r\n\t\t\t\tPen.lineTo((margin + i) @ (view.bounds.height - (item * height + margin)));\r\n\t\t\t\tPen.stroke;\r\n\t\t\t});\r\n\t\t\t// points\r\n\t\t\tif (d.hidePoints.not, {\r\n\t\t\t\tpoints.do({arg item, i;\r\n\t\t\t\t\tvar dataStringPt = Point(0, 8);\r\n\t\t\t\t\tvar point = Point(\r\n\t\t\t\t\t\t(item.x * width) + margin,\r\n\t\t\t\t\t\tview.bounds.height - ((item.y * height) + margin)\r\n\t\t\t\t\t);\r\n\t\t\t\t\tPen.color = Color.blue;\r\n\t\t\t\t\tPen.addArc(point, 4, 0, 2pi );\r\n\t\t\t\t\tPen.stroke;\r\n\t\t\t\t\tPen.stringAtPoint((i + 1).asString, Point(\r\n\t\t\t\t\t\t(point.x + 5).clip(10, view.bounds.width - 18),\r\n\t\t\t\t\t\t(point.y - 12).clip(3, view.bounds.height - 18),\r\n\t\t\t\t\t));\r\n\t\t\t\t\tif (guiData.mouseMode == \\dragPoint and: {guiData.dragPointInd == i}, {\r\n\t\t\t\t\t\tPen.color = Color.grey;\r\n\t\t\t\t\t\tPen.moveTo(point.x @ margin);\r\n\t\t\t\t\t\tPen.lineTo(point.x @ (view.bounds.height - margin));\r\n\t\t\t\t\t\tPen.moveTo(margin @ point.y);\r\n\t\t\t\t\t\tPen.lineTo((view.bounds.width - margin) @ point.y);\r\n\t\t\t\t\t\tPen.stroke;\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t})\r\n\t\t.mouseDownAction_({arg view, x, y, modifiers;\r\n\t\t\tvar margin = 5;\r\n\t\t\tvar width = view.bounds.width - (margin * 2);\r\n\t\t\tvar height = view.bounds.height - (margin * 2);\r\n\t\t\tvar points = d.arrPoints;\r\n\t\t\tvar posx = (x - margin) / width;\r\n\t\t\tvar posy = (view.bounds.height - y - margin) / height;\r\n\t\t\tvar found = false;\r\n\t\t\tvar ind, pointX, pointY, diffx, diffy;\r\n\t\t\tif (d.hidePoints.not, {\r\n\t\t\t\tguiData.mouseMode = nil;\r\n\t\t\t\tguiData.dragPointInd = nil;\r\n\t\t\t\tind = 0;\r\n\t\t\t\t// if near to existing point, select that\r\n\t\t\t\twhile {found.not and: {ind < points.size}} {\r\n\t\t\t\t\tpointX = (points[ind].x * width) + margin;\r\n\t\t\t\t\tpointY = (points[ind].y * height) + margin;\r\n\t\t\t\t\tdiffx = pointX - x;\r\n\t\t\t\t\tdiffy = pointY - (view.bounds.height - y);\r\n\t\t\t\t\tif ((diffx.abs < 6) and: {diffy.abs < 6}, {\r\n\t\t\t\t\t\tfound = true;\r\n\t\t\t\t\t\tguiData.dragPointInd = ind;\r\n\t\t\t\t\t});\r\n\t\t\t\t\tind = ind + 1;\r\n\t\t\t\t};\r\n\t\t\t\tif (found, {\r\n\t\t\t\t\tguiData.mouseMode = \\dragPoint;\r\n\t\t\t\t}, {\r\n\t\t\t\t\tguiData.newPoint = Point(posx.clip(0.0001, 0.9999), posy.clip(0, 1));\r\n\t\t\t\t\tguiData.dragPointInd = d.addNewPoint;\r\n\t\t\t\t\tif (guiData.dragPointInd.notNil, {\r\n\t\t\t\t\t\tguiData.mouseMode = \\dragPoint;\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t\tview.refresh;\r\n\t\t\t\td.updateEnvInfoView;\r\n\t\t\t});\r\n\t\t})\r\n\t\t.mouseMoveAction_({arg view, x, y, modifiers;\r\n\t\t\tvar margin, width, height, posx, posy, points, point;\r\n\t\t\tvar  prevx, nextx;\r\n\t\t\tif (guiData.mouseMode == \\dragPoint, {\r\n\t\t\t\tmargin = 5;\r\n\t\t\t\twidth = view.bounds.width - (margin * 2);\r\n\t\t\t\theight = view.bounds.height - (margin * 2);\r\n\t\t\t\tposx = (x - margin).clip(0, width) / width;\r\n\t\t\t\tposy = (view.bounds.height - y - margin) / height;\r\n\t\t\t\tpoints = d.arrPoints;\r\n\t\t\t\tpoint = points[guiData.dragPointInd];\r\n\t\t\t\t// if first point\r\n\t\t\t\tif (guiData.dragPointInd == 0, {\r\n\t\t\t\t\tpoint.y = posy.clip(0, 1);\r\n\t\t\t\t\tif (d.lastPointEqualsFirst == true, {\r\n\t\t\t\t\t\tpoints[points.size - 1].y = point.y;\r\n\t\t\t\t\t});\r\n\t\t\t\t}, {\r\n\t\t\t\t\t// if last point\r\n\t\t\t\t\tif (guiData.dragPointInd == (points.size - 1), {\r\n\t\t\t\t\t\tpoint.y = posy.clip(0, 1);\r\n\t\t\t\t\t\tif (d.lastPointEqualsFirst == true, {\r\n\t\t\t\t\t\t\tpoints.first.y = point.y;\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}, {\r\n\t\t\t\t\t\t// else\r\n\t\t\t\t\t\tprevx = points[guiData.dragPointInd - 1].x;\r\n\t\t\t\t\t\tnextx = points[guiData.dragPointInd + 1].x;\r\n\t\t\t\t\t\tpoint.x = posx.clip(prevx + 0.0001, nextx - 0.0001);\r\n\t\t\t\t\t\tpoint.y = posy.clip(0, 1);\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t\tview.refresh;\r\n\t\t\t\td.updateEnvInfoView;\r\n\t\t\t});\r\n\t\t})\r\n\t\t.mouseUpAction_({arg view, x, y, modifiers;\r\n\t\t\tif (guiData.mouseMode == \\dragPoint, {\r\n\t\t\t\tif (guiData.newPoint.notNil, {\r\n\t\t\t\t\td.updateBufGui;\r\n\t\t\t\t}, {\r\n\t\t\t\t\td.updateBuffers;\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t\tguiData.mouseMode = nil;\r\n\t\t\tguiData.dragPointInd = nil;\r\n\t\t\tguiData.newPoint = nil;\r\n\t\t\tview.refresh;\r\n\t\t\td.updateEnvInfoView;\r\n\t\t});\r\n\t\t// info view\r\n\t\tguiData.envInfoView = StaticText().align_(\\center)\r\n\t\t.minWidth_(150).maxWidth_(150).minHeight_(20).maxHeight_(20)\r\n\t\t.stringColor_(d.colLabelString).background_(d.colLabel)\r\n\t\t.string_(d.envInfoString).font_(guiData.titleFont);\r\n\t\t// left panel\r\n\t\tleftPanelLayout = VLayout(\r\n\t\t\t// top row\r\n\t\t\ttopRowViews,\r\n\t\t\t// line\r\n\t\t\tView().minHeight_(1).maxHeight_(1).background_(d.colLabel),\r\n\t\t\t2, // spacer\r\n\t\t\t// env controls\r\n\t\t\tHLayout(\r\n\t\t\t\t// envInfo\r\n\t\t\t\tguiData.envInfoView,\r\n\t\t\t\tView().minWidth_(2).maxWidth_(40), // spacer\r\n\t\t\t\t// checkbox\r\n\t\t\t\tButton().states_([\r\n\t\t\t\t\t[\"[ ] Hide points\", d.colButton, Color.white],\r\n\t\t\t\t\t[\"[X] Hide points\", Color.white, d.colButton]])\r\n\t\t\t\t.minWidth_(95).maxWidth_(95).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\td.hidePoints = view.value.asBoolean;\r\n\t\t\t\t\tguiData.envView.refresh;\r\n\t\t\t\t})\r\n\t\t\t\t.value_(d.hidePoints.binaryValue),\r\n\t\t\t\tView().minWidth_(2).maxWidth_(40), // spacer\r\n\t\t\t\t// Smoothing popup\r\n\t\t\t\tPopUpMenu().minWidth_(160).maxWidth_(160).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.items_(11.collect({arg i; var ind;\r\n\t\t\t\t\tif (i == 0,\r\n\t\t\t\t\t\t{ind = \"Smoothing: Off (default)\"},\r\n\t\t\t\t\t\t{ind = \"Smoothing:\" + (i * 3).asString + \"Samples\"}\r\n\t\t\t\t\t);\r\n\t\t\t\t\tind;\r\n\t\t\t\t})).stringColor_(Color.black).background_(d.colHighlight)\r\n\t\t\t\t.value_(d.smoothType)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\td.smoothType = view.value;\r\n\t\t\t\t\td.updateBuffers;\r\n\t\t\t\t\tguiData.envView.refresh;\r\n\t\t\t\t}),\r\n\t\t\t\tView().minWidth_(2).maxWidth_(40), // spacer\r\n\t\t\t\t// checkbox\r\n\t\t\t\tButton().states_([\r\n\t\t\t\t\t[\"[ ] Last point = first\", d.colButton, Color.white],\r\n\t\t\t\t\t[\"[X] Last point = first\", Color.white, d.colButton]])\r\n\t\t\t\t.minWidth_(120).maxWidth_(120).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\tvar val = view.value.asBoolean;\r\n\t\t\t\t\td.lastPointEqualsFirst = val;\r\n\t\t\t\t\tif (val, {\r\n\t\t\t\t\t\td.setLastPointToFirst;\r\n\t\t\t\t\t\td.updateBufGui;\r\n\t\t\t\t\t});\r\n\t\t\t\t})\r\n\t\t\t\t.value_(d.lastPointEqualsFirst.binaryValue),\r\n\t\t\t\tnil,  // spacer\r\n\t\t\t).spacing_(8),\r\n\t\t\t1, // spacer\r\n\t\t\t[guiData.envView, stretch: 1],\r\n\t\t\t1, // spacer\r\n\t\t\tHLayout(*guiData.pointLabels.collect({arg item; [item, stretch: 1]})).spacing_(2),\r\n\t\t\t1, // spacer\r\n\t\t\tHLayout(*guiData.pointDelBtns.collect({arg item; [item, stretch: 1]})).spacing_(2),\r\n\t\t\t1, // spacer\r\n\t\t\tHLayout(*guiData.pointCurveTypeBtns.collect({arg item; [item, stretch: 1]})).spacing_(2),\r\n\t\t\t1, // spacer\r\n\t\t\t[HLayout(*guiData.pointCurveSldrs.collect({arg item; [item, stretch: 1]})).spacing_(2), stretch: 0.5],\r\n\t\t\t1, // spacer\r\n\t\t\tHLayout(*guiData.pointCurveNumboxes.collect({arg item; [item, stretch: 1]})).spacing_(2),\r\n\t\t\t// nil,  // spacer\r\n\t\t).spacing_(6);\r\n\t}, {\r\n\t\t// build left panel for sine controls\r\n\t\tguiData.leftPanelViews =  [\r\n\t\t\t// bank: name, spec, arrFuncionNames, functionAction\r\n\t\t\t[\"Sine Freqs\", d.arrFreqs, d.sineFreqSpec, d.arrFreqActions.flop.slice(0), {d.runFreqAction}],\r\n\t\t\t[\"Levels\", d.arrLevels, ControlSpec(0, 1), d.arrLevelActions.flop.slice(0), {d.runLevelAction}],\r\n\t\t\t[\"Phases\", d.arrPhases, ControlSpec(0, 1), d.arrPhaseActions.flop.slice(0), {d.runPhaseAction}],\r\n\t\t].collect({arg layer, layerInd;\r\n\t\t\tvar title = layer[0];\r\n\t\t\tvar arrVals = layer[1];\r\n\t\t\tvar controlSpec = layer[2];\r\n\t\t\tvar arrActionNames = layer[3];\r\n\t\t\tvar functionAction = layer[4];\r\n\t\t\tvar maxHeight = layer[5];\r\n\t\t\tvar titleViews, controlViews, multiSliderView;\r\n\t\t\t// title panel\r\n\t\t\ttitleViews = VLayout(\r\n\t\t\t\t// label\r\n\t\t\t\tStaticText().minWidth_(96).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t\t.font_(guiData.titleFont)\r\n\t\t\t\t.string_(title).stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\t\t// action list\r\n\t\t\t\tListView().minHeight_(50)\r\n\t\t\t\t.items_(arrActionNames)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\tguiData.holdActionInd = view.value;\r\n\t\t\t\t\tfunctionAction.value;\r\n\t\t\t\t\td.updateBufGui;\r\n\t\t\t\t}),\r\n\t\t\t\t2, // spacer\r\n\t\t\t);\r\n\t\t\t// Sine controls\r\n\t\t\tif (d.useMultiSlider, {\r\n\t\t\t\tcontrolViews = d.numSines.collect({ arg i;\r\n\t\t\t\t\tvar numbox = NumberBox().maxDecimals_(4)\r\n\t\t\t\t\t.font_(Font(\"Ariel\", 9))\r\n\t\t\t\t\t.action_({arg view;\r\n\t\t\t\t\t\tview.value = controlSpec.constrain(view.value);\r\n\t\t\t\t\t\tarrVals[i] = view.value;\r\n\t\t\t\t\t\tmultiSliderView.value = d.numSines.collect({arg i; controlSpec.unmap(arrVals[i])});\r\n\t\t\t\t\t\td.updateBufPlot;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.value_(arrVals[i]);\r\n\t\t\t\t\tif ((i % 2) == 0, {numbox.background_(d.colHighlight)});\r\n\t\t\t\t\tnumbox;\r\n\t\t\t\t});\r\n\t\t\t\tmultiSliderView = MultiSliderView().size_(d.numSines).elasticMode_(1)\r\n\t\t\t\t.thumbSize_(30).isFilled_(true).fillColor_(d.colButton.blend(Color.white, 0.7))\r\n\t\t\t\t.minWidth_(d.numSines * 20).maxHeight_(170)\r\n\t\t\t\t.background_(Color.white)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\tvar arrViewVals = view.value.collect({arg argVal, i;\r\n\t\t\t\t\t\tvar val = controlSpec.map(argVal);\r\n\t\t\t\t\t\tarrVals[i] = val;\r\n\t\t\t\t\t\tcontrolViews[i].value = val;\r\n\t\t\t\t\t});\r\n\t\t\t\t})\r\n\t\t\t\t.mouseUpAction_({\r\n\t\t\t\t\td.updateBufPlot;\r\n\t\t\t\t})\r\n\t\t\t\t.value_(d.numSines.collect{arg i; controlSpec.unmap(arrVals[i])});\r\n\t\t\t\tHLayout(titleViews,\r\n\t\t\t\t\tVLayout(\r\n\t\t\t\t\t\tmultiSliderView,\r\n\t\t\t\t\t\tHLayout(*controlViews).margins_(3, 0, 8, 0)\r\n\t\t\t\t\t).spacing_(0)\r\n\t\t\t\t).spacing_(2);\r\n\t\t\t}, {\r\n\t\t\t\tcontrolViews =  d.numSines.collect({ arg i;\r\n\t\t\t\t\tvar slider, numbox;\r\n\t\t\t\t\tslider = Slider().thumbSize_(8).maxHeight_(170)\r\n\t\t\t\t\t.background_(Color.white).knobColor_(d.colButton)\r\n\t\t\t\t\t.action_({arg view;\r\n\t\t\t\t\t\tvar val = controlSpec.map(view.value);\r\n\t\t\t\t\t\tarrVals[i] = val;\r\n\t\t\t\t\t\tnumbox.value = val;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.mouseUpAction_({\r\n\t\t\t\t\t\td.updateBufPlot;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.value_(controlSpec.unmap(arrVals[i]));\r\n\t\t\t\t\tnumbox = NumberBox().maxDecimals_(4)\r\n\t\t\t\t\t.font_(Font(\"Ariel\", 9))\r\n\t\t\t\t\t.action_({arg view;\r\n\t\t\t\t\t\tview.value = controlSpec.constrain(view.value);\r\n\t\t\t\t\t\tarrVals[i] = view.value;\r\n\t\t\t\t\t\tslider.value = controlSpec.unmap(view.value);\r\n\t\t\t\t\t\td.updateBufPlot;\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.value_(arrVals[i]);\r\n\t\t\t\t\tif ((i % 2) == 0, {numbox.background_(d.colHighlight)});\r\n\t\t\t\t\tVLayout(slider, numbox)\r\n\t\t\t\t\t.spacing_(2)\r\n\t\t\t\t\t.margins_([0, 0, 0, 10]);  // bottom margin\r\n\t\t\t\t});\r\n\t\t\t\tHLayout( *([titleViews] ++ controlViews) ).spacing_(2);\r\n\t\t\t});\r\n\t\t});\r\n\t\tleftPanelLayout = VLayout (*([topRowViews] ++ guiData.leftPanelViews));\r\n\t});  // end of build left panel\r\n\t// clipboard buttons\r\n\tguiData.storeBtns = [\r\n\t\t// label\r\n\t\tStaticText().minWidth_(80).maxWidth_(80).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t.string_(\"Store\").stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t]\r\n\t++ 16.collect({arg i;\r\n\t\t// only show store button if data not found\r\n\t\tif (d.clipboards[i].isNil, {\r\n\t\t\t// button\r\n\t\t\tStaticText().string_(i + 1)\r\n\t\t\t.minWidth_(21).maxWidth_(21).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.stringColor_(Color.white).background_(d.colButton.blend(Color.white, 0.2))\r\n\t\t\t.mouseDownAction_({\r\n\t\t\t\td.clipboardIndex = i;\r\n\t\t\t\td.storeClip;\r\n\t\t\t});\r\n\t\t}, {\r\n\t\t\t// spacer\r\n\t\t\tView().background_(d.colSemiGrey)\r\n\t\t\t.minWidth_(21).maxWidth_(21).minHeight_(20).maxHeight_(20);\r\n\t\t});\r\n\t})\r\n\t++ [nil];\r\n\tguiData.loadBtns = [\r\n\t\t// label\r\n\t\tStaticText().minWidth_(80).maxWidth_(80).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t.string_(\"Load\").stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t]\r\n\t++ 16.collect({arg i;\r\n\t\t// only show load button if data found\r\n\t\tif (d.clipboards[i].notNil, {\r\n\t\t\t// button\r\n\t\t\tStaticText().string_(i + 1)\r\n\t\t\t.minWidth_(21).maxWidth_(21).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.stringColor_(Color.white).background_(d.colButton.blend(Color.black, 0.2))\r\n\t\t\t.mouseDownAction_({arg view;\r\n\t\t\t\td.clipboardIndex = i;\r\n\t\t\t\td.loadClip;\r\n\t\t\t});\r\n\t\t}, {\r\n\t\t\t// spacer\r\n\t\t\tView().background_(d.colSemiGrey)\r\n\t\t\t.minWidth_(21).maxWidth_(21).minHeight_(20).maxHeight_(20);\r\n\t\t});\r\n\t})\r\n\t++ [nil];\r\n\t// presets\r\n\tguiData.presetView = ListView()\r\n\t.minHeight_(80).maxHeight_(80)\r\n\t.items_(d.presets.flop.slice(0))\r\n\t.action_({arg view;\r\n\t\td.presetIndex = view.value;\r\n\t\td.loadPreset;\r\n\t});\r\n\tguiData.deleteBtns = [\r\n\t\t// label\r\n\t\tStaticText().minWidth_(80).maxWidth_(80).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t.string_(\"Delete\").stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t]\r\n\t++ 16.collect({arg i;\r\n\t\t// only show clear button if data found\r\n\t\tif (d.clipboards[i].notNil, {\r\n\t\t\t// button\r\n\t\t\tStaticText().string_(\"x\")\r\n\t\t\t.minWidth_(21).maxWidth_(21).minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.stringColor_(Color.white).background_(d.colButton.blend(Color.black, 0.2))\r\n\t\t\t.mouseDownAction_({arg view;\r\n\t\t\t\td.clipboardIndex = i;\r\n\t\t\t\td.clearClip;\r\n\t\t\t});\r\n\t\t}, {\r\n\t\t\t// spacer\r\n\t\t\tView().background_(d.colSemiGrey)\r\n\t\t\t.minWidth_(21).maxWidth_(21).minHeight_(20).maxHeight_(20);\r\n\t\t});\r\n\t})\r\n\t++ [nil];\r\n\t// lower right view\r\n\tif (d.envMode, {\r\n\t\t// env actions popup\r\n\t\tguiData.lowerRightView = VLayout(\r\n\t\t\t// label\r\n\t\t\tStaticText().string_(\"Envelope Actions\").font_(guiData.titleFont)\r\n\t\t\t.minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\tListView()\r\n\t\t\t.minHeight_(210).maxHeight_(210)\r\n\t\t\t.items_(d.arrEnvActions.flop.slice(0))\r\n\t\t\t.stringColor_(Color.black).background_(Color.white)\r\n\t\t\t.action_({arg view;\r\n\t\t\t\tguiData.envActionInd = view.value;\r\n\t\t\t\td.runEnvAction;\r\n\t\t\t\td.updateBufGui;\r\n\t\t\t}),\r\n\t\t\tnil,  // spacer\r\n\t\t);\r\n\t}, {\r\n\t\t// waveform plot\r\n\t\tguiData.plotView = UserView()\r\n\t\t.minWidth_(420).maxWidth_(700).minHeight_(240).maxHeight_(700)\r\n\t\t.background_(d.colLabel);\r\n\t\tguiData.plotView.drawFunc = {arg view;\r\n\t\t\tvar margin = 1;\r\n\t\t\tvar plotWidth = view.bounds.width - (margin * 2);\r\n\t\t\tvar plotHeight = view.bounds.height - (margin * 2);\r\n\t\t\tvar plotArray;\r\n\t\t\tif (guiData.plotArray.notNil, {\r\n\t\t\t\tplotArray = guiData.plotArray.resamp1(plotWidth).linlin(-1, 1, 0, 1);\r\n\t\t\t\tPen.color = Color.grey(0.85).blend(d.colButton);\r\n\t\t\t// outline\r\n\t\t\tPen.moveTo(margin @ margin);\r\n\t\t\tPen.lineTo(margin @ (plotHeight + margin));\r\n\t\t\tPen.lineTo((plotWidth + margin) @ (plotHeight + margin));\r\n\t\t\tPen.lineTo((plotWidth + margin) @ margin);\r\n\t\t\tPen.lineTo(margin @ margin);\r\n\t\t\tPen.stroke;\r\n\t\t\t// curve\r\n\t\t\t\tplotArray.do({arg item, i;\r\n\t\t\t\t\tPen.moveTo((margin + i) @ (view.bounds.height * 0.5));\r\n\t\t\t\t\tPen.lineTo((margin + i) @ (view.bounds.height - (item * plotHeight + margin)));\r\n\t\t\t\t\tPen.stroke;\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t};\r\n\t\tguiData.lowerRightView = guiData.plotView;\r\n\t});\r\n\t// Table position slider\r\n\tguiData.tablePosSlider = HLayout(\r\n\t\t// label\r\n\t\tStaticText().string_(\"Position\").align_(\\center)\r\n\t\t.minWidth_(60).maxWidth_(60).minHeight_(20).maxHeight_(20)\r\n\t\t.font_(guiData.titleFont)\r\n\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t// slider\r\n\t\tSlider().thumbSize_(8).orientation_(\\horizontal)\r\n\t\t.minWidth_(130).maxWidth_(150).minHeight_(20).maxHeight_(20)\r\n\t\t.background_(Color.white).knobColor_(d.colButton)\r\n\t\t.action_({arg view;\r\n\t\t\td.tablePos = view.value;\r\n\t\t\td.multiSynthNode.set(\\tablePos, d.tablePos);\r\n\t\t})\r\n\t\t.value_(d.tablePos)\r\n\t).spacing_(2);\r\n\t// play waveform button\r\n\tguiData.playWaveformBtn = StaticText().string_(\"Play Waveform\").align_(\\center)\r\n\t.minWidth_(96).maxWidth_(96).minHeight_(22).maxHeight_(22)\r\n\t.stringColor_(Color.white).background_(d.colPlay)\r\n\t.mouseDownAction_({d.playWaveSynth; d.updatePlayButtons;});\r\n\t// play wavetable button\r\n\tguiData.playWavetableBtn = StaticText().string_(\"Play Wavetable\")\r\n\t.minWidth_(96).maxWidth_(96)\r\n\t.minHeight_(22).maxHeight_(22).align_(\\center)\r\n\t.stringColor_(Color.white).background_(d.colPlay)\r\n\t.mouseDownAction_({d.playMultiWaveSynth; d.updatePlayButtons;});\r\n\tif (d.tableCount > 1, {\r\n\t\tguiData.wavetableRow = HLayout(\r\n\t\t\tguiData.playWavetableBtn,\r\n\t\t\tnil, // spacer\r\n\t\t\t// stop button\r\n\t\t\tStaticText().string_(\"Stop\")\r\n\t\t\t.minWidth_(42).maxWidth_(42)\r\n\t\t\t.minHeight_(22).maxHeight_(22).align_(\\center)\r\n\t\t\t.stringColor_(Color.white).background_(d.colStop)\r\n\t\t\t.mouseDownAction_({d.stopSynth; d.updatePlayButtons;}),\r\n\t\t\tView().maxWidth_(20), // spacer\r\n\t\t\tnil, // spacer\r\n\t\t\t// table pos slider\r\n\t\t\tguiData.tablePosSlider,\r\n\t\t\tnil, // spacer\r\n\t\t\t// Save Wavetable button\r\n\t\t\tStaticText().string_(\"Save Wavetable\")\r\n\t\t\t.minWidth_(100).maxWidth_(100)\r\n\t\t\t.minHeight_(22).maxHeight_(22).align_(\\center)\r\n\t\t\t.stringColor_(Color.white).background_(d.colSave)\r\n\t\t\t.mouseDownAction_({d.saveWavetable;}),\r\n\t\t).spacing_(2);\r\n\t}, {\r\n\t\t// info text\r\n\t\tguiData.wavetableRow =\r\n\t\t\tStaticText().string_(\"Store at least 2 waveforms in clipboards to see the Wavetable controls\")\r\n\t\t\t.minHeight_(22).maxHeight_(22).align_(\\center)\r\n\t\t\t.stringColor_(d.colLabelString).background_(Color.white);\r\n\t});\r\n\t// assemble window layout\r\n\tguiData.window.layout = HLayout(  // split panels\r\n\t\t// left panel\r\n\t\t[leftPanelLayout, stretch: 1],\r\n\t\t// line\r\n\t\tView().minWidth_(1).maxWidth_(1).background_(d.colLabel),\r\n\t\t// right panel\r\n\t\tVLayout(\r\n\t\t\t5, // spacer\r\n\t\t\tHLayout(  // Row of controls\r\n\t\t\t\t// label\r\n\t\t\t\tStaticText().string_(\"Waveform\")\r\n\t\t\t\t.font_(guiData.titleFont)\r\n\t\t\t\t.minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\t),\r\n\t\t\tHLayout(  // Row of controls\r\n\t\t\t\t// Samples popup\r\n\t\t\t\tPopUpMenu()\r\n\t\t\t\t.minWidth_(160).maxWidth_(160).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.items_(d.arrBufLengthPresets.flop.slice(0).collect({arg item;\r\n\t\t\t\t\t\"Length: \" + item + \"Samples\"}))\r\n\t\t\t\t.stringColor_(Color.black).background_(d.colHighlight)\r\n\t\t\t\t.value_(d.arrBufLengthPresets.flop.slice(1).indexOfEqual(d.bufLength))\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\td.bufLength = d.arrBufLengthPresets.flop.slice(1)[view.value];\r\n\t\t\t\t\td.allocUpdateBuffers;\r\n\t\t\t\t}),\r\n\t\t\t\tnil, // spacer\r\n\t\t\t\t// File format popup\r\n\t\t\t\tPopUpMenu()\r\n\t\t\t\t.minWidth_(190).maxWidth_(190).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.items_(d.arrSaveFormats.collect({ arg item; \"File Format: \" + item[0]}))\r\n\t\t\t\t.stringColor_(Color.black).background_(d.colHighlight)\r\n\t\t\t\t.value_(d.saveFormatIndex)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\td.saveFormatIndex = view.value;\r\n\t\t\t\t}),\r\n\t\t\t\tnil, // spacer\r\n\t\t\t\t// Load defaults button\r\n\t\t\t\tStaticText().string_(\"Default Settings\").align_(\\center)\r\n\t\t\t\t.minWidth_(100).maxWidth_(100).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t.stringColor_(Color.white).background_(d.colButton)\r\n\t\t\t\t.mouseDownAction_({d.setDefaultFormat}),\r\n\t\t\t).spacing_(8),\r\n\t\t\tHLayout(  // Row of controls\r\n\t\t\t\tguiData.playWaveformBtn,\r\n\t\t\t\t// stop button\r\n\t\t\t\tStaticText().string_(\"Stop\")\r\n\t\t\t\t.minWidth_(42).maxWidth_(42)\r\n\t\t\t\t.minHeight_(22).maxHeight_(22).align_(\\center)\r\n\t\t\t\t.stringColor_(Color.white).background_(d.colStop)\r\n\t\t\t\t.mouseDownAction_({d.stopSynth; d.updatePlayButtons;}),\r\n\t\t\t\t// checkbox\r\n\t\t\t\tButton().states_([\r\n\t\t\t\t\t[\"[ ] Pulsing\", d.colButton, Color.white],\r\n\t\t\t\t\t[\"[X] Pulsing\", Color.white, d.colButton]])\r\n\t\t\t\t.minWidth_(74).maxWidth_(74).minHeight_(22).maxHeight_(22)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\tvar val = view.value.asBoolean;\r\n\t\t\t\t\td.pulsing = val;\r\n\t\t\t\t\td.rebuildSynth;\r\n\t\t\t\t})\r\n\t\t\t\t.value_(d.pulsing.binaryValue),\r\n\t\t\t\t// checkbox\r\n\t\t\t\tButton().states_([\r\n\t\t\t\t\t[\"[ ] Add length to name\", d.colButton, Color.white],\r\n\t\t\t\t\t[\"[X] Add length to name\", Color.white, d.colButton]])\r\n\t\t\t\t.minWidth_(140).maxWidth_(140).minHeight_(22).maxHeight_(22)\r\n\t\t\t\t.action_({arg view;\r\n\t\t\t\t\tvar val = view.value.asBoolean;\r\n\t\t\t\t\td.addBufLengthToFilename = val;\r\n\t\t\t\t\td.rebuildSynth;\r\n\t\t\t\t})\r\n\t\t\t\t.value_(d.addBufLengthToFilename.binaryValue),\r\n\t\t\t\tnil, // spacer\r\n\t\t\t\t// Save Waveform button\r\n\t\t\t\tStaticText().string_(\"Save Waveform\")\r\n\t\t\t\t.minWidth_(100).maxWidth_(100)\r\n\t\t\t\t.minHeight_(22).maxHeight_(22).align_(\\center)\r\n\t\t\t\t.stringColor_(Color.white).background_(d.colSave)\r\n\t\t\t\t.mouseDownAction_({d.saveWaveform;}),\r\n\t\t\t).spacing_(8),\r\n\t\t\tHLayout(\r\n\t\t\t\tHLayout(\r\n\t\t\t\t\t// label\r\n\t\t\t\t\tStaticText().string_(\"Play Freq\")\r\n\t\t\t\t\t.minWidth_(64).maxWidth_(64).maxHeight_(20).align_(\\center)\r\n\t\t\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\t\t\t// freq slider/numbox\r\n\t\t\t\t\tguiData.playFreqSlider,\r\n\t\t\t\t\tguiData.playFreqNumbox,\r\n\t\t\t\t).spacing_(2),\r\n\t\t\t\tnil, // spacer\r\n\t\t\t\tHLayout(\r\n\t\t\t\t\t// label\r\n\t\t\t\t\tStaticText().string_(\"Vol\")\r\n\t\t\t\t\t.minWidth_(30).maxWidth_(30).maxHeight_(20).align_(\\center)\r\n\t\t\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\t\t\t// vol slider\r\n\t\t\t\t\tSlider().thumbSize_(8).orientation_(\\horizontal)\r\n\t\t\t\t\t.minWidth_(100).maxWidth_(140).minHeight_(20).maxHeight_(20)\r\n\t\t\t\t\t.background_(Color.white).knobColor_(d.colButton)\r\n\t\t\t\t\t.action_({arg view;\r\n\t\t\t\t\t\td.outLevel = view.value;\r\n\t\t\t\t\t\td.synthNode.set(\\outLevel, view.value);\r\n\t\t\t\t\t\td.multiSynthNode.set(\\outLevel, view.value);\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.value_(d.outLevel),\r\n\t\t\t\t).spacing_(2),\r\n\t\t\t),\r\n\t\t\t// line\r\n\t\t\tView().minHeight_(1).maxHeight_(1).background_(d.colLabel),\r\n\t\t\t// Clipboards\r\n\t\t\tHLayout(\r\n\t\t\t\t// label\r\n\t\t\t\tStaticText().string_(\"Wavetable / Clipboards\")\r\n\t\t\t\t.minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t\t.font_(guiData.titleFont)\r\n\t\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\t\t// Delete All button\r\n\t\t\t\tStaticText().string_(\"Delete All\")\r\n\t\t\t\t.minWidth_(70).maxWidth_(70)\r\n\t\t\t\t.minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t\t.stringColor_(Color.white).background_(d.colDeleteAll)\r\n\t\t\t\t.mouseDownAction_({\r\n\t\t\t\t\tif (d.multiSynthNode.notNil, {\r\n\t\t\t\t\t\td.multiSynthNode.set(\\gate, 0); d.multiSynthNode = nil;\r\n\t\t\t\t\t});\r\n\t\t\t\t\td.clearAllClipboards;\r\n\t\t\t\t}),\r\n\t\t\t).spacing_(8),\r\n\t\t\tHLayout(*guiData.storeBtns).spacing_(4),\r\n\t\t\tHLayout(*guiData.loadBtns).spacing_(4),\r\n\t\t\tHLayout(*guiData.deleteBtns).spacing_(4),\r\n\t\t\t2,\r\n\t\t\tguiData.wavetableRow,\r\n\t\t\t// line\r\n\t\t\tView().minHeight_(1).maxHeight_(1).background_(d.colLabel),\r\n\t\t\t// Presets\r\n\t\t\tStaticText().string_(\"Presets\").font_(guiData.titleFont)\r\n\t\t\t.minHeight_(20).maxHeight_(20).align_(\\center)\r\n\t\t\t.stringColor_(d.colLabelString).background_(d.colLabel),\r\n\t\t\tguiData.presetView,\r\n\t\t\t// line\r\n\t\t\tView().minHeight_(1).maxHeight_(1).background_(d.colLabel),\r\n\t\t\t// Waveform plot or env actions\r\n\t\t\tguiData.lowerRightView,\r\n\t\t\tnil,  // spacer\r\n\t\t);  // end of right panel\r\n\t).spacing_(8);\r\n\td.updatePlot;\r\n\td.updatePlayButtons;\r\n};\r\n// end of d.makeGui\r\nd.deferMakeGui = {\r\n\t{\r\n\t\ts.sync;\r\n\t\t{d.makeGui}.defer(0.05);\r\n\t}.forkIfNeeded(AppClock);\r\n};\r\n// help window\r\nd.showHelpWindow = {\r\n\t{ // defer\r\n\t\t// create window if needed\r\n\t\tif (guiData.helpWindow.isNil, {\r\n\t\t\tguiData.helpWindow = Window(\r\n\t\t\t\t\"Help for Wavetable Builder\",\r\n\t\t\t\tRect(0, 0, 640, 620),\r\n\t\t\t\tscroll: true\r\n\t\t\t).front;\r\n\t\t\tguiData.helpWindow.alwaysOnTop = d.alwaysOnTop;\r\n\t\t\tguiData.helpWindow.view.resize_(5);\r\n\t\t\tguiData.helpWindow.view.background_(Color.white);\r\n\t\t\tguiData.helpWindow.onClose_({\r\n\t\t\t\tguiData.helpWindow = nil;\r\n\t\t\t});\r\n\t\t\tStaticText(guiData.helpWindow, Rect(20, 20, 600, 590)).align_(\\left)\r\n\t\t\t.stringColor_(d.colLabelString).background_(Color.white)\r\n\t\t\t.font_(Font(\"Helvetica\", 12))\r\n\t\t\t.string_(\"Wavetable Builder\\n\\n\"\r\n++ \"This app is for building single-cycle waveforms and wavetables containing 2-16 waveforms.\\n\"\r\n++ \"These can then be saved as sound files and used with wavetable synths.\\n\\n\"\r\n\r\n++ \"Before starting, choose the Waveform Length and the File Format in the top right of the screen.\\n\\n\"\r\n\r\n++ \"For building waveforms there are 2 different modes - Sines and Envelope:\\n\\n\"\r\n\r\n++ \"In Sines mode, sine waves of different frequencies are mixed together to create the waveform. \\n\"\r\n++ \"You can set the frequencies, levels and phases of the sine waves.\\n\"\r\n++ \"There are a few Presets available for waveforms that use Sines mode to create some standard synth waveforms.\\n\\n\"\r\n\r\n++ \"In Envelope mode, you create the waveform shape using points linked together. You can drag the points around or click in an empty space to add a new point.\\n\"\r\n++ \"For each point (apart from the last one) you can set the Type of line linking it to the next point. If you set the Type to Curve, you can set the curvature with a slider.\\n\"\r\n++ \"Smoothing can be set using the yellow popup menu above the envelope. This can reduce aliasing with sharp-edged waveforms.\\n\\n\"\r\n\r\n++ \"In both Sines and Envelope modes, there are various panels with named Actions in them to change the waveform settings in various ways.\\n\\n\"\r\n\r\n++ \"Use the Play Waveform button to hear the waveform while you are changing the controls.\\n\"\r\n++ \"Note: the sound will not change immediately when you are dragging sliders or envelope points, but only when the mouse button is released.\\n\"\r\n++ \"If the Pulsing switch is turned on, the sound level will pulse slowly in and out.\\n\\n\"\r\n\r\n++ \"Once you have built a waveform, you can use the Save Waveform button to save it to a sound file. The 'Add length to name' switch will extend the given file name by adding the waveform length to the end.\\n\\n\"\r\n\r\n++ \"16 clipboards are available for storing waveforms temporarily. Once stored, a load button will restore all the settings for the stored waveform.\\n\\n\"\r\n\r\n++ \"Once you have stored at least 2 waveforms in clipboards, wavetable controls will be shown below the clipboard buttons.\\n\"\r\n++ \"Play Wavetable uses the Position slider to morph between the different stored waveforms.\\n\"\r\n++ \"Save Wavetable joins all the stored waveforms together and saves them to a sound file.\\n\");\r\n\t\t}, {\r\n\t\t\tguiData.helpWindow.front;\r\n\t\t});\r\n\t}.defer;\r\n};\r\n//\r\n// ============ start up ============\r\n//\r\n// start server if needed\r\ns = Server.default;\r\nif (s.serverRunning.not, {s.boot});\r\ns.doWhenBooted({\r\n\tfork{\r\n\t\t// allocate buffers\r\n\t\td.allocateBuffers;\r\n\t\ts.sync;\r\n\t\t// init vals\r\n\t\td.setDefaultSineArrays;\r\n\t\td.setDefaultPoints;\r\n\t\t// start gui\r\n\t\td.deferMakeGui;\r\n\t\t{ // delayed\r\n\t\t\td.updateBufPlot;\r\n\t\t}.defer(0.1);\r\n\t};\r\n});\r\n\"---  Starting Wavetable Builder ...\".postln;\r\n\"\";\r\n)",
   "is_private" : null,
   "id" : "1-5hT",
   "labels" : [
      "gui",
      "wavetable"
   ],
   "ancestor_list" : [],
   "description" : "GUI-based app for building and saving single-cycle waveforms and wavetables. \r\nUses either a mix of sine waves or an envelope of points connected by curves.",
   "name" : "Wavetable Builder with GUI",
   "author" : "txmod"
}
