«Sampling Demo with QuNeo - Patch 1» by Bruno Ruviaro

on 23 Sep'13 00:25 in playbacksamplingquneo

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).

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
raw 5296 chars (focus & ctrl+a+c to copy)
reception
comments