{
   "labels" : [
      "gui",
      "spatialization",
      "multichannel",
      "spatial audio"
   ],
   "code" : "/* SpacePan V1.0\r\n\r\nThis is a basic spatial audio tool using source oriented vector based\r\ndelay and amplitude panning as well as basic room simulation.\r\n\r\nThis code was written using SC3.11 / OSX10.13.6\r\nDependencies: Quarks wslib, mathlib and SC3plugins package.\r\n\r\nIt is crucial to read the helpfile!\r\nCheck out the tutorial on youtube as well.\r\n\r\ndominik.wegmann@tonmeister.de - www.sccode.org/audioholic\r\n*/\r\n\r\n(// ----- code starts here. set main parameters first!\r\nvar sources, statsources, currpath, makeBusses, makeSynths, makeGUI, makeNodes;\r\nvar srcgroup, fxsgroup, fxrgroup, mstrgroup, master, testsig, reverb, early, delay, aux, revbus, dlybus, auxbus, fxretbus, masterbus, tracklevel;\r\nvar newsrc, removesrc, editsrc, srcmute, srcsolo, srcon, srcstate, srclevel, makesnap, recallsnap, mirrorXYC;\r\nvar pos2pan, pos2point, rect2pos, pos2angle, pos2rect, pos2drive, autorota, autorand, saveprj, loadprj, gethelp, autorefreshview, cleanup;\r\nvar gui, guipoints, srcsize, btnsize, fdrsize, txtsize, clicked, selected, mousepoint, oldmspoint, mousedist, roomdim, roomscale, lspos, statpos, scolors, colors, fontsml, fontlrg, rotaspeedSpec, randspeedSpec, randrangeSpec, rotastepSpec, focusSpec;\r\nvar speedofsound = 343, autorefreshrate = 0.03, showlbl = true, showls = true, showroomsens = false, solostate = 0;\r\nvar masterout = 0, roomsens = 0.5, roomdamp = -12, roomfreq = 4000, fxpan = 1, xfade = 1, xfadetime = 2, xstep = 0.02;\r\n\r\n// ----- server options\r\ns = Server.local;\r\ns.options.device = \"Babyface Pro (71967432)\"; //your audio device.\r\ns.options.numOutputBusChannels = 12; //n outputs. use \"masterout\" to offset\r\ns.options.numInputBusChannels = 4; //n inputs\r\ns.options.sampleRate = 48000;\r\ns.options.memSize = 2.pow(16);\r\ns.newBusAllocators;\r\n\r\n// ----- set room & loudspeakers\r\nroomdim = 16; //virtual room size in meters (always square shape)\r\nroomscale = roomdim/2; //factor to reduce divisions in some functions\r\nlspos = [ //your real loudspeaker positions [x, y] You can offset, but be precise with speaker-to-speaker relation!\r\n\t[-1.5, 2], //!!!set arraysize of amps#[] and delays#[] in synthdefs \"spacepan\" & \"staticsrc\" to number of speakers!\r\n\t[0, 2],\r\n\t[1.5, 2],\r\n\t[2, 0],\r\n\t[2, -2],\r\n\t[0, -3],\r\n\t[-2, -2],\r\n\t[-2, 0]\r\n];\r\n\r\nstatpos = [ //relative pos of static sources (fx returns), always 4\r\n\t[-0.4, 0.4],\r\n\t[0.4, 0.4],\r\n\t[-0.4, -0.4],\r\n\t[0.4, -0.4]\r\n] * roomscale;\r\n\r\nlspos.size.do{arg i, it; lspos[i] = lspos[i].asPoint}; //convert pos to point array\r\nstatpos.size.do{arg i, it; statpos[i] = statpos[i].asPoint};\r\n\r\ncurrpath = PathName(thisProcess.nowExecutingPath).parentPath; //project path\r\nsources = Dictionary.new; //create dict for sound sources\r\nrotaspeedSpec = ControlSpec(0.4, 0.01, 'exp', 0.001, 0.1);\r\nrotastepSpec = ControlSpec(0.01, 0.02, 'exp', 0.001, 0.015);\r\nrandspeedSpec = ControlSpec(3.0, 0.1, 'exp', 0.1, 1.0);\r\nrandrangeSpec = ControlSpec(0.1, roomscale/2, 'exp', 0.1, 1);\r\nfocusSpec = ControlSpec(0, 2, 'lin', 0.1);\r\n\r\ngui = (); //the gui environment\r\nguipoints = 800;\r\nsrcsize = 15;\r\ntxtsize = Point(40, 24);\r\nbtnsize = Point(65, 24);\r\nfdrsize = Point(135, 24);\r\nfontlrg = Font(\"Helvetica\", 18);\r\nfontsml = Font(\"Helvetica\", 13);\r\nscolors = [ //source colors\r\n\tColor.new255(238, 238, 53), //yellow, default\r\n\tColor.new255(110, 159, 247), //blue, grp1\r\n\tColor.new255(0, 230, 114), //green, grp2\r\n\tColor.new255(215, 140, 149), //red, grp3\r\n];\r\n\r\ncolors = [ //some colors to use somehow\r\n\tColor.new255(245, 222, 179), //wheat\r\n\tColor.new255(171, 130, 255) //orange\r\n];\r\n\r\n// ----- init all the main stuff\r\nRoutine.run({\r\n\r\n\ts.bootSync;\r\n\r\n\t\"\\n--- busses ---\".postln;\r\n\tmakeBusses.value;\r\n\ts.sync;\r\n\r\n\t\"\\n--- synthdefs ---\".postln;\r\n\tmakeSynths.value;\r\n\ts.sync;\r\n\r\n\t\"\\n--- groups, fx and master ---\".postln;\r\n\tmakeNodes.value;\r\n\ts.sync;\r\n\r\n\t\"\\n--- draw the gui ---\".postln;\r\n\t{makeGUI.value}.defer;\r\n\ts.sync;\r\n\r\n\tautorefreshview.start;\r\n});\r\n\r\nmakeBusses = {\r\n\tmasterbus = Bus.audio(s, lspos.size);\r\n\trevbus = Bus.audio(s, 4);\r\n\tdlybus = Bus.audio(s, 4);\r\n\tauxbus = Bus.audio(s, 4);\r\n\tfxretbus = Bus.audio(s, 4);\r\n\t(\"Master Bus Index: \"++masterbus.index).postln;\r\n\t(\"Reverb Bus Index: \"++revbus.index).postln;\r\n\t(\"Delay Bus Index: \"++dlybus.index).postln;\r\n\t(\"Aux Bus Index: \"++auxbus.index).postln;\r\n\t(\"FX Return Bus Index: \"++fxretbus.index).postln;\r\n\t(\"Sending default test signal on bus 100\").postln;\r\n};\r\n\r\nmakeNodes = {\r\n\r\n\t// ----- groups\r\n\tsrcgroup = Group(s); //source\r\n\tfxsgroup = Group.after(srcgroup); //fx sends\r\n\tfxrgroup = Group.after(fxsgroup); //fx returns\r\n\tmstrgroup = Group.after(fxrgroup); //master\r\n\r\n\t// ----- test signal\r\n\ttestsig = Synth(\"testsig\", [\\out, 100]);\r\n\r\n\t// ----- 4 channel reverb\r\n\treverb = 2.collect{arg it;\r\n\t\tSynth(\"reverb\", [\r\n\t\t\t\\in, revbus.index+(it*2),\r\n\t\t\t\\out, fxretbus.index+(it*2),\r\n\t\t\t\\rt, 1.7,\r\n\t\t\t\\stereo, 0,\r\n\t\t\t\\lf, 200,\r\n\t\t\t\\hf, 4000,\r\n\t\t\t\\lfratio, 1.0,\r\n\t\t\t\\hfratio, 1.0,\r\n\t\t\t\\early, 0.5,\r\n\t\t\t\\late, 0.7,\r\n\t\t\t\\modRate, 0.5,\r\n\t\t\t\\modDepth, 0.3,\r\n\t\t\t\\amp, 0.5\r\n\t], fxsgroup)};\r\n\r\n\t// ----- 4 channel delay\r\n\tdelay = 4.collect{arg it;\r\n\t\tSynth(\"delay\", [\r\n\t\t\t\\in, dlybus.index+it,\r\n\t\t\t\\out, fxretbus.index+it,\r\n\t\t\t\\dly, 0.3,\r\n\t\t\t\\fb, 0.7,\r\n\t\t\t\\amp, 0.5\r\n\t], fxsgroup)};\r\n\r\n\t// ----- static sources (fx returns)\r\n\tstatsources = 4.collect{arg it;\r\n\t\tvar pos, amps;\r\n\t\tpos = statpos[it];\r\n\t\td = lspos.collect{arg i; pos.dist(i)};\r\n\t\ta = d.minItem/d;\r\n\t\tamps = a / a.squared.sum.sqrt;\r\n\r\n\t\tSynth(\"staticsrc\", [\r\n\t\t\t\\inbus, fxretbus.index+it,\r\n\t\t\t\\out, masterbus,\r\n\t\t\t\\amps, amps\r\n\t], fxrgroup)};\r\n\r\n\t// ----- master\r\n\tmaster = Synth(\"master\", [\\in, masterbus, \\out, masterout, \\amp, 1, \\gate, 1], mstrgroup);\r\n\r\n\t// ----- source input metering\r\n\ttracklevel = Synth(\"tracklevel\", addAction:'addToTail');\r\n\r\n};\r\n\r\n// ----- main gui window\r\nmakeGUI = {\r\n\tgui.win = Window(\" SPACEPAN \", Rect(0, 0, 1040, 810).center_(Window.availableBounds.center), resizable: false, border: true).front;\r\n\t//gui.win.alwaysOnTop_(true).userCanClose_(true);\r\n\tgui.win.onClose = {cleanup.value};\r\n\tgui.win.view.background = Color.gray(0.2);\r\n\r\n\t// ----- subviews\r\n\tgui.srctop = View(gui.win, Rect(5, 5, 225, 40)).backColor_(Color.gray(0.85));\r\n\tgui.srctop.addFlowLayout(5@5, 5@5);\r\n\r\n\tgui.src = View(gui.win, Rect(5, 50, 225, 540)).backColor_(Color.gray(0.85)).enabled_(false);\r\n\tgui.src.addFlowLayout(5@5, 5@5);\r\n\r\n\tgui.opt = View(gui.win, Rect(5, 595, 225, 100)).backColor_(Color.gray(0.85));\r\n\tgui.opt.addFlowLayout(5@5, 5@5);\r\n\r\n\tgui.mstr = View(gui.win, Rect(5, 700, 225, 105)).backColor_(Color.gray(0.85));\r\n\tgui.mstr.addFlowLayout(5@5, 5@5);\r\n\r\n\tgui.fxpanel = Window(\"FX SETTINGS\", Rect(0, 0, 285, 350),\r\n\t\tresizable: false, scroll: true).visible_(false).userCanClose_(false);\r\n\tgui.fxpanel.addFlowLayout(5@5, 5@5);\r\n\r\n\tgui.snaplist = Window(\"SNAPSHOTS\", Rect(0, 0, 285, 450),\r\n\t\tresizable: false).visible_(false).userCanClose_(false);\r\n\r\n\tgui.meters = Window(\"METERING\",\r\n\t\tRect(0, 0, ServerMeterView.getWidth(0, s.options.numOutputBusChannels), ServerMeterView.height),\r\n\t\tresizable: false).visible_(false).userCanClose_(false);\r\n\r\n\tgui.space = UserView(gui.win, Rect(235, 5, guipoints, guipoints)).backColor_(Color.black);\r\n\r\n\t// ----- source panel top\r\n\tStaticText(gui.srctop, Point(70, 30)).string_(\"Source: \").font_(fontlrg);\r\n\r\n\tgui.srcID = StaticText(gui.srctop, Point(40, 28)).string_(\"0\").font_(fontlrg).align_(\\center);\r\n\r\n\tgui.srcPrev = Button(gui.srctop, Point(27, 27)).string_(\" < \").font_(fontlrg)\r\n\t.action_({arg view;\r\n\t\tif(selected > 1) {\r\n\t\t\tselected = selected -1;\r\n\t\t\teditsrc.value(selected)};\r\n\t});\r\n\r\n\tgui.srcNext = Button(gui.srctop, Point(27, 27)).string_(\" > \").font_(fontlrg)\r\n\t.action_({arg view;\r\n\t\tif (sources.keys.maxItem > selected){\r\n\t\t\tselected = selected +1;\r\n\t\t\teditsrc.value(selected)};\r\n\t});\r\n\r\n\tgui.srcAdd = Button(gui.srctop, Point(27, 27)).font_(fontlrg)\r\n\t.states_([[\" + \", Color.black, Color.green(0.9)]])\r\n\t.action_({arg view; newsrc.value});\r\n\r\n\r\n\t// ----- source edit panel\r\n\tStaticText(gui.src, txtsize).string_(\"Label\").font_(fontsml);\r\n\tgui.srcLabel = TextField(gui.src, btnsize).string_(\"Name\").font_(fontsml);\r\n\r\n\tgui.srcGrp = PopUpMenu(gui.src, btnsize).\r\n\titems_([\"NoGrp\", \"Grp1\", \"Grp2\", \"Grp3\"]).font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Input\").font_(fontsml);\r\n\tgui.srcInput = NumberBox(gui.src, btnsize).value_(0).font_(fontsml);\r\n\r\n\tgui.srcMeter = LevelIndicator(gui.src, btnsize).style_(\\led).stepWidth_(1).warning_(0.8).critical_(0.95);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"\").font_(fontsml);\r\n\tgui.srcSolo = Button(gui.src, btnsize).font_(fontsml)\r\n\t.states_([[\"Solo\", Color.black, Color.gray(0.9)], [\"Solo\", Color.black, Color.yellow]]);\r\n\r\n\tgui.srcMute = Button(gui.src, btnsize).font_(fontsml)\r\n\t.states_([ [\"Mute\", Color.black, Color.red ], [\"Mute\", Color.black, Color.gray(0.9)] ])\r\n\t.value_(1);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Level\").font_(fontsml);\r\n\tgui.srcVol = Slider(gui.src, fdrsize);\r\n\tgui.srcVolnum = StaticText(gui.src, Point(30,24)).string_(\"-inf\").font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Focus\").font_(fontsml);\r\n\tgui.srcFocus = Slider(gui.src, fdrsize).step_(0.1);\r\n\tgui.srcFocusnum = StaticText(gui.src, Point(30,24)).string_(\"0.0\").font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Mode\").font_(fontsml);\r\n\tStaticText(gui.src, Point(115,24)).string_(\"Apply Delay\").font_(fontsml);\r\n\tgui.srcPanmode = CheckBox(gui.src, Point(24,24)).value_(0);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"\").font_(fontsml);\r\n\tStaticText(gui.src, Point(115,24)).string_(\"Apply Room FX\").font_(fontsml);\r\n\tgui.srcRoom = CheckBox(gui.src, Point(24,24)).value_(1);\r\n\r\n\t// ----- fx sends\r\n\tStaticText(gui.src, txtsize).string_(\"Rev\").font_(fontsml);\r\n\tgui.srcRev = Slider(gui.src, fdrsize);\r\n\tgui.srcRevnum = StaticText(gui.src, Point(30,24)).string_(\"-inf\").font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Dly\").font_(fontsml);\r\n\tgui.srcDly = Slider(gui.src, fdrsize);\r\n\tgui.srcDlynum = StaticText(gui.src, Point(30,24)).string_(\"-inf\").font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Aux\").font_(fontsml);\r\n\tgui.srcAux = Slider(gui.src, fdrsize).enabled_(false);\r\n\tgui.srcAuxnum = StaticText(gui.src, Point(30,24)).string_(\"-inf\").font_(fontsml);\r\n\r\n\t// ----- automation (rota/rand)\r\n\tStaticText(gui.src, txtsize).string_(\"Rota\").font_(fontsml);\r\n\tgui.srcRota = Button(gui.src,Point(35,24))\r\n\t.states_([[\"ON\", Color.black, Color.gray(0.9)], [\"ON\", Color.black, Color.cyan]]).font_(fontsml);\r\n\r\n\tgui.srcRotaDir = PopUpMenu(gui.src, Point(95, 24)).items_([\"Clockwise\",\"Anticlock\"]).font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Speed\").font_(fontsml);\r\n\tgui.srcRotaSpeed = Slider(gui.src, fdrsize);\r\n\tgui.srcRotaSpeedString = StaticText(gui.src, Point(30,24)).string_(\"0.0\").font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Rand\").font_(fontsml);\r\n\tgui.srcRand = Button(gui.src,Point(35,24)).font_(fontsml)\r\n\t.states_([[\"ON\", Color.black, Color.gray(0.9)], [\"ON\", Color.black, Color.cyan]]).font_(fontsml);\r\n\r\n\tgui.src.decorator.nextLine;\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Step\").font_(fontsml);\r\n\tgui.srcRandRange = Slider(gui.src, fdrsize);\r\n\tgui.srcRandRangeString = StaticText(gui.src, Point(30,24)).string_(\"0.0\").font_(fontsml);\r\n\r\n\tStaticText(gui.src, txtsize).string_(\"Speed\").font_(fontsml);\r\n\tgui.srcRandSpeed = Slider(gui.src, fdrsize);\r\n\tgui.srcRandSpeedString = StaticText(gui.src, Point(30,24)).string_(\"0.0\").font_(fontsml);\r\n\r\n\t// ----- position mirroring\r\n\tStaticText(gui.src, txtsize).string_(\"Mirror\").font_(fontsml);\r\n\r\n\tButton(gui.src, Point(41,24)).states_([[\" x \"]]).font_(fontsml)\r\n\t.action_({arg view; mirrorXYC.value(1)});\r\n\r\n\tButton(gui.src, Point(41,24)).states_([[\" y \"]]).font_(fontsml)\r\n\t.action_({arg view; mirrorXYC.value(2)});\r\n\r\n\tButton(gui.src, Point(41,24)).states_([[\" c \"]]).font_(fontsml)\r\n\t.action_({arg view; mirrorXYC.value(3)});\r\n\r\n\t// ----- show src position\r\n\tStaticText(gui.src, txtsize).string_(\"Pos\").font_(fontsml);\r\n\tgui.srcposX = StaticText(gui.src, btnsize).string_(\"X: 0.0 m\").font_(fontsml);\r\n\tgui.srcposY = StaticText(gui.src, btnsize).string_(\"Y: 0.0 m\").font_(fontsml);\r\n\tStaticText(gui.src, txtsize).string_(\"\").font_(fontsml);\r\n\tgui.srcposDeg = StaticText(gui.src, btnsize).string_(\"A: 0.0 °\").font_(fontsml);\r\n\tgui.srcposDist = StaticText(gui.src, btnsize).string_(\"D: 0.0 m\").font_(fontsml);\r\n\r\n\r\n\t// ----- duplicate/remove\r\n\t/*gui.srcCopy = Button(gui.src, Point(105,24))\r\n\t.string_(\"Duplicate Src\").font_(fontsml)\r\n\t.action_({arg view; copysrc.value(selected)});\r\n\r\n\tgui.srcRemove = Button(gui.src, Point(105,24))\r\n\t.string_(\"Remove Src\").font_(fontsml)\r\n\t.action_({arg view; removesrc.value});*/\r\n\r\n\r\n\t// ----- space view for source positioning\r\n\tgui.space.drawFunc_({ arg view;\r\n\t\tvar bounds = view.bounds.moveTo(0,0);\r\n\r\n\t\t// cross at center:\r\n\t\tPen.width = 1;\r\n\t\tPen.strokeColor = colors[0];\r\n\t\tPen.line(Point(guipoints/2 - 15, guipoints/2), Point(guipoints/2 + 15, guipoints/2));\r\n\t\tPen.line(Point(guipoints/2, guipoints/2 - 15), Point(guipoints/2, guipoints/2 + 15));\r\n\t\tPen.stroke;\r\n\r\n\t\t// roomfx threshold:\r\n\t\tif(showroomsens){\r\n\t\t\tPen.width = 1;\r\n\t\t\tPen.strokeColor = colors[0];\r\n\t\t\tPen.strokeOval(Rect.aboutPoint(pos2point.value(Point(0, 0)), roomsens*guipoints/2, roomsens*guipoints/2));\r\n\t\t};\r\n\r\n\t\t// loudspeakers:\r\n\t\tif(showls){\r\n\t\t\tPen.fillColor =  Color.red(0.9);//colors[0];\r\n\t\t\tlspos.size.do{ arg i;\r\n\t\t\t\tPen.fillRect(Rect.aboutPoint(pos2point.value(lspos[i]), 8, 8));\r\n\t\t\t\tPen.stringCenteredIn((i+1).asString, Rect.aboutPoint(pos2point.value(lspos[i]), 8, 8), Font(size: 10), Color.black);\r\n\t\t\t};\r\n\t\t};\r\n\t\t// sources:\r\n\t\tif(sources.size > 0){\r\n\t\t\tsources.pairsDo { arg key, src, i;\r\n\t\t\t\tPen.fillColor = src[\\state].if( { scolors[src[\\grpid]] }, { Color.grey(0.7) } );\r\n\t\t\t\tPen.fillOval(src[\\rect]);\r\n\t\t\t\tif(showlbl){\r\n\t\t\t\t\tPen.stringAtPoint(src[\\label], src[\\rect].rightBottom - Point(4, 4));\r\n\t\t\t\t};\r\n\t\t\t\tPen.fillColor = Color.black;\r\n\t\t\t\tPen.stringCenteredIn(src[\\id].asString, src[\\rect]);\r\n\r\n\t\t\t};\r\n\t\t};\r\n\t\t// source marker:\r\n\t\tif(selected.notNil) {\r\n\t\t\tPen.width = 4;\r\n\t\t\tPen.strokeColor = Color.red;\r\n\t\t\tPen.strokeOval(sources[selected][\\rect]);\r\n\t\t};\r\n\r\n\t})\r\n\t// action when source is clicked:\r\n\t.mouseDownAction_({ arg view, x, y, modifiers, buttonNumber, clickCount;\r\n\t\tmousepoint = Point(x,y);\r\n\t\toldmspoint = mousepoint;\r\n\t\tclicked = nil;\r\n\t\tsources.pairsDo { arg key, src, i;  if (src[\\rect].containsPoint(mousepoint)) { clicked = key } };\r\n\t\tif(clicked.notNil) {\r\n\t\t\tmousedist = (mousepoint - (sources[clicked][\\rect].origin));\r\n\t\t\tif(clicked != selected) {\r\n\t\t\t\tselected = clicked;\r\n\t\t\t\teditsrc.value(selected);\r\n\t\t\t};\r\n\r\n\t\t\tif(sources[clicked][\\grpid] > 0 && modifiers.isShift){\r\n\t\t\t\tsources.pairsDo{ arg key, src, i;\r\n\t\t\t\t\tif(src[\\grpid] == sources[clicked][\\grpid]){\r\n\t\t\t\t\t\tif(modifiers.isAlt){ srcstate.value(key) };\r\n\t\t\t\t\t\tif(modifiers.isCtrl){ srcsolo.value(key) };\r\n\t\t\t\t\t};\r\n\t\t\t\t};\r\n\t\t\t\teditsrc.value(clicked);\r\n\t\t\t}{\r\n\t\t\t\tif(modifiers.isAlt) { srcstate.value(clicked); editsrc.value(clicked) };\r\n\t\t\t\tif(modifiers.isCtrl) { srcsolo.value(clicked); editsrc.value(clicked) };\r\n\t\t\t};\r\n\t\t};\r\n\t})\r\n\t// action when source is moved:\r\n\t.mouseMoveAction_({ arg view, x, y, modifiers;\r\n\t\tvar bounds = view.bounds.moveTo(0,0);\r\n\t\tif (clicked.notNil){\r\n\t\t\tmousepoint = Point(x.clip(bounds.left, bounds.right), y.clip(bounds.top, bounds.bottom));\r\n\r\n\t\t\tif(sources[clicked][\\grpid] > 0 and: modifiers.isShift){ //move group\r\n\r\n\t\t\t\tsources.pairsDo{ arg key, src, i;\r\n\r\n\t\t\t\t\tif(src[\\grpid] == sources[clicked][\\grpid]){\r\n\t\t\t\t\t\tsrc[\\rect] = src[\\rect].translate(mousepoint - oldmspoint);\r\n\t\t\t\t\t\trect2pos.value(key);\r\n\t\t\t\t\t\tpos2drive.value(key);\r\n\t\t\t\t\t};\r\n\t\t\t\t};\r\n\r\n\t\t\t\toldmspoint = mousepoint;\r\n\t\t\t}{ //move single source\r\n\t\t\t\tsources[clicked][\\rect] = sources[clicked][\\rect].moveToPoint(mousepoint - mousedist);\r\n\t\t\t\trect2pos.value(clicked);\r\n\t\t\t\tpos2drive.value(clicked);\r\n\t\t\t};\r\n\r\n\t\t\tview.action.value(this, x, y, modifiers);\r\n\t\t};\r\n\t})\r\n\t// action when mousebutton is released:\r\n\t.mouseUpAction_({ arg view, x, y, modifiers;\r\n\t\tclicked = nil;\r\n\t});\r\n\t// end of spaceview\r\n\r\n\t// ----- option panel\r\n\tgui.sessnMenu = Menu(\r\n\t\tMenuAction(\"Load\", { loadprj.value }),\r\n\t\tMenuAction(\"Save\", { saveprj.value }),\r\n\t\tMenuAction(\"Help\", { gethelp.value }),\r\n\t\tMenuAction(\"Quit\", { gui.win.close }),\r\n\t);\r\n\r\n\tgui.optnMenu = Menu(\r\n\t\tMenuAction(\"Show Loudspeakers\")\r\n\t\t.checkable_(true)\r\n\t\t.checked_(showls)\r\n\t\t.action_({arg a, checked;\r\n\t\t\tif(checked){\r\n\t\t\t\tshowls = true;\r\n\t\t\t}{\r\n\t\t\t\tshowls = false;\r\n\t\t\t};\r\n\t\t}),\r\n\r\n\t\tMenuAction(\"Show Labels\")\r\n\t\t.checkable_(true)\r\n\t\t.checked_(showlbl)\r\n\t\t.action_({arg a, checked;\r\n\t\t\tif(checked){\r\n\t\t\t\tshowlbl = true;\r\n\t\t\t}{\r\n\t\t\t\tshowlbl = false;\r\n\t\t\t};\r\n\t\t}),\r\n\r\n\t\tMenuAction(\"Show Roomsens\")\r\n\t\t.checkable_(true)\r\n\t\t.checked_(showroomsens)\r\n\t\t.action_({arg a, checked;\r\n\t\t\tif(checked){\r\n\t\t\t\tshowroomsens = true;\r\n\t\t\t}{\r\n\t\t\t\tshowroomsens = false;\r\n\t\t\t};\r\n\t\t});\r\n\r\n\t);\r\n\r\n\tgui.sessn = Button(gui.opt, Point(105, 24)).string_(\"Session..\").font_(fontsml)\r\n\t.action_({arg view;\r\n\t\tgui.sessnMenu.front;\r\n\t});\r\n\r\n\tgui.optn = Button(gui.opt, Point(105, 24)).string_(\"View..\").font_(fontsml)\r\n\t.action_({arg view;\r\n\t\tgui.optnMenu.front;\r\n\t});\r\n\r\n\tgui.showsnaplist = Button(gui.opt, Point(105, 24)).string_(\"Snapshots\").font_(fontsml)\r\n\t.action_({arg view;\r\n\t\tif(gui.snaplist.visible == false) {\r\n\t\t\tgui.snaplist.visible = true;\r\n\t\t\tgui.snaplist.bounds = gui.snaplist.bounds.moveToPoint(Point(gui.win.bounds.rightBottom.x,\r\n\t\t\t\tgui.win.bounds.rightBottom.y - gui.snaplist.bounds.extent.y));\r\n\t\t}{\r\n\t\t\tgui.snaplist.visible = false };\r\n\t});\r\n\r\n\tgui.showsettings = Button(gui.opt, Point(105, 24)).string_(\"FX Settings\").font_(fontsml)\r\n\t.action_({arg view;\r\n\t\tif(gui.fxpanel.visible == false) {\r\n\t\t\tgui.fxpanel.visible = true;\r\n\t\t\tgui.fxpanel.bounds = gui.fxpanel.bounds.moveToPoint(gui.win.bounds.rightTop);\r\n\t\t}{\r\n\t\t\tgui.fxpanel.visible = false };\r\n\t});\r\n\r\n\tgui.showmeters = Button(gui.opt, Point(105, 24)).string_(\"Out Meters\").font_(fontsml)\r\n\t.action_({arg view;\r\n\t\tif(gui.meters.visible == false) {\r\n\t\t\tm = ServerMeterView.new(s, gui.meters, Point(0,0), 0, s.options.numOutputBusChannels);\r\n\t\t\tgui.meters.visible = true;\r\n\t\t\tgui.meters.bounds = gui.meters.bounds.moveToPoint(gui.win.bounds.rightTop);\r\n\t\t}{\r\n\t\t\tgui.meters.visible = false;\r\n\t\t\tm.free;\r\n\t\t};\r\n\r\n\t});\r\n\r\n\tgui.na2 = Button(gui.opt, Point(105, 24)).string_(\"n/a\").font_(fontsml)\r\n\t.action_({arg view;\r\n\t});\r\n\r\n\r\n\t// ----- master\r\n\tStaticText(gui.mstr, txtsize).string_(\"Master\").font_(fontsml);\r\n\tgui.mstrlevel = Slider(gui.mstr, fdrsize).value_(1)\r\n\t.action_({arg view;\r\n\t\tmaster.set(\\amp, view.value);\r\n\t\tgui.mstrlevelnum.string_(view.value.ampdb.round(0.1).asString);\r\n\t});\r\n\r\n\tgui.mstrlevelnum = StaticText(gui.mstr, Point(30, 24)).string_(\"0\").font_(fontsml);\r\n\r\n\tStaticText(gui.mstr, txtsize).string_(\"Out\").font_(fontsml);\r\n\tgui.masterBus = NumberBox(gui.mstr, btnsize).value_(masterout).font_(fontsml)\r\n\t.action_({arg view;\r\n\t\tview.value = view.value.clip(0, 63);\r\n\t\tmaster.set(\\out, view.value);\r\n\t});\r\n\r\n\tgui.mstrMute = Button(gui.mstr, btnsize).font_(fontsml)\r\n\t.states_([ [\"Mute\", Color.black, Color.red ], [\"Mute\", Color.black, Color.gray(0.9)] ])\r\n\t.value_(1)\r\n\t.action_({arg view;\r\n\t\tmaster.set(\\gate, view.value);\r\n\t});\r\n\r\n\t// ----- fx settings window\r\n\tStaticText(gui.fxpanel, 260@20).string_(\"::: GENERAL :::\").align_(\\center);\r\n\r\n\tgui.roomsens = EZSlider.new(gui.fxpanel, 260@20 ,\"Rm Sens\",ControlSpec(0.1, 0.9,\\lin, 0.1, roomsens),\r\n\t\t{arg view;\r\n\t\t\troomsens = view.value;\r\n\t\t\tif(sources.size > 0){\r\n\t\t\t\tsources.pairsDo({arg key, src, i;\r\n\t\t\t\t\tsrc[\\node].set(\\roomsens, view.value);\r\n\t\t\t\t});\r\n\t\t\t};\r\n\t});\r\n\r\n\tgui.roomfreq = EZSlider.new(gui.fxpanel, 260@20 ,\"Rm LPF\",ControlSpec(200, 12000, 'exp', 1, roomfreq),\r\n\t\t{arg view;\r\n\t\t\troomfreq = view.value;\r\n\t\t\tif(sources.size > 0){\r\n\t\t\t\tsources.pairsDo({arg key, src, i;\r\n\t\t\t\t\tsrc[\\node].set(\\roomfreq, view.value);\r\n\t\t\t\t});\r\n\t\t\t};\r\n\t});\r\n\r\n\tgui.roomdamp = EZSlider.new(gui.fxpanel, 260@20 ,\"Rm Damp\",ControlSpec(-3, -24, \\lin, 1, roomdamp),\r\n\t\t{arg view;\r\n\t\t\troomdamp = view.value;\r\n\t\t\tif(sources.size > 0){\r\n\t\t\t\tsources.pairsDo({arg key, src, i;\r\n\t\t\t\t\tsrc[\\node].set(\\roomdamp, view.value.dbamp);\r\n\t\t\t\t});\r\n\t\t\t};\r\n\t});\r\n\r\n\t// ----- reverb\r\n\tStaticText(gui.fxpanel, 260@20).string_(\"::: REVERB :::\").align_(\\center);\r\n\r\n\tgui.revlevel = EZSlider.new(gui.fxpanel, 260@20 ,\"Rev Level\",ControlSpec(-60, 0, \\lin, 1, -6),\r\n\t\t{arg view;\r\n\t\t\treverb.do{arg it;\r\n\t\t\t\tit.set(\\amp, view.value.dbamp)\r\n\t\t\t}\r\n\t});\r\n\r\n\tgui.revtime = EZSlider.new(gui.fxpanel, 260@20 ,\"Time\",ControlSpec(1.0, 5.0, \\lin, 0.1, 1.7),\r\n\t\t{arg view;\r\n\t\t\treverb.do{arg it;\r\n\t\t\t\tit.set(\\rt, view.value)\r\n\t\t\t}\r\n\t});\r\n\r\n\tgui.revlf = EZSlider.new(gui.fxpanel, 260@20 ,\"LF Ratio\",ControlSpec(0.5, 1.5, \\lin, 0.1, 1.0),\r\n\t\t{arg view;\r\n\t\t\treverb.do{arg it;\r\n\t\t\t\tit.set(\\lfratio, view.value)\r\n\t\t\t}\r\n\t});\r\n\r\n\tgui.revhf = EZSlider.new(gui.fxpanel, 260@20 ,\"HF Ratio\",ControlSpec(0.5, 1.5, \\lin, 0.1, 1.0),\r\n\t\t{arg view;\r\n\t\t\treverb.do{arg it;\r\n\t\t\t\tit.set(\\hfratio, view.value)\r\n\t\t\t}\r\n\t});\r\n\r\n\t// ----- delay\r\n\tStaticText(gui.fxpanel, 260@20).string_(\"::: DELAY :::\").align_(\\center);\r\n\r\n\tgui.dlylevel = EZSlider.new(gui.fxpanel, 260@20,\"Dly Level\",ControlSpec(-60, 0, \\lin, 1, -6),\r\n\t\t{arg view;\r\n\t\t\tdelay.do{arg it;\r\n\t\t\t\tit.set(\\amp, view.value.dbamp)\r\n\t\t\t}\r\n\t});\r\n\r\n\tgui.dlytime = EZSlider.new(gui.fxpanel, 260@20,\"Dly T\",ControlSpec(0.1, 1.1, \\lin, 0.01, 0.3),\r\n\t\t{arg view;\r\n\t\t\tdelay.do{arg it;\r\n\t\t\t\tit.set(\\dly, view.value)\r\n\t\t\t}\r\n\t});\r\n\r\n\tgui.dlyfb = EZSlider.new(gui.fxpanel, 260@20,\"Dly FB\",ControlSpec(0.1, 1, \\lin, 0.1, 0.7),\r\n\t\t{arg view;\r\n\t\t\tdelay.do{arg it;\r\n\t\t\t\tit.set(\\fb, view.value)\r\n\t\t\t}\r\n\t});\r\n\r\n\r\n\t// ---- snapshot window\r\n\tgui.snapshots = ListView(gui.snaplist, Rect(5, 5, 275, 320))\r\n\t.background_(Color.gray(0.9))\r\n\t.font_(fontsml)\r\n\t.hiliteColor_(Color.yellow(alpha: 0.6))\r\n\t.selectedStringColor_(Color.black)\r\n\t.items_([ ])\r\n\t.selectionMode_(\\single)\r\n\t.action_({arg view;\r\n\t\t//recallsnap.value;\r\n\t});\r\n\r\n\tgui.activeSnap = StaticText(gui.snaplist, Rect(5, gui.snapshots.bounds.leftBottom.y+5, gui.snapshots.bounds.width, 35))\r\n\t.backColor_(Color.white)\r\n\t.string_(\" >> \")\r\n\t.font_(fontlrg);\r\n\r\n\tgui.deleteSnap = Button(gui.snaplist, Rect(5, gui.snapshots.bounds.leftBottom.y+45, 65, 35)).string_(\"Delete\")\r\n\t.action_({arg view;\r\n\t\tif(gui.snapshots.items.size > 0){ gui.snapshots.items = gui.snapshots.items.removing(gui.snapshots.item) };\r\n\t});\r\n\r\n\tgui.makeSnap = Button(gui.snaplist, Rect(75, gui.snapshots.bounds.leftBottom.y+45, 65, 35)).string_(\"Make\")\r\n\t.action_({arg view;\r\n\t\tmakesnap.value;\r\n\t});\r\n\r\n\tgui.recallSnap = Button(gui.snaplist, Rect(145, gui.snapshots.bounds.leftBottom.y+45, 65, 35)).string_(\"Recall\")\r\n\t.action_({arg view;\r\n\t\trecallsnap.value;\r\n\t\tgui.activeSnap.string = \" >> \"++gui.snapshots.item;\r\n\t});\r\n\r\n\tgui.nextSnap = Button(gui.snaplist, Rect(215, gui.snapshots.bounds.leftBottom.y+45, 65, 35)).string_(\"Next\")\r\n\t.action_({arg view;\r\n\t\tgui.snapshots.value = (gui.snapshots.value + 1).clip(0, gui.snapshots.items.maxIndex);\r\n\t\trecallsnap.value;\r\n\t\tgui.activeSnap.string = \" >> \"++gui.snapshots.item;\r\n\t});\r\n\r\n\tgui.xactive = Button(gui.snaplist, Rect(5, gui.snapshots.bounds.leftBottom.y+85, 65, 25))\r\n\t.states_([ [\"X [sec]\", Color.black, Color.grey(0.9) ], [\"X [sec]\", Color.black, Color.cyan] ])\r\n\t.value_(1)\r\n\t.action_({arg view; xfade = view.value});\r\n\r\n\tgui.xtime = NumberBox(gui.snaplist, Rect(75, gui.snapshots.bounds.leftBottom.y+85, 30, 25))\r\n\t.value_(xfadetime)\r\n\t.align_(\\center)\r\n\t.action_({arg view; view.value = view.value.clip(1, 10); xfadetime = view.value });\r\n\r\n};// ----- end of GUI\r\n\r\n\r\n// ----- create new source\r\nnewsrc = { var id, src, point;\r\n\r\n\tif(sources.size < 1){\r\n\t\tgui.src.enabled_(true);\r\n\t\tid = 1}{\r\n\t\tid = sources.keys.maxItem + 1};\r\n\r\n\tsrc = Dictionary[\r\n\t\t\\id -> id,\r\n\t\t\\label -> id.asString,\r\n\t\t\\inbus -> 100,\r\n\t\t\\rect -> nil,\r\n\t\t\\pos -> Point(0.2.rand2, 0).round(0.01),\r\n\t\t\\grpid -> 0,\r\n\t\t\\room -> 1,\r\n\t\t\\dodelays -> 0,\r\n\t\t\\focus -> 0,\r\n\t\t\\amp -> 0,\r\n\t\t\\solo -> false,\r\n\t\t\\rotastate -> false,\r\n\t\t\\rota -> nil,\r\n\t\t\\rotaspeed -> 0.04,\r\n\t\t\\rotadir -> -1,\r\n\t\t\\randstate -> false,\r\n\t\t\\rand -> nil,\r\n\t\t\\randrange -> 1,\r\n\t\t\\randspeed -> 1,\r\n\t\t\\aux -> 0,\r\n\t\t\\rev -> 1,\r\n\t\t\\dly -> 0,\r\n\t\t\\node -> nil,\r\n\t\t\\state -> true\r\n\t];\r\n\r\n\tsrc[\\rect] = pos2rect.value(src[\\pos]);\r\n\tsources.add(id -> src);\r\n\tselected = id;\r\n\teditsrc.value(selected);\r\n\tsrcon.value(selected);\r\n};\r\n\r\n// ----- start the src processing synth\r\nsrcon = { arg id;\r\n\tvar src;\r\n\tsrc = sources[id];\r\n\r\n\tsrc[\\node] = Synth(\"spacepan\", [\r\n\t\t\\inbus, src[\\inbus],\r\n\t\t\\out, masterbus,\r\n\t\t\\revbus, revbus,\r\n\t\t\\dlybus, dlybus,\r\n\t\t\\auxbus, auxbus,\r\n\t\t\\amp, src[\\amp],\r\n\t\t\\dodelays, src[\\dodelays],\r\n\t\t\\rev, src[\\rev],\r\n\t\t\\dly, src[\\dly],\r\n\t\t\\aux, src[\\aux],\r\n\t\t\\room, src[\\room],\r\n\t\t\\roomsens, roomsens,\r\n\t\t\\roomdamp, roomdamp.dbamp,\r\n\t\t\\roomfreq, roomfreq\r\n\t], srcgroup);\r\n\tpos2drive.value(id);\r\n};\r\n\r\n\r\n// ----- edit selected source settings\r\neditsrc = {arg selected;\r\n\tvar src;\r\n\tsrc = sources[selected];\r\n\tsrclevel.value;\r\n\ttracklevel.set(\\inbus, src[\\inbus]);\r\n\r\n\tgui.srcID\r\n\t.string_(src[\\id].asString);\r\n\r\n\tgui.srcLabel\r\n\t.value_(src[\\label])\r\n\t.action_({arg view;\r\n\t\tsrc[\\label]\t= view.value[..7];\r\n\t});\r\n\r\n\tgui.srcGrp\r\n\t.background_(scolors[src[\\grpid]])\r\n\t.value_(src[\\grpid])\r\n\t.action_({ arg view;\r\n\t\tview.background = scolors[view.value];\r\n\t\tsrc[\\grpid] = view.value;\r\n\t});\r\n\r\n\tgui.srcInput\r\n\t.value_(src[\\inbus])\r\n\t.action_({arg view;\r\n\t\tsrc[\\inbus] = view.value;\r\n\t\tsrc[\\node].set(\\inbus, src[\\inbus]);\r\n\t\ttracklevel.set(\\inbus, src[\\inbus], \\vol, src[\\vol]);\r\n\t});\r\n\r\n\tgui.srcVol\r\n\t.value_(src[\\amp])\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\amp] = view.value;\r\n\t\t\t\t\tsc[\\node].set(\\amp, sc[\\amp]);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\amp] = view.value;\r\n\t\t\tsrc[\\node].set(\\amp, src[\\amp]);\r\n\t\t};\r\n\r\n\t\tgui.srcVolnum.string = src[\\amp].ampdb.round(0.1).asString;\r\n\r\n\t});\r\n\r\n\tgui.srcVolnum\r\n\t.string_(src[\\amp].ampdb.round(0.1).asString);\r\n\r\n\tgui.srcMute\r\n\t.value_(src[\\state])\r\n\t.action_({arg view, mod;\r\n\t\tif(src[\\grpid] > 0 && mod.isShift){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsrcstate.value(sc[\\id])\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{ srcstate.value(selected) }\r\n\t});\r\n\r\n\tgui.srcSolo\r\n\t.value_(src[\\solo])\r\n\t.action_({arg view, mod;\r\n\t\tif(src[\\grpid] > 0 && mod.isShift){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsrcsolo.value(sc[\\id])\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{ srcsolo.value(selected) }\r\n\r\n\t});\r\n\r\n\tgui.srcPanmode\r\n\t.value_(src[\\dodelays])\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\dodelays] = view.value;\r\n\t\t\t\tsc[\\node].set(\\dodelays, sc[\\dodelays]) };\r\n\t\t}{\r\n\t\t\tsrc[\\dodelays] = view.value;\r\n\t\t\tsrc[\\node].set(\\dodelays, src[\\dodelays]) };\r\n\t\t};\r\n\t});\r\n\r\n\tgui.srcFocus\r\n\t.value_(focusSpec.unmap(src[\\focus]))\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\focus] = focusSpec.map(view.value);\r\n\t\t\t\t\tpos2drive.value(sc[\\id]);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\focus] = focusSpec.map(view.value);\r\n\t\t\tpos2drive.value(selected);\r\n\t\t};\r\n\r\n\t\tgui.srcFocusnum.string = view.value.asString;\r\n\t});\r\n\r\n\tgui.srcFocusnum.string = focusSpec.unmap(src[\\focus]).asString;\r\n\r\n\tgui.srcRoom\r\n\t.value_(src[\\room])\r\n\t.action_({arg view;\r\n\t\tsrc[\\room] = view.value;\r\n\t\tsrc[\\node].set(\\room, src[\\room]);\r\n\t});\r\n\r\n\tgui.srcRev\r\n\t.value_(src[\\rev])\r\n\t.action_({arg view;\r\n\t\tsrc[\\rev] = view.value;\r\n\t\tgui.srcRevnum.string = src[\\rev].ampdb.round(0.1).asString;\r\n\t\tsrc[\\node].set(\\rev, src[\\rev]);\r\n\t});\r\n\r\n\tgui.srcRevnum.string_(src[\\rev].ampdb.round(0.1).asString);\r\n\r\n\tgui.srcDly\r\n\t.value_(src[\\dly])\r\n\t.action_({arg view;\r\n\t\tsrc[\\dly] = view.value;\r\n\t\tgui.srcDlynum.string = src[\\dly].ampdb.round(0.1).asString;\r\n\t\tsrc[\\node].set(\\dly, src[\\dly]);\r\n\t});\r\n\r\n\tgui.srcDlynum.string_(src[\\dly].ampdb.round(0.1).asString);\r\n\r\n\tgui.srcAux\r\n\t.value_(src[\\aux])\r\n\t.action_({arg view;\r\n\t\tsrc[\\aux] = view.value;\r\n\t\tgui.srcAuxnum.string = src[\\aux].ampdb.round(0.1).asString;\r\n\t\tsrc[\\node].set(\\aux, src[\\aux]);\r\n\t});\r\n\r\n\tgui.srcAuxnum.string_(src[\\aux].ampdb.round(0.1).asString);\r\n\r\n\tgui.srcRota\r\n\t.value_(src[\\rotastate].asInteger)\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\rotastate] = view.value.asBoolean;\r\n\t\t\t\t\tif(sc[\\randstate]){\r\n\t\t\t\t\t\tsc[\\randstate] = false;\r\n\t\t\t\t\t\tautorand.value(key);\r\n\t\t\t\t\t\tgui.srcRand.value_(0);\r\n\t\t\t\t\t};\r\n\t\t\t\t\tautorota.value(key);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\rotastate] = view.value.asBoolean;\r\n\t\t\tif(src[\\randstate]){\r\n\t\t\t\tgui.srcRand.value_(0);\r\n\t\t\t\tsrc[\\randstate] = false;\r\n\t\t\t\tautorand.value(selected);\r\n\t\t\t};\r\n\t\t\tautorota.value(selected);\r\n\t\t};\r\n\t});\r\n\r\n\tgui.srcRotaSpeed\r\n\t.value_(rotaspeedSpec.unmap(src[\\rotaspeed]))\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\rotaspeed] = rotaspeedSpec.map(view.value);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\rotaspeed] = rotaspeedSpec.map(view.value);\r\n\t\t};\r\n\t\tgui.srcRotaSpeedString.string = view.value.round(0.01).asString;\r\n\t});\r\n\r\n\tgui.srcRotaSpeedString.string = gui.srcRotaSpeed.value.round(0.01).asString;\r\n\r\n\tgui.srcRotaDir\r\n\t.value_(src[\\rotadir].linlin(-1, 1, 0, 1))\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\rotadir] = view.value.linlin(0, 1, -1, 1);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\rotadir] = view.value.linlin(0, 1, -1, 1);\r\n\t\t};\r\n\t});\r\n\r\n\tgui.srcRand\r\n\t.value_(src[\\randstate].asInteger)\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\randstate] = view.value.asBoolean;\r\n\t\t\t\t\tif(sc[\\rotastate]){\r\n\t\t\t\t\t\tsc[\\rotastate] = false;\r\n\t\t\t\t\t\tautorota.value(key);\r\n\t\t\t\t\t\tgui.srcRota.value_(0);\r\n\t\t\t\t\t};\r\n\t\t\t\t\tautorand.value(key);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\randstate] = view.value.asBoolean;\r\n\t\t\tif(src[\\rotastate]){\r\n\t\t\t\tgui.srcRota.value_(0);\r\n\t\t\t\tsrc[\\rotastate] = false;\r\n\t\t\t\tautorota.value(selected);\r\n\t\t\t};\r\n\t\t\tautorand.value(selected);\r\n\t\t};\r\n\t});\r\n\r\n\tgui.srcRandRange\r\n\t.value_(randrangeSpec.unmap(src[\\randrange]))\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\randrange] = randrangeSpec.map(view.value);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\randrange] = randrangeSpec.map(view.value);\r\n\t\t};\r\n\t\tgui.srcRandRangeString.string = view.value.round(0.01).asString;\r\n\t});\r\n\r\n\tgui.srcRandRangeString.string = gui.srcRandRange.value.round(0.01).asString;\r\n\r\n\tgui.srcRandSpeed\r\n\t.value_(randspeedSpec.unmap(src[\\randspeed]))\r\n\t.action_({arg view;\r\n\t\tif(src[\\grpid] > 0){\r\n\t\t\tsources.pairsDo{arg key, sc, i;\r\n\t\t\t\tif(sc[\\grpid] == src[\\grpid]){\r\n\t\t\t\t\tsc[\\randspeed] = randspeedSpec.map(view.value);\r\n\t\t\t\t};\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\tsrc[\\randspeed] = randspeedSpec.map(view.value);\r\n\t\t};\r\n\t\tgui.srcRandSpeedString.string = view.value.round(0.01).asString;\r\n\t});\r\n\r\n\tgui.srcRandSpeedString.string = gui.srcRandSpeed.value.round(0.01).asString;\r\n};\r\n\r\n// ----- mute source\r\nsrcstate = {arg id;\r\n\tvar src;\r\n\tsrc = sources[id];\r\n\tif(src[\\state]){\r\n\t\tsrc[\\node].set(\\gate, 0); src[\\state] = false} {\r\n\t\tsrc[\\node].set(\\gate, 1); src[\\state] = true };\r\n};\r\n\r\n// ----- solo source\r\nsrcsolo = {arg id;\r\n\tif(sources[id][\\solo] == false){\r\n\t\tsources[id][\\solo] = true;\r\n\t\tsolostate = solostate +1;\r\n\t\t(\"!!! SOLO SOURCE \"++id.asString).postln;\r\n\t}{\r\n\t\tsources[id][\\solo] = false;\r\n\t\tsolostate = solostate -1;\r\n\t};\r\n\r\n\tif(solostate > 0){\r\n\t\tsources.do{arg src;\r\n\t\t\tif(src[\\solo] == true && src[\\state] == true){\r\n\t\t\t\tsrc[\\node].set(\\gate, 1)\r\n\t\t\t}{ src[\\node].set(\\gate, 0) };\r\n\t\t};\r\n\t}{\r\n\t\tsources.do{arg src;\r\n\t\t\tif(src[\\state] == true){\r\n\t\t\t\tsrc[\\node].set(\\gate, 1)\r\n\t\t\t}\r\n\t\t};\r\n\t\t(\"!!! SOLO OFF\").postln;\r\n\t};\r\n\r\n};\r\n\r\n// ----- mirror source pos\r\nmirrorXYC = {arg mode;\r\n\tvar src;\r\n\tsrc = sources[selected];\r\n\tif(mode == 1){src[\\pos] = src[\\pos].mirrorX};\r\n\tif(mode == 2){src[\\pos] = src[\\pos].mirrorY};\r\n\tif(mode == 3){src[\\pos] = src[\\pos].mirrorO};\r\n\tsrc[\\rect] = pos2rect.value(src[\\pos]);\r\n\tpos2drive.value(selected);\r\n};\r\n\r\n// ----- update spacepan synth according to src gui position\r\npos2drive = {arg id;\r\n\tvar src, dists, distance, amps, delays, pan;\r\n\tsrc = sources[id];\r\n\r\n\t//s.bind{\r\n\tdists = lspos.collect{arg i; src[\\pos].dist(i).clip(0.1, roomdim)}; //clip dist to prevent div by zero\r\n\tdelays = dists / speedofsound; //delays src->ls\r\n\tamps = dists.minItem / dists; //amp factors based on dist\r\n\tamps = amps.lincurve(amps.minItem, 1, 0, 1, src[\\focus]); //focus windowing\r\n\tamps = amps / amps.squared.sum.sqrt; //equal power\r\n\tdistance = src[\\pos].rho / roomscale; //distance for roomfx\r\n\tpan = pos2pan.value(src[\\pos]); //4ch fx send pan\r\n\r\n\t//s.sync;\r\n\tsrc[\\node].set(\r\n\t\t\\delays, delays,\r\n\t\t\\amps, amps,\r\n\t\t\\distance, distance,\r\n\t\t\\pan, pan,\r\n\t\t\\fxdlycomp, delays.minItem\r\n\t)\r\n\t//}\r\n};\r\n\r\n// ----- convert position to pan\r\npos2pan = {arg pos;\r\n\t[pos.x.linlin(roomscale.neg, roomscale, fxpan.neg, fxpan), pos.y.linlin(roomscale.neg, roomscale, fxpan.neg, fxpan)];\r\n};\r\n\r\n// ----- convert meters to pixels\r\npos2point = {arg pos;\r\n\tPoint(pos.x.linlin(roomscale.neg, roomscale, 0, guipoints),\r\n\t\tpos.y.linlin(roomscale.neg, roomscale, guipoints, 0)).round(1.0);\r\n};\r\n\r\n// ----- calculate rect for spacegui\r\npos2rect = {arg pos;\r\n\tRect.aboutPoint(pos2point.value(pos), srcsize, srcsize);\r\n};\r\n\r\n// ----- convert pixels to meters\r\nrect2pos = {arg id;\r\n\tvar src = sources[id];\r\n\tp = src[\\rect].center;\r\n\tsrc[\\pos] = (Point(p.x.linlin(0, guipoints, roomscale.neg, roomscale),\r\n\t\tp.y.linlin(0, guipoints, roomscale, roomscale.neg))).round(0.01);\r\n};\r\n\r\n// ----- convert pos to normalized degrees (-180..+180)\r\npos2angle = {arg pos;\r\n\tpos = pos.rotate(-0.5pi);\r\n\tpos.theta.raddeg.neg.round(0.1);\r\n};\r\n\r\n// ----- osc function to feed src levelmeter\r\nsrclevel = {\r\n\tOSCFunc({arg msg;\r\n\t\t{\r\n\t\t\tgui.srcMeter.value = msg[3].ampdb.linlin(-60, 0, 0, 1);\r\n\t\t}.defer;\r\n\t},'/level', s.addr);\r\n};\r\n\r\n// ----- automation: rotation\r\nautorota = {arg id;\r\n\tvar src = sources[id];\r\n\tif(src[\\rotastate]){\r\n\t\tsrc[\\rota] = Task({\r\n\t\t\tinf.do({arg i;\r\n\t\t\t\t{src[\\pos] = src[\\pos].rotate(rotastepSpec.map(rotaspeedSpec.unmap(src[\\rotaspeed])) * src[\\rotadir]);\r\n\t\t\t\t\t//src[\\pos] = src[\\pos].rotate(0.015 * src[\\rotadir]);\r\n\t\t\t\t\tpos2drive.value(src[\\id]);\r\n\t\t\t\t\tsrc[\\rect] = pos2rect.value(src[\\pos]);\r\n\t\t\t\t}.defer;\r\n\t\t\t\t(src[\\rotaspeed]).wait;\r\n\t\t\t});\r\n\t\t}).start\r\n\t}{\r\n\t\tsrc[\\rota].stop;\r\n\t};\r\n};\r\n\r\n// ----- automation: random walk\r\nautorand = {arg id;\r\n\tvar src = sources[id];\r\n\tif(src[\\randstate]){\r\n\t\tsrc[\\rand] = Task({\r\n\t\t\tinf.do({arg i;\r\n\t\t\t\t{src[\\pos] = (src[\\pos].translate(Point(src[\\randrange].rand2,\r\n\t\t\t\t\tsrc[\\randrange].rand2).round(0.01))).clip(roomscale.neg, roomscale);\r\n\t\t\t\t\tsrc[\\rect] = pos2rect.value(src[\\pos]);\r\n\t\t\t\t\tpos2drive.value(src[\\id]);\r\n\t\t\t\t}.defer;\r\n\t\t\t\t(src[\\randspeed]).wait;\r\n\t\t\t});\r\n\t\t}).start\r\n\t}{\r\n\t\tsrc[\\rand].stop;\r\n\t};\r\n\r\n};\r\n\r\n// ----- autorefresh gui\r\nautorefreshview = Task({\r\n\tloop {\r\n\t\t{ gui.space.refresh;\r\n\t\t\tif(selected.notNil){\r\n\t\t\t\tgui.srcposX.string_(\"X: \"++sources[selected][\\pos].x.round(0.01)++\" m\");\r\n\t\t\t\tgui.srcposY.string_(\"Y: \"++sources[selected][\\pos].y.round(0.01)++\" m\");\r\n\t\t\t\tgui.srcposDeg.string_(\"A: \"++pos2angle.value(sources[selected][\\pos])++\" °\");\r\n\t\t\t\tgui.srcposDist.string_(\"D: \"++sources[selected][\\pos].rho.round(0.01)++\" m\");\r\n\t\t\t};\r\n\t\t}.defer;\r\n\t\tautorefreshrate.wait;\r\n\t}\r\n});\r\n\r\n// ----- remove src\r\nremovesrc = {arg id;\r\n\tvar src;\r\n\tif(sources.size > 0){\r\n\t\tsrc = sources[id];\r\n\t\tif(src[\\rotastate]){src[\\rota].stop};\r\n\t\tif(src[\\randstate]){src[\\rand].stop};\r\n\t\tif(src[\\state]){src[\\state] = false};\r\n\t\tsrc[\\node].free;\r\n\t\tsources.removeAt(id);\r\n\t};\r\n};\r\n\r\n// ----- make snapshot\r\nmakesnap = {\r\n\tvar path, stamp, array, filename, file;\r\n\tpath = currpath++\"Snapshots/\";\r\n\tstamp = Date.getDate.format(\"%d%m%y_%H%M%S\");\r\n\tfilename = \"Snapshot_\"++stamp;\r\n\tfile = File(path ++ filename ++\".txt\", \"w\");\r\n\tsources.pairsDo{arg i; array = array.add([sources[i][\\id], sources[i][\\pos]])};\r\n\tfile.write(array.asCompileString);\r\n\tfile.close;\r\n\t{gui.snapshots.items = gui.snapshots.items.add(filename)}.defer;\r\n\r\n};\r\n\r\n// ----- recall snapshot\r\nrecallsnap = {arg view;\r\n\tvar path, file, filename, array, steps, env;\r\n\tpath = currpath++\"Snapshots/\";\r\n\tfilename = gui.snapshots.item;\r\n\tsteps = (xfadetime/xstep).asInteger;\r\n\r\n\tsources.do{ arg src;\r\n\t\tif(src[\\rotastate]){ src[\\rota].stop; src[\\rotastate] = false };\r\n\t\tif(src[\\randstate]){ src[\\rand].stop; src[\\randstate] = false };\r\n\t\teditsrc.value(selected);\r\n\t};\r\n\t{\r\n\t\tfile = File(path ++ filename ++\".txt\", \"r\");\r\n\t\tarray = file.readAllString.interpret;\r\n\r\n\t\tif(xfade == 0){\r\n\t\t\tsources.do{arg src, i;\r\n\t\t\t\tsrc[\\pos] = array[i][1];\r\n\t\t\t\tsrc[\\rect] = pos2rect.value(src[\\pos]);\r\n\t\t\t\tpos2drive.value(src[\\id]);\r\n\t\t\t};\r\n\t\t}{\r\n\t\t\t{\r\n\t\t\t\tsources.do{arg src, i;\r\n\t\t\t\t\tenv = env.add(Env.new(\r\n\t\t\t\t\t\t[src[\\pos].asArray,\r\n\t\t\t\t\t\t\tarray[i][1].asArray],\r\n\t\t\t\t\t\t[xfadetime]).discretize(steps)\r\n\t\t\t\t\t);\r\n\t\t\t\t};\r\n\r\n\t\t\t\tsources.do{arg src, it;\r\n\t\t\t\t\tTask({\r\n\t\t\t\t\t\tsteps.do({arg i;\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tsrc[\\pos].x = env[it][0][i];\r\n\t\t\t\t\t\t\t\tsrc[\\pos].y = env[it][1][i];\r\n\t\t\t\t\t\t\t\tsrc[\\rect] = pos2rect.value(src[\\pos]);\r\n\t\t\t\t\t\t\t\tpos2drive.value(src[\\id]);\r\n\t\t\t\t\t\t\t}.defer;\r\n\t\t\t\t\t\t\txstep.wait;\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}).start;\r\n\t\t\t\t};\r\n\t\t\t}.defer;\r\n\t\t};\r\n\t}.defer;\r\n};\r\n\r\n// ----- save current session\r\nsaveprj = { var array, snaps, fxparas, file;\r\n\r\n\t\tDialog.savePanel( { arg path;\r\n\t\t\tfile = File(path ++ \".txt\", \"w\");\r\n\t\t\tfxparas = [\r\n\t\t\t\tgui.roomsens.value,\r\n\t\t\t\tgui.roomfreq.value,\r\n\t\t\t\tgui.roomdamp.value,\r\n\t\t\t\tgui.revlevel.value,\r\n\t\t\t\tgui.revtime.value,\r\n\t\t\t\tgui.revhf.value,\r\n\t\t\t\tgui.revlf.value,\r\n\t\t\t\tgui.dlylevel.value,\r\n\t\t\t\tgui.dlytime.value,\r\n\t\t\t\tgui.dlyfb.value\r\n\t\t\t];\r\n\r\n\t\t\tsnaps = gui.snapshots.items;\r\n\t\t{\r\n\t\t\tsources.do{ arg src;\r\n\t\t\t\tif(src[\\rotastate]){ src[\\rota].stop; src[\\rotastate] = false };\r\n\t\t\t\tif(src[\\randstate]){ src[\\rand].stop; src[\\randstate] = false };\r\n\t\t\t\tsrc[\\node].free;\r\n\t\t\t\tsrc[\\node] = nil;\r\n\t\t\t};\r\n\r\n\t\t\tarray = array.add(sources);\r\n\t\t\tarray = array.add(fxparas);\r\n\t\t\tarray = array.add(snaps);\r\n\r\n\t\t\tfile.write(array.asCompileString);\r\n\t\t\tfile.close;\r\n\t\t}.defer;\r\n\r\n\r\n\t\t\tsources.pairsDo{ arg key, src, i;\r\n\t\t\t\tsrcon.value(key);\r\n\t\t\t\tif(src[\\state] == false){ src[\\node].set(\\gate, 0) };\r\n\t\t\t};\r\n\t\t},\r\n\t\tcancelFunc: {\"canceled\".postln});\r\n\r\n};\r\n\r\n// ----- load a session\r\nloadprj = { var array, file;\r\n\t{\r\n\t\tDialog.openPanel({ arg paths;\r\n\r\n\t\t\tautorefreshview.stop;\r\n\t\t\tgui.src.enabled_(true);\r\n\r\n\t\t\tsources.keys.copy.do{ arg id; removesrc.value(id) };\r\n\t\t\tfile = File(paths, \"r\");\r\n\t\t\tarray = file.readAllString.interpret;\r\n\t\t\t{\r\n\t\t\t\tgui.roomsens.value = array[1][0];\r\n\t\t\t\tgui.roomfreq.value = array[1][1];\r\n\t\t\t\tgui.roomdamp.value = array[1][2];\r\n\t\t\t\tgui.revlevel.value = array[1][3];\r\n\t\t\t\tgui.revtime.value = array[1][4];\r\n\t\t\t\tgui.revhf.value = array[1][5];\r\n\t\t\t\tgui.revlf.value = array[1][6];\r\n\t\t\t\tgui.dlylevel.value = array[1][7];\r\n\t\t\t\tgui.dlytime.value = array[1][8];\r\n\t\t\t\tgui.dlyfb.value = array[1][9];\r\n\r\n\t\t\t\troomsens = array[1][0];\r\n\t\t\t\troomfreq = array[1][1];\r\n\t\t\t\troomdamp = array[1][2];\r\n\r\n\t\t\t\treverb.do{ arg it;\r\n\t\t\t\t\tit.set(\\amp, gui.revlevel.value.dbamp,\r\n\t\t\t\t\t\t\\rt, gui.revtime.value,\r\n\t\t\t\t\t\t\\hfratio, gui.revhf.value,\r\n\t\t\t\t\t\t\\lfratio, gui.revlf.value) };\r\n\r\n\t\t\t\tdelay.do{ arg it;\r\n\t\t\t\t\tit.set(\\amp, gui.dlylevel.value.dbamp,\r\n\t\t\t\t\t\t\\dly, gui.dlytime.value,\r\n\t\t\t\t\t\t\\fb, gui.dlyfb.value) };\r\n\r\n\t\t\t\tarray[0].pairsDo{ arg key, dict, i;\r\n\t\t\t\t\tsources.add( key -> dict);\r\n\t\t\t\t\tsrcon.value(key);\r\n\t\t\t\t};\r\n\r\n\t\t\t\tsources.pairsDo{ arg key, src, i;\r\n\t\t\t\t\tif(src[\\state] == false){ src[\\node].set(\\gate, 0) };\r\n\t\t\t\t\trect2pos.value(src[\\id]);\r\n\r\n\t\t\t\t};\r\n\r\n\t\t\t\tselected = sources.keys.choose;\r\n\t\t\t\teditsrc.value(selected);\r\n\r\n\t\t\t\tgui.snapshots.items = array[2];\r\n\r\n\t\t\t}.defer;\r\n\r\n\t\t\tautorefreshview.start;\r\n\t\t},\r\n\t\tcancelFunc: {\"canceled\".postln});\r\n\t}.defer;\r\n};\r\n\r\n// ----- close session clear stuff\r\ncleanup = {\r\n\tautorefreshview.stop;\r\n\tsources.do{ arg src;\r\n\t\tif(src[\\rotastate]){src[\\rota].stop};\r\n\t\tif(src[\\randstate]){src[\\rand].stop};\r\n\t\tsrc[\\node].free;\r\n\t};\r\n\tstatsources.do{arg it; it.free};\r\n\treverb.do{arg it; it.free};\r\n\tdelay.do{arg it; it.free};\r\n\ttestsig.free;\r\n\ttracklevel.free;\r\n\tmaster.free;\r\n\tmasterbus.free;\r\n\trevbus.free;\r\n\tauxbus.free;\r\n\tdlybus.free;\r\n\tsrcgroup.free;\r\n\tfxsgroup.free;\r\n\tfxrgroup.free;\r\n\tmstrgroup.free;\r\n\tif(gui.meters.visible == true) {gui.meters.close};\r\n\tif(gui.fxpanel.visible == true) {gui.fxpanel.close};\r\n\tif(gui.snaplist.visible == true) {gui.snaplist.close};\r\n};\r\n\r\n// ----- open helpfile\r\ngethelp = {\r\n\tvar path = currpath ++ \"/spacepan_help.txt\";\r\n\tDocument.open(path);\r\n};\r\n\r\n\r\n// ----- add synths to server\r\nmakeSynths = {\r\n\r\n\tSynthDef(\"spacepan\", {\r\n\t\targ inbus = 100,\r\n\t\tout = 0,\r\n\t\tdelays = #[0,0,0,0,0,0,0,0], //always set this array to lspos size!\r\n\t\tamps = #[0,0,0,0,0,0,0,0], //always set this array to lspos size!\r\n\t\tdodelays = 1,\r\n\t\tdistance = 0,\r\n\t\tpan = #[0,0],\r\n\t\troom = 1,\r\n\t\troomsens = 0.5,\r\n\t\troomfreq = 4000,\r\n\t\troomdamp = 0.5,\r\n\t\tfxdlycomp = 0.02,\r\n\t\trev = 1,\r\n\t\tdly = 0,\r\n\t\taux = 0,\r\n\t\tauxbus = 0,\r\n\t\trevbus = 0,\r\n\t\tdlybus = 0,\r\n\t\tamp = 0,\r\n\t\tgate = 1;\r\n\r\n\t\tvar sig, dirlevel, fxlevel, fxsig, predly;\r\n\r\n\t\tsig = In.ar(inbus, 1) * amp;\r\n\t\tsig = sig * Linen.kr(gate, 0.1, 1, 0.1, doneAction: 0);\r\n\t\tdistance = distance * room;\r\n\t\tsig = LPF.ar(sig, distance.lincurve(roomsens, 1, 20000, roomfreq, 1));\r\n\t\tfxsig = sig;\r\n\r\n\t\tsig = sig * amps.lag(0.05);\r\n\t\tsig = Select.ar(dodelays, [\r\n\t\t\tsig,\r\n\t\t\tDelayC.ar(sig, 0.1, delays.lag(0.05))\r\n\t\t]);\r\n\r\n\t\tfxsig = Select.ar(dodelays, [\r\n\t\t\tfxsig,\r\n\t\t\tDelayC.ar(fxsig, 0.1, fxdlycomp)\r\n\t\t]);\r\n\r\n\t\tdirlevel = distance.lincurve(roomsens, 1, 1, roomdamp, 1);\r\n\t\tfxlevel = distance.lincurve(roomsens, 1, 0, 1, 1);\r\n\t\tpredly = distance.lincurve(roomsens, 1, 0.03, 0.003, 1);\r\n\r\n\r\n\t\tOut.ar(out, sig * dirlevel);\r\n\t\tOut.ar(revbus, Pan4.ar(DelayC.ar(fxsig, 0.1, predly), pan[0], pan[1], fxlevel * rev));\r\n\t\tOut.ar(dlybus, Pan4.ar(fxsig, pan[0], pan[1], fxlevel * dly));\r\n\t}).add;\r\n\r\n\tSynthDef(\"staticsrc\", { arg inbus, out, amps = #[0,0,0,0,0,0,0,0]; //set this array to lspos size!\r\n\t\tvar sig = In.ar(inbus, 1);\r\n\t\tOut.ar(out, sig * amps);\r\n\t}).add;\r\n\r\n\tSynthDef(\"tracklevel\", { arg inbus = 100;\r\n\t\tvar sig = In.ar(inbus, 1);\r\n\t\tSendReply.kr(Impulse.kr(20), '/level',[Amplitude.kr(sig)]);\r\n\t}).add;\r\n\r\n\tSynthDef(\"master\", { arg in, out, amp = 1, gate = 1;\r\n\t\tvar sig = In.ar(masterbus, lspos.size);\r\n\t\tsig = sig * Linen.kr(gate, 0.1, 1, 0.1, doneAction: 0);\r\n\t\tsig = Limiter.ar(sig, 0.9, 0.01);\r\n\t\tOut.ar(out, LeakDC.ar(sig, mul: amp));\r\n\t}).add;\r\n\r\n\tSynthDef(\"reverb\", { arg in, out, rt, stereo, lf, lfratio, hf, hfratio, early, late, modRate, modDepth, amp;\r\n\t\tvar sig = In.ar(in, 2);\r\n\t\tsig = NHHall.ar(sig, rt, stereo, lf, lfratio, hf, hfratio, early, late, modRate, modDepth);\r\n\t\tOut.ar(out, sig * amp);\r\n\t}).add;\r\n\r\n\tSynthDef(\"delay\", { arg in, out, dly, fb, amp;\r\n\t\tvar sig = In.ar(in, 1);\r\n\t\tvar fdbck = LocalIn.ar(1);\r\n\t\tsig = fdbck * fb + sig;\r\n\t\tsig = BPF.ar(sig, 1000, 2.0);\r\n\t\tLocalOut.ar(DelayC.ar(sig.tanh, 2, dly));\r\n\t\tOut.ar(out , LeakDC.ar(sig, mul: amp));\r\n\t}).add;\r\n\r\n\tSynthDef(\"testsig\", { arg out;\r\n\t\tvar sig = Decay2.ar(Impulse.ar(0.25), 0.1, 1, PinkNoise.ar, Decay.ar(Impulse.ar(1), 0.3, SinOsc.ar(500))) ;\r\n\t\tOut.ar(out, sig);\r\n\t}).add;\r\n\r\n};\r\n)\r\n\r\n//EOF\r\n\r\n/*SPACEPAN V1 README\r\n\r\nNote: I will convert the spacepan code to classes and write a proper helpfile within the next months, for now it is just a single .scd file and this lousy .txt ! You may also watch the video tutorial on youtube.\r\n\r\n*** BEFORE YOU START ***\r\n\r\n- You will need the \"SC3plugins\" extensions and the \"wslib\" and \"mathlib\" quarks (type \"Quarks.gui\") to run the code!\r\n\r\n- You will need at least 4 loudspeakers in a front-back configuration. For proper results use >= 8 loudspeakers\r\n\r\n- Create a dedicated spacepan folder in your SC working folder. This folder must contain SpacePanV1.scd, spacepan_help.txt and a subfolder named \"Snapshots\"\r\n\r\n*** RUNNING SPACEPAN ***\r\n\r\n- Open SpacePanV1.scd\r\n\r\n- Define your audio device, samplerate and the number of in/outs\r\n\r\n- Set the size of the virtual room. Depends on your real space, reasonable size will be between 10..50 meters\r\n\r\n- Set your loudspeaker positions array lspos = [x, y] meters. You can choose your reference point (from where you measure x y) but it makes sense to define the middle of your loudspeaker arrangement to be the reference (0, 0). Try to be precise.\r\n\r\n- VERY IMPORTANT: You have to set the arrays named amps = #[...] and delays = #[...] in the synthdefs \"spacepan\" and \"staticsrc\" to the number of loudspeakers! For example for 6 speakers set arrays to #[0,0,0,0,0,0]. There will be a more convenient solution in the next version of this code...\r\n\r\n- Select the code (cursor to first bracket to autoselect all) and evaluate (cmd+enter)\r\n\r\n- The gui shows up. No source by default. Click the green \"+\" in the upper left corner of the source panel to add a source.\r\n\r\n- If needed, change your master out index (number box bottom left)\r\n\r\n- Turn up the volume of your source. You should hear the ping sound.\r\n\r\n- Check your outputs by clicking \"Outmeters\"\r\n\r\n*** SOURCE PANEL FUNCTIONS ***\r\n\r\n- \"Label\" : label the source\r\n- \"Group\" : assign source group. use shiftkey to apply actions to group (mute = shift+alt+click, solo = shift+ctrl+click, move=shift+move).\r\n- \"Input\" : input bus. input metering is always pre fader.\r\n- \"Solo\" : solo selected source. you can do that in spaceview using \"ctrl\" + click. In group mode use \"shift+ctrl\" to solo the group.\r\n- \"Mute\" : mute selected source. you can do that in spaceview using \"alt\" + click. In group mode use \"shift+alt\" to solo the group.\r\n- \"Level\" : source level -inf...0dB. Group levels are always linked\r\n- \"Focus\" : spread of the source. always use 1 if you have a stereo input source routed to group to minimize spill and phasing. focus is always linked.\r\n- \"Apply delay\" : if position algo uses time+amplitude or amplitude only\r\n- \"Apply room fx\" : apply distance based lowpass filtering and fx sends\r\n- \"Reverb\" : reverb send level.\r\n- \"Delay\" : delay send level.\r\n- \"Aux\" : aux send level. not active yet.\r\n- \"Rota\" : autorotation. adjust speed and direction as you like. always linked in groups.\r\n- \"Rand\" : randomwalk. set stepsize and speed as you like. always linked in groups.\r\n- \"Mirror\": mirror current source position. not linked in groups.\r\n- \"Position\" : x and y position, distance and angle of selected source.\r\n\r\n*** OPTION PANEL ***\r\n\r\n- \"Session..\" : load & save a session to .txt. if you want to overwrite a file, remove the .txt extension\r\n- \"Out Meters\" : output metering\r\n- \"View..\" : view options\r\n- \"Snapshots\" : open snapshot window. always use same number of sources. do not make a snapshot and then add sources, this will fail in recall.\r\n- \"FX Settings\" : open fx settings panel.\r\n\r\n*** MASTER PANEL ***\r\n\r\n- \"Level\" : master out volume\r\n- \"Offset\": offset master out\r\n\r\nCode : sccode.org/audioholic / \r\nContact: dominik.wegmann@tonmeister.de\r\n*/",
   "id" : "1-5eg",
   "is_private" : null,
   "author" : "audioholic",
   "name" : "SpacePan - A Spatial Audio Environment",
   "ancestor_list" : [],
   "description" : "This is a basic spatial audio tool using source oriented vector based\r\ndelay and amplitude panning as well as basic room simulation.\r\n\r\nThis code was written using SC3.11 / OSX10.13.6\r\nDependencies: Quarks wslib, mathlib and SC3plugins package.\r\n\r\nAs I cannot submit the complete folder, there is no helpfile in here, so I copied it to the tail of the code!!\r\nCopy the readme text to a new file, name it \"spacepan_help.txt and save it to the spacepan project folder. Additionally, you´ll need to create a subfolder \"Snapshots\" to your spacepan project folder. You can download the complete folder here: https://www.dropbox.com/s/vx591sp2sh4ps52/SpacePanV1.zip?dl=0\r\nTutorial: https://www.youtube.com/watch?v=4k96AOfATIs&feature=youtu.be"
}
