{
   "labels" : [
      "playback",
      "sampling",
      "quneo"
   ],
   "code" : "// **************************************************\r\n// Sampling Demo (QuNeo)\r\n// Patch 2 - Basic Sample Playback with optional pitch bend\r\n// Bruno Ruviaro, 2013-07-26\r\n// Revision 2014-05-15\r\n// **************************************************\r\n\r\n/*\r\n\r\nHow to run:\r\nSELECT ALL (Ctrl + A), EVALUATE (Ctrl + Enter)\r\n\r\nEach pad triggers a different sound file.\r\nHorizontal axis of each pad controls transposition.\r\nPlay button activates looping.\r\nStop button deactivates looping.\r\n\r\nPitch bend optional (off by default).\r\n\r\nAll samples should have the same number of channelss (i.e., all mono, or all stereo).\r\n\r\nNote: you need to provide a path to a folder with 16\r\naudio files. Write in the path in variable ~folderPath below.\r\nTip: drag the samples folder from a Files window onto a blank part of\r\nthis SuperCollider file. The path will automatically be copied here.\r\n\r\n*/\r\n\r\n// Path to samples folder\r\n~folderPath = \"/home/lork/Music/SuperCollider/quneo-sampling/sine-test-samples\";\r\n\r\n// add pitch bend if desired (up to 0.5)\r\n~pitchBend = 0.0;\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\ns.waitForBoot({\r\n\r\n\tvar maxRate, minRate, loopSwitch, bufferArray, padArray, rateArray, previousXvalues, quneoChannel, lastTime = 0, count = 0, bank = 36;\r\n\r\n\tif(MIDIClient.sources.size==0,\r\n\t\t{ MIDIIn.connectAll },\r\n\t\t{ \"MIDI already connected\".postln }\r\n\t);\r\n\r\n\t// Choose here how much transposition is desired for X axis\r\n\tmaxRate = 1 + ~pitchBend;\r\n\tminRate = 1 - ~pitchBend;\r\n\tloopSwitch = 0; // 1 is loop ON, 0 is loop OFF\r\n\tquneoChannel = 11;\r\n\r\n\t// This next array is needed to smooth out jumpy input from QuNeo's x axis...\r\n\tpreviousXvalues = Array.newClear(127);\r\n\r\n\t// Initialize stuff\r\n\tpadArray = Array.newClear(127);\r\n\tbufferArray = Array.newClear(127);\r\n\trateArray = Array.newClear(127);\r\n\tBuffer.freeAll;\r\n\r\n\tRoutine.new{\r\n\r\n\t\t// First read buffers (starting at index 36 of array, to match midi note numbers and make it easy to remember).\r\n\r\n\t\t(~folderPath ++ \"/*.wav\").pathMatch.do({ arg i, c; bufferArray[c+36] = Buffer.read(s, i) });\r\n\r\n\t\t// Wait until all buffers are read\r\n\t\ts.sync;\r\n\r\n\t\t// Now add the SynthDef (note that it will need to know numChannels of buffers)\r\n\t\tSynthDef(\"playBuf\", {arg bufnum = 0, rate, amp = 0.5, gate = 1, loop = 1;\r\n\t\t\tvar snd, env;\r\n\t\t\tenv = EnvGen.ar(Env.asr(0.001, 1, 1), gate: gate, doneAction: 2);\r\n\t\t\tsnd = PlayBuf.ar(\r\n\t\t\t\tnumChannels: bufferArray[36].numChannels, // assuming all samples are same num of channels...\r\n\t\t\t\tbufnum: bufnum,\r\n\t\t\t\trate: Lag.kr(rate),\r\n\t\t\t\tloop: loop);\r\n\t\t\tsnd = snd * env * amp;\r\n\t\t\tOut.ar(0, snd);\r\n\t\t}).add;\r\n\t}.play;\r\n\r\n\r\n\t// MIDI from QuNeo\r\n\tMIDIdef.cc(\r\n\t\tkey: \\xpads,\r\n\t\tfunc: {arg vel, ccNum;\r\n\t\t\tvar index, rate, previous, winner;\r\n\r\n\t\t\tindex = (24, 27 .. 69).indexOf(ccNum);\r\n\t\t\tindex = index + bank; // 0-15 range becomes midi note range\r\n\r\n\t\t\tprevious = previousXvalues[index]; // what was the velocity immediately preceding the current one?\r\n\r\n\t\t\t// In order to avoid discontinuous leaps, we compare previous x value with current 'candidate':\r\n\t\t\t// ... if difference is within acceptable bounds, then candidate \"wins\";\r\n\t\t\t// ... if difference is too big (too big of a leap), then reject candidate and previous value \"wins\".\r\n\r\n\t\t\tcase\r\n\t\t\t{previous.isNil} {winner = vel; previousXvalues[index] = vel} // first time as the pad is triggered\r\n\t\t\t{vel==63} {winner = previous} // this ignores the fall back value of QuNeo pads\r\n\t\t\t{(vel - previous).abs <= 5} {winner = vel; previousXvalues[index] = vel} // new value is sensible, OK we take it\r\n\t\t\t{(vel - previous).abs > 5} {winner = previous}; // if new value is a big leap, ignore it and use previous\r\n\r\n\t\t\t// up to the lines above, winner is still a number between 0-127\r\n\t\t\t// below I convert it to appropriate ranges\r\n\r\n\t\t\tcase\r\n\t\t\t{winner.isNil} {\"rate is now nil\".postln} // should never happen, but left here as debugging mechanism\r\n\t\t\t{winner<=40} {rate = winner.linexp(0, 40, minRate, 1)}\r\n\t\t\t{(winner>40)&&(winner<90)} {rate = 1}\r\n\t\t\t{winner>=90} {rate = winner.linexp(90, 127, 1, maxRate,)};\r\n\r\n\t\t\trateArray[index] = rate; // store chosen rate into array to be used once at synth attack time\r\n\t\t\tpadArray[index].set(\\rate, rate); // set new rates as they come in\r\n\r\n\t\t},\r\n\t\tccNum: (24, 27 .. 69),\r\n\t\tchan: quneoChannel);\r\n\r\n\tMIDIdef.noteOn(\r\n\t\tkey: \\noteOn,\r\n\t\tfunc: {arg vel, note;\r\n\t\t\tvar index = note;\r\n\t\t\tpadArray[index] = Synth(\"playBuf\", [\r\n\t\t\t\t\\bufnum, bufferArray[index],\r\n\t\t\t\t\\rate, rateArray[index], // attack of note should be at correct rate based on x axis\r\n\t\t\t\t\\amp, vel/127,\r\n\t\t\t\t\\loop, loopSwitch\r\n\t\t\t]);\r\n\r\n\t\t\tcase\r\n\t\t\t{ note >= 84 } { bank = 84 }\r\n\t\t\t{ note >= 68 } { bank = 68 }\r\n\t\t\t{ note >= 52 } { bank = 52 }\r\n\t\t\t{ note >= 36 } { bank = 36 };\r\n\r\n\t\t\t[\"noteON\", index, \"BANK\", bank].postln;\r\n\t\t},\r\n\t\tnoteNum: (36..99),\r\n\t\tchan: quneoChannel);\r\n\r\n\tMIDIdef.noteOn(\\loop, {arg vel, note;\r\n\t\tcase\r\n\t\t{note==25} {loopSwitch = 0}\r\n\t\t{note==26} {loopSwitch = 1};\r\n\t\tnote.postln;\r\n\t}, [25, 26]);\r\n\r\n\tMIDIdef.noteOff(\r\n\t\tkey: \\noteOff,\r\n\t\tfunc: {arg vel, note;\r\n\t\t\tvar index = note;\r\n\t\t\tpadArray[index].release; // release node\r\n\t\t\tpadArray[index] = nil;\r\n\t\t\tpreviousXvalues[index] = nil;\r\n\t\t\trateArray[index] = nil;\r\n\t\t\t// [\"noteOFF\", index].postln;\r\n\t\t},\r\n\t\tnoteNum: (36..99),\r\n\t\tchan: quneoChannel);\r\n\r\n\t\"Sampling QuNeo\".postln;\r\n\r\n}); // end of block",
   "is_private" : null,
   "id" : "1-4US",
   "name" : "Sampling Demo with QuNeo - Patch 1",
   "author" : "Bruno Ruviaro",
   "description" : "Each QuNeo pad triggers a different sound file. Horizontal axis controls pitch bend (off by default). Play/Stop buttons control looping. Allows use of 4 banks (up to 64 samples).",
   "ancestor_list" : []
}
