{
   "description" : "A fun little GUI toy emulating a freehand waveform editor as seen in Iannis Xenakis' UPIC software. Draw on the graph to edit an oscillator's waveform in realtime.",
   "ancestor_list" : [],
   "author" : "snappizz",
   "name" : "UPIC waveform editor",
   "code" : "s.waitForBoot({\r\n\r\n    var size = 1024;\r\n    var canvas, reset, test;\r\n    var wave, waveBuf, testSynth;\r\n    var draw, lastPos, lastVal, update;\r\n\r\n    w = Window(\"Waveform editor\", Rect(100, 100, 800, 330));\r\n\r\n    wave = Signal.sineFill(size, [1]);\r\n    waveBuf = Buffer.alloc(s, size * 2);\r\n    ~waveBuf = waveBuf;\r\n    testSynth = nil;\r\n\r\n    update = { waveBuf.loadCollection(wave.asWavetable); };\r\n\r\n    lastPos = nil;\r\n    lastVal = nil;\r\n\r\n    draw = { |me, x, y, mod, btn|\r\n        var pos = (size * (x / me.bounds.width)).floor,\r\n            val = (2 * (y / me.bounds.height)) - 1;\r\n        val = min(max(val, -1), 1);\r\n        wave.clipPut(pos, val);\r\n        if(lastPos != nil, {\r\n            for(lastPos + 1, pos - 1, { |i|\r\n                wave.clipPut(i, lastVal + (((i - lastPos) / (pos - lastPos)) * (val - lastVal)));\r\n            });\r\n            for(pos + 1, lastPos - 1, { |i|\r\n                wave.clipPut(i, lastVal + (((i - lastPos) / (pos - lastPos)) * (val - lastVal)));\r\n            });\r\n        });\r\n        lastPos = pos;\r\n        lastVal = val;\r\n        update.value; \r\n    };\r\n\r\n    canvas = UserView(w, Rect(0, 30, 800, 300))\r\n        .background_(Color.white)\r\n        .animate_(true)\r\n        .mouseDownAction_(draw)\r\n        .mouseMoveAction_(draw)\r\n        .mouseUpAction_({\r\n            lastPos = nil;\r\n            lastVal = nil;\r\n        })\r\n        .drawFunc_({ |me|\r\n            Pen.moveTo(0@(me.bounds.height * (wave[0] + 1) / 2));\r\n            for(1, size - 1, { |i, a|\r\n                Pen.lineTo((me.bounds.width * i / size)@(me.bounds.height * (wave[i] + 1) / 2))\r\n            });\r\n            Pen.stroke;\r\n        });\r\n\r\n    reset = Button(w, Rect(0, 0, 60, 30))\r\n        .states_([[\"Reset\"]])\r\n        .action_({\r\n            wave.sineFill([1]);\r\n            update.value;\r\n        });\r\n\r\n    test = Button(w, Rect(60, 0, 60, 30))\r\n        .states_([[\"Test\"], [\"Stop\"]])\r\n        .action_({ |me|\r\n            if(me.value == 1, {\r\n                update.value;\r\n                testSynth = {\r\n                    var sig;\r\n                    sig = Osc.ar(waveBuf, MouseY.kr(30, 1000)) * 0.1;\r\n                    sig = LeakDC.ar(sig);\r\n                    sig = sig!2;\r\n                }.play;\r\n            }, {\r\n                testSynth.free;\r\n            });\r\n        });\r\n\r\n    w.onClose_({\r\n        testSynth.free;\r\n        wave.free;\r\n    });\r\n\r\n    w.front;\r\n\r\n});",
   "id" : "1-4VJ",
   "is_private" : null,
   "labels" : [
      "gui",
      "xenakis",
      "editor",
      "upic",
      "theremin"
   ]
}
