«Sampling Demo with QuNeo - Patch 1» by Bruno Ruviaro
on 23 Sep'13 06:25 inEach 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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
// ************************************************** // Sampling Demo (QuNeo) // Patch 2 - Basic Sample Playback with optional pitch bend // Bruno Ruviaro, 2013-07-26 // Revision 2014-05-15 // ************************************************** /* How to run: SELECT ALL (Ctrl + A), EVALUATE (Ctrl + Enter) Each pad triggers a different sound file. Horizontal axis of each pad controls transposition. Play button activates looping. Stop button deactivates looping. Pitch bend optional (off by default). All samples should have the same number of channelss (i.e., all mono, or all stereo). Note: you need to provide a path to a folder with 16 audio files. Write in the path in variable ~folderPath below. Tip: drag the samples folder from a Files window onto a blank part of this SuperCollider file. The path will automatically be copied here. */ // Path to samples folder ~folderPath = "/home/lork/Music/SuperCollider/quneo-sampling/sine-test-samples"; // add pitch bend if desired (up to 0.5) ~pitchBend = 0.0; s.waitForBoot({ var maxRate, minRate, loopSwitch, bufferArray, padArray, rateArray, previousXvalues, quneoChannel, lastTime = 0, count = 0, bank = 36; if(MIDIClient.sources.size==0, { MIDIIn.connectAll }, { "MIDI already connected".postln } ); // Choose here how much transposition is desired for X axis maxRate = 1 + ~pitchBend; minRate = 1 - ~pitchBend; loopSwitch = 0; // 1 is loop ON, 0 is loop OFF quneoChannel = 11; // This next array is needed to smooth out jumpy input from QuNeo's x axis... previousXvalues = Array.newClear(127); // Initialize stuff padArray = Array.newClear(127); bufferArray = Array.newClear(127); rateArray = Array.newClear(127); Buffer.freeAll; Routine.new{ // First read buffers (starting at index 36 of array, to match midi note numbers and make it easy to remember). (~folderPath ++ "/*.wav").pathMatch.do({ arg i, c; bufferArray[c+36] = Buffer.read(s, i) }); // Wait until all buffers are read s.sync; // Now add the SynthDef (note that it will need to know numChannels of buffers) SynthDef("playBuf", {arg bufnum = 0, rate, amp = 0.5, gate = 1, loop = 1; var snd, env; env = EnvGen.ar(Env.asr(0.001, 1, 1), gate: gate, doneAction: 2); snd = PlayBuf.ar( numChannels: bufferArray[36].numChannels, // assuming all samples are same num of channels... bufnum: bufnum, rate: Lag.kr(rate), loop: loop); snd = snd * env * amp; Out.ar(0, snd); }).add; }.play; // MIDI from QuNeo MIDIdef.cc( key: \xpads, func: {arg vel, ccNum; var index, rate, previous, winner; index = (24, 27 .. 69).indexOf(ccNum); index = index + bank; // 0-15 range becomes midi note range previous = previousXvalues[index]; // what was the velocity immediately preceding the current one? // In order to avoid discontinuous leaps, we compare previous x value with current 'candidate': // ... if difference is within acceptable bounds, then candidate "wins"; // ... if difference is too big (too big of a leap), then reject candidate and previous value "wins". case {previous.isNil} {winner = vel; previousXvalues[index] = vel} // first time as the pad is triggered {vel==63} {winner = previous} // this ignores the fall back value of QuNeo pads {(vel - previous).abs <= 5} {winner = vel; previousXvalues[index] = vel} // new value is sensible, OK we take it {(vel - previous).abs > 5} {winner = previous}; // if new value is a big leap, ignore it and use previous // up to the lines above, winner is still a number between 0-127 // below I convert it to appropriate ranges case {winner.isNil} {"rate is now nil".postln} // should never happen, but left here as debugging mechanism {winner<=40} {rate = winner.linexp(0, 40, minRate, 1)} {(winner>40)&&(winner<90)} {rate = 1} {winner>=90} {rate = winner.linexp(90, 127, 1, maxRate,)}; rateArray[index] = rate; // store chosen rate into array to be used once at synth attack time padArray[index].set(\rate, rate); // set new rates as they come in }, ccNum: (24, 27 .. 69), chan: quneoChannel); MIDIdef.noteOn( key: \noteOn, func: {arg vel, note; var index = note; padArray[index] = Synth("playBuf", [ \bufnum, bufferArray[index], \rate, rateArray[index], // attack of note should be at correct rate based on x axis \amp, vel/127, \loop, loopSwitch ]); case { note >= 84 } { bank = 84 } { note >= 68 } { bank = 68 } { note >= 52 } { bank = 52 } { note >= 36 } { bank = 36 }; ["noteON", index, "BANK", bank].postln; }, noteNum: (36..99), chan: quneoChannel); MIDIdef.noteOn(\loop, {arg vel, note; case {note==25} {loopSwitch = 0} {note==26} {loopSwitch = 1}; note.postln; }, [25, 26]); MIDIdef.noteOff( key: \noteOff, func: {arg vel, note; var index = note; padArray[index].release; // release node padArray[index] = nil; previousXvalues[index] = nil; rateArray[index] = nil; // ["noteOFF", index].postln; }, noteNum: (36..99), chan: quneoChannel); "Sampling QuNeo".postln; }); // end of block
reception
comments