chris@552
|
1 /*
|
chris@555
|
2 ____ _____ _ _
|
chris@555
|
3 | __ )| ____| | / \
|
chris@555
|
4 | _ \| _| | | / _ \
|
chris@555
|
5 | |_) | |___| |___ / ___ \
|
chris@555
|
6 |____/|_____|_____/_/ \_\
|
chris@555
|
7
|
chris@555
|
8 The platform for ultra-low latency audio and sensor processing
|
chris@555
|
9
|
chris@555
|
10 http://bela.io
|
chris@555
|
11
|
chris@555
|
12 A project of the Augmented Instruments Laboratory within the
|
chris@555
|
13 Centre for Digital Music at Queen Mary University of London.
|
chris@555
|
14 http://www.eecs.qmul.ac.uk/~andrewm
|
chris@555
|
15
|
chris@555
|
16 (c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
|
chris@555
|
17 Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
|
chris@555
|
18 Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
|
chris@555
|
19
|
chris@555
|
20 The Bela software is distributed under the GNU Lesser General Public License
|
chris@555
|
21 (LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
|
chris@555
|
22 */
|
chris@555
|
23
|
chris@555
|
24 /*
|
chris@555
|
25 * USING A CUSTOM RENDER.CPP FILE FOR PUREDATA PATCHES - HEAVY
|
chris@555
|
26 * ===========================================================
|
chris@555
|
27 * || ||
|
chris@555
|
28 * || OPEN THE ENCLOSED _main.pd PATCH FOR MORE INFORMATION ||
|
chris@555
|
29 * || ----------------------------------------------------- ||
|
chris@555
|
30 * ===========================================================
|
chris@552
|
31 */
|
chris@552
|
32
|
chris@552
|
33 #include <Bela.h>
|
chris@552
|
34 #include <Midi.h>
|
chris@552
|
35 #include <Scope.h>
|
chris@552
|
36 #include <cmath>
|
chris@552
|
37 #include <Heavy_bela.h>
|
chris@552
|
38 #include <string.h>
|
chris@552
|
39 #include <stdlib.h>
|
chris@552
|
40 #include <string.h>
|
chris@552
|
41 #include <DigitalChannelManager.h>
|
chris@552
|
42
|
chris@552
|
43 /*
|
chris@552
|
44 * MODIFICATION
|
chris@552
|
45 * ------------
|
chris@552
|
46 * Global variables for tremolo effect applied to libpd output
|
chris@552
|
47 */
|
chris@552
|
48
|
chris@552
|
49 float gTremoloRate = 4.0;
|
chris@552
|
50 float gPhase;
|
chris@552
|
51
|
chris@552
|
52 /*********/
|
chris@552
|
53
|
chris@552
|
54 /*
|
chris@552
|
55 * HEAVY CONTEXT & BUFFERS
|
chris@552
|
56 */
|
chris@552
|
57
|
chris@552
|
58 Hv_bela *gHeavyContext;
|
chris@552
|
59 float *gHvInputBuffers = NULL, *gHvOutputBuffers = NULL;
|
chris@552
|
60 unsigned int gHvInputChannels = 0, gHvOutputChannels = 0;
|
chris@552
|
61
|
chris@552
|
62 float gInverseSampleRate;
|
chris@552
|
63
|
chris@552
|
64 /*
|
chris@552
|
65 * HEAVY FUNCTIONS
|
chris@552
|
66 */
|
chris@552
|
67
|
chris@552
|
68 // TODO: rename this
|
chris@552
|
69 #define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs)
|
chris@552
|
70
|
chris@552
|
71 void printHook(double timestampSecs, const char *printLabel, const char *msgString, void *userData) {
|
chris@552
|
72 rt_printf("Message from Heavy patch: [@ %.3f] %s: %s\n", timestampSecs, printLabel, msgString);
|
chris@552
|
73 }
|
chris@552
|
74
|
chris@552
|
75
|
chris@552
|
76 // digitals
|
chris@552
|
77 static DigitalChannelManager dcm;
|
chris@552
|
78
|
chris@552
|
79 void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){
|
chris@552
|
80 hv_sendFloatToReceiver(gHeavyContext, hv_stringToHash((char*)receiverName), (float)state);
|
chris@552
|
81 // rt_printf("%s: %d\n", (char*)receiverName, state);
|
chris@552
|
82 }
|
chris@552
|
83
|
chris@552
|
84 // TODO: turn them into hv hashes and adjust sendDigitalMessage accordingly
|
chris@552
|
85 char hvDigitalInHashes[16][21]={
|
chris@552
|
86 {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"},
|
chris@552
|
87 {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"},
|
chris@552
|
88 {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"},
|
chris@552
|
89 {"bela_digitalIn26"}
|
chris@552
|
90 };
|
chris@552
|
91
|
chris@552
|
92 static void sendHook(
|
chris@552
|
93 double timestamp, // in milliseconds
|
chris@552
|
94 const char *receiverName,
|
chris@552
|
95 const HvMessage *const m,
|
chris@552
|
96 void *userData) {
|
chris@552
|
97
|
chris@552
|
98 /*
|
chris@552
|
99 * MODIFICATION
|
chris@552
|
100 * ------------
|
chris@552
|
101 * Parse float sent to receiver 'tremoloRate' and assign it to a global variable
|
chris@552
|
102 */
|
chris@552
|
103
|
chris@552
|
104 if(strncmp(receiverName, "tremoloRate", 11) == 0){
|
chris@552
|
105 float value = hv_msg_getFloat(m, 0); // see the Heavy C API documentation: https://enzienaudio.com/docs/index.html#8.c
|
chris@552
|
106 gTremoloRate = value;
|
chris@552
|
107 }
|
chris@552
|
108
|
chris@552
|
109 /*********/
|
chris@552
|
110
|
chris@552
|
111 // Bela digital
|
chris@552
|
112
|
chris@552
|
113 // Bela digital run-time messages
|
chris@552
|
114
|
chris@552
|
115 // TODO: this first block is almost an exact copy of libpd's code, should we add this to the class?
|
chris@552
|
116 // let's make this as optimized as possible for built-in digital Out parsing
|
chris@552
|
117 // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26
|
chris@552
|
118 static int prefixLength = 15; // strlen("bela_digitalOut")
|
chris@552
|
119 if(strncmp(receiverName, "bela_digitalOut", prefixLength)==0){
|
chris@552
|
120 if(receiverName[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2)
|
chris@552
|
121 if(receiverName[prefixLength + 1] != 0){
|
chris@552
|
122 // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi
|
chris@552
|
123 int receiver = ((receiverName[prefixLength] - 48) * 10);
|
chris@552
|
124 receiver += (receiverName[prefixLength+1] - 48);
|
chris@552
|
125 unsigned int channel = receiver - LIBPD_DIGITAL_OFFSET; // go back to the actual Bela digital channel number
|
chris@552
|
126 bool value = hv_msg_getFloat(m, 0);
|
chris@552
|
127 if(channel < 16){ //16 is the hardcoded value for the number of digital channels
|
chris@552
|
128 dcm.setValue(channel, value);
|
chris@552
|
129 }
|
chris@552
|
130 }
|
chris@552
|
131 }
|
chris@552
|
132 }
|
chris@552
|
133
|
chris@552
|
134 // Bela digital initialization messages
|
chris@552
|
135 if(strcmp(receiverName, "bela_setDigital") == 0){
|
chris@552
|
136 // Third argument (optional) can be ~ or sig for signal-rate, message-rate otherwise.
|
chris@552
|
137 // [in 14 ~(
|
chris@552
|
138 // |
|
chris@552
|
139 // [s bela_setDigital]
|
chris@552
|
140 // is signal("sig" or "~") or message("message", default) rate
|
chris@552
|
141 bool isMessageRate = true; // defaults to message rate
|
chris@552
|
142 bool direction = 0; // initialize it just to avoid the compiler's warning
|
chris@552
|
143 bool disable = false;
|
chris@552
|
144 int numArgs = hv_msg_getNumElements(m);
|
chris@552
|
145 if(numArgs < 2 || numArgs > 3 || !hv_msg_isSymbol(m, 0) || !hv_msg_isFloat(m, 1))
|
chris@552
|
146 return;
|
chris@552
|
147 if(numArgs == 3 && !hv_msg_isSymbol(m,2))
|
chris@552
|
148 return;
|
chris@552
|
149 char * symbol = hv_msg_getSymbol(m, 0);
|
chris@552
|
150
|
chris@552
|
151 if(strcmp(symbol, "in") == 0){
|
chris@552
|
152 direction = INPUT;
|
chris@552
|
153 } else if(strcmp(symbol, "out") == 0){
|
chris@552
|
154 direction = OUTPUT;
|
chris@552
|
155 } else if(strcmp(symbol, "disable") == 0){
|
chris@552
|
156 disable = true;
|
chris@552
|
157 } else {
|
chris@552
|
158 return;
|
chris@552
|
159 }
|
chris@552
|
160 int channel = hv_msg_getFloat(m, 1) - LIBPD_DIGITAL_OFFSET;
|
chris@552
|
161 if(disable == true){
|
chris@552
|
162 dcm.unmanage(channel);
|
chris@552
|
163 return;
|
chris@552
|
164 }
|
chris@552
|
165 if(numArgs >= 3){
|
chris@552
|
166 char* s = hv_msg_getSymbol(m, 2);
|
chris@552
|
167 if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){
|
chris@552
|
168 isMessageRate = false;
|
chris@552
|
169 }
|
chris@552
|
170 }
|
chris@552
|
171 dcm.manage(channel, direction, isMessageRate);
|
chris@552
|
172 }
|
chris@552
|
173 }
|
chris@552
|
174
|
chris@552
|
175
|
chris@552
|
176 /*
|
chris@552
|
177 * SETUP, RENDER LOOP & CLEANUP
|
chris@552
|
178 */
|
chris@552
|
179
|
chris@552
|
180 // leaving this here, trying to come up with a coherent interface with libpd.
|
chris@552
|
181 // commenting them out so the compiler does not warn
|
chris@552
|
182 // 2 audio + (up to)8 analog + (up to) 16 digital + 4 scope outputs
|
chris@552
|
183 //static const unsigned int gChannelsInUse = 30;
|
chris@552
|
184 //static unsigned int gAnalogChannelsInUse = 8; // hard-coded for the moment, TODO: get it at run-time from hv_context
|
chris@552
|
185 //static const unsigned int gFirstAudioChannel = 0;
|
chris@552
|
186 //static const unsigned int gFirstAnalogChannel = 2;
|
chris@552
|
187 static const unsigned int gFirstDigitalChannel = 10;
|
chris@552
|
188 static const unsigned int gFirstScopeChannel = 26;
|
chris@552
|
189 static unsigned int gDigitalSigInChannelsInUse;
|
chris@552
|
190 static unsigned int gDigitalSigOutChannelsInUse;
|
chris@552
|
191
|
chris@552
|
192 // Bela Midi
|
chris@552
|
193 Midi midi;
|
chris@552
|
194 unsigned int hvMidiHashes[7];
|
chris@552
|
195 // Bela Scope
|
chris@552
|
196 Scope scope;
|
chris@552
|
197 unsigned int gScopeChannelsInUse;
|
chris@552
|
198 float* gScopeOut;
|
chris@552
|
199
|
chris@552
|
200
|
chris@552
|
201 bool setup(BelaContext *context, void *userData) {
|
chris@552
|
202 if(context->audioInChannels != context->audioOutChannels ||
|
chris@552
|
203 context->analogInChannels != context->analogOutChannels){
|
chris@552
|
204 // It should actually work, but let's test it before releasing it!
|
chris@552
|
205 printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
|
chris@552
|
206 return false;
|
chris@552
|
207 }
|
chris@552
|
208
|
chris@552
|
209 /*
|
chris@552
|
210 * MODIFICATION
|
chris@552
|
211 * ------------
|
chris@552
|
212 * Initialise variables for tremolo effect
|
chris@552
|
213 */
|
chris@552
|
214
|
chris@552
|
215 gPhase = 0.0;
|
chris@552
|
216
|
chris@552
|
217 /*********/
|
chris@552
|
218
|
chris@552
|
219 /* HEAVY */
|
chris@552
|
220 hvMidiHashes[kmmNoteOn] = hv_stringToHash("__hv_notein");
|
chris@552
|
221 // hvMidiHashes[kmmNoteOff] = hv_stringToHash("noteoff"); // this is handled differently, see the render function
|
chris@552
|
222 hvMidiHashes[kmmControlChange] = hv_stringToHash("__hv_ctlin");
|
chris@552
|
223 // Note that the ones below are not defined by Heavy, but they are here for (wishing) forward-compatibility
|
chris@552
|
224 // You need to receive from the corresponding symbol in Pd and unpack the message, e.g.:
|
chris@552
|
225 //[r __hv_pgmin]
|
chris@552
|
226 //|
|
chris@552
|
227 //[unpack f f]
|
chris@552
|
228 //| |
|
chris@552
|
229 //| [print pgmin_channel]
|
chris@552
|
230 //[print pgmin_number]
|
chris@552
|
231 hvMidiHashes[kmmProgramChange] = hv_stringToHash("__hv_pgmin");
|
chris@552
|
232 hvMidiHashes[kmmPolyphonicKeyPressure] = hv_stringToHash("__hv_polytouchin");
|
chris@552
|
233 hvMidiHashes[kmmChannelPressure] = hv_stringToHash("__hv_touchin");
|
chris@552
|
234 hvMidiHashes[kmmPitchBend] = hv_stringToHash("__hv_bendin");
|
chris@552
|
235
|
chris@552
|
236 gHeavyContext = hv_bela_new(context->audioSampleRate);
|
chris@552
|
237
|
chris@552
|
238 gHvInputChannels = hv_getNumInputChannels(gHeavyContext);
|
chris@552
|
239 gHvOutputChannels = hv_getNumOutputChannels(gHeavyContext);
|
chris@552
|
240
|
chris@552
|
241 gScopeChannelsInUse = gHvOutputChannels > gFirstScopeChannel ?
|
chris@552
|
242 gHvOutputChannels - gFirstScopeChannel : 0;
|
chris@552
|
243 gDigitalSigInChannelsInUse = gHvInputChannels > gFirstDigitalChannel ?
|
chris@552
|
244 gHvInputChannels - gFirstDigitalChannel : 0;
|
chris@552
|
245 gDigitalSigOutChannelsInUse = gHvOutputChannels > gFirstDigitalChannel ?
|
chris@552
|
246 gHvOutputChannels - gFirstDigitalChannel - gScopeChannelsInUse: 0;
|
chris@552
|
247
|
chris@552
|
248 printf("Starting Heavy context with %d input channels and %d output channels\n",
|
chris@552
|
249 gHvInputChannels, gHvOutputChannels);
|
chris@552
|
250 printf("Channels in use:\n");
|
chris@552
|
251 printf("Digital in : %u, Digital out: %u\n", gDigitalSigInChannelsInUse, gDigitalSigOutChannelsInUse);
|
chris@552
|
252 printf("Scope out: %u\n", gScopeChannelsInUse);
|
chris@552
|
253
|
chris@552
|
254 if(gHvInputChannels != 0) {
|
chris@552
|
255 gHvInputBuffers = (float *)calloc(gHvInputChannels * context->audioFrames,sizeof(float));
|
chris@552
|
256 }
|
chris@552
|
257 if(gHvOutputChannels != 0) {
|
chris@552
|
258 gHvOutputBuffers = (float *)calloc(gHvOutputChannels * context->audioFrames,sizeof(float));
|
chris@552
|
259 }
|
chris@552
|
260
|
chris@552
|
261 gInverseSampleRate = 1.0 / context->audioSampleRate;
|
chris@552
|
262
|
chris@552
|
263 // Set heavy print hook
|
chris@552
|
264 hv_setPrintHook(gHeavyContext, printHook);
|
chris@552
|
265 // Set heavy send hook
|
chris@552
|
266 hv_setSendHook(gHeavyContext, sendHook);
|
chris@552
|
267
|
chris@552
|
268 // TODO: change these hardcoded port values and actually change them in the Midi class
|
chris@552
|
269 midi.readFrom(0);
|
chris@552
|
270 midi.writeTo(0);
|
chris@552
|
271 midi.enableParser(true);
|
chris@552
|
272
|
chris@552
|
273 if(gScopeChannelsInUse > 0){
|
chris@552
|
274 // block below copy/pasted from libpd, except
|
chris@552
|
275 scope.setup(gScopeChannelsInUse, context->audioSampleRate);
|
chris@552
|
276 gScopeOut = new float[gScopeChannelsInUse];
|
chris@552
|
277 }
|
chris@552
|
278 // Bela digital
|
chris@552
|
279 dcm.setCallback(sendDigitalMessage);
|
chris@552
|
280 if(context->digitalChannels > 0){
|
chris@552
|
281 for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){
|
chris@552
|
282 dcm.setCallbackArgument(ch, hvDigitalInHashes[ch]);
|
chris@552
|
283 }
|
chris@552
|
284 }
|
chris@552
|
285 // unlike libpd, no need here to bind the bela_digitalOut.. receivers
|
chris@552
|
286
|
chris@552
|
287 return true;
|
chris@552
|
288 }
|
chris@552
|
289
|
chris@552
|
290
|
chris@552
|
291 void render(BelaContext *context, void *userData)
|
chris@552
|
292 {
|
chris@552
|
293 {
|
chris@552
|
294 int num;
|
chris@552
|
295 while((num = midi.getParser()->numAvailableMessages()) > 0){
|
chris@552
|
296 static MidiChannelMessage message;
|
chris@552
|
297 message = midi.getParser()->getNextChannelMessage();
|
chris@552
|
298 switch(message.getType()){
|
chris@552
|
299 case kmmNoteOn: {
|
chris@552
|
300 //message.prettyPrint();
|
chris@552
|
301 int noteNumber = message.getDataByte(0);
|
chris@552
|
302 int velocity = message.getDataByte(1);
|
chris@552
|
303 int channel = message.getChannel();
|
chris@552
|
304 // rt_printf("message: noteNumber: %f, velocity: %f, channel: %f\n", noteNumber, velocity, channel);
|
chris@552
|
305 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff",
|
chris@552
|
306 (float)noteNumber, (float)velocity, (float)channel+1);
|
chris@552
|
307 break;
|
chris@552
|
308 }
|
chris@552
|
309 case kmmNoteOff: {
|
chris@552
|
310 /* PureData does not seem to handle noteoff messages as per the MIDI specs,
|
chris@552
|
311 * so that the noteoff velocity is ignored. Here we convert them to noteon
|
chris@552
|
312 * with a velocity of 0.
|
chris@552
|
313 */
|
chris@552
|
314 int noteNumber = message.getDataByte(0);
|
chris@552
|
315 // int velocity = message.getDataByte(1); // would be ignored by Pd
|
chris@552
|
316 int channel = message.getChannel();
|
chris@552
|
317 // note we are sending the below to hvHashes[kmmNoteOn] !!
|
chris@552
|
318 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff",
|
chris@552
|
319 (float)noteNumber, (float)0, (float)channel+1);
|
chris@552
|
320 break;
|
chris@552
|
321 }
|
chris@552
|
322 case kmmControlChange: {
|
chris@552
|
323 int channel = message.getChannel();
|
chris@552
|
324 int controller = message.getDataByte(0);
|
chris@552
|
325 int value = message.getDataByte(1);
|
chris@552
|
326 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmControlChange], 0, "fff",
|
chris@552
|
327 (float)value, (float)controller, (float)channel+1);
|
chris@552
|
328 break;
|
chris@552
|
329 }
|
chris@552
|
330 case kmmProgramChange: {
|
chris@552
|
331 int channel = message.getChannel();
|
chris@552
|
332 int program = message.getDataByte(0);
|
chris@552
|
333 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmProgramChange], 0, "ff",
|
chris@552
|
334 (float)program, (float)channel+1);
|
chris@552
|
335 break;
|
chris@552
|
336 }
|
chris@552
|
337 case kmmPolyphonicKeyPressure: {
|
chris@552
|
338 //TODO: untested, I do not have anything with polyTouch... who does, anyhow?
|
chris@552
|
339 int channel = message.getChannel();
|
chris@552
|
340 int pitch = message.getDataByte(0);
|
chris@552
|
341 int value = message.getDataByte(1);
|
chris@552
|
342 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPolyphonicKeyPressure], 0, "fff",
|
chris@552
|
343 (float)channel+1, (float)pitch, (float)value);
|
chris@552
|
344 break;
|
chris@552
|
345 }
|
chris@552
|
346 case kmmChannelPressure:
|
chris@552
|
347 {
|
chris@552
|
348 int channel = message.getChannel();
|
chris@552
|
349 int value = message.getDataByte(0);
|
chris@552
|
350 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmChannelPressure], 0, "ff",
|
chris@552
|
351 (float)value, (float)channel+1);
|
chris@552
|
352 break;
|
chris@552
|
353 }
|
chris@552
|
354 case kmmPitchBend:
|
chris@552
|
355 {
|
chris@552
|
356 int channel = message.getChannel();
|
chris@552
|
357 int value = ((message.getDataByte(1) << 7) | message.getDataByte(0));
|
chris@552
|
358 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPitchBend], 0, "ff",
|
chris@552
|
359 (float)value, (float)channel+1);
|
chris@552
|
360 break;
|
chris@552
|
361 }
|
chris@552
|
362 case kmmNone:
|
chris@552
|
363 case kmmAny:
|
chris@552
|
364 break;
|
chris@552
|
365 }
|
chris@552
|
366 }
|
chris@552
|
367 }
|
chris@552
|
368
|
chris@552
|
369 // De-interleave the data
|
chris@552
|
370 if(gHvInputBuffers != NULL) {
|
chris@552
|
371 for(unsigned int n = 0; n < context->audioFrames; n++) {
|
chris@552
|
372 for(unsigned int ch = 0; ch < gHvInputChannels; ch++) {
|
chris@552
|
373 if(ch >= context->audioInChannels+context->analogInChannels) {
|
chris@552
|
374 // THESE ARE PARAMETER INPUT 'CHANNELS' USED FOR ROUTING
|
chris@552
|
375 // 'sensor' outputs from routing channels of dac~ are passed through here
|
chris@552
|
376 break;
|
chris@552
|
377 } else {
|
chris@552
|
378 // If more than 2 ADC inputs are used in the pd patch, route the analog inputs
|
chris@552
|
379 // i.e. ADC3->analogIn0 etc. (first two are always audio inputs)
|
chris@552
|
380 if(ch >= context->audioInChannels) {
|
chris@552
|
381 int m = n/2;
|
chris@552
|
382 float mIn = context->analogIn[m*context->analogInChannels + (ch-context->audioInChannels)];
|
chris@552
|
383 gHvInputBuffers[ch * context->audioFrames + n] = mIn;
|
chris@552
|
384 } else {
|
chris@552
|
385 gHvInputBuffers[ch * context->audioFrames + n] = context->audioIn[n * context->audioInChannels + ch];
|
chris@552
|
386 }
|
chris@552
|
387 }
|
chris@552
|
388 }
|
chris@552
|
389 }
|
chris@552
|
390 }
|
chris@552
|
391
|
chris@552
|
392 // Bela digital in
|
chris@552
|
393 // note: in multiple places below we assume that the number of digital frames is same as number of audio
|
chris@552
|
394 // Bela digital in at message-rate
|
chris@552
|
395 dcm.processInput(context->digital, context->digitalFrames);
|
chris@552
|
396
|
chris@552
|
397 // Bela digital in at signal-rate
|
chris@552
|
398 if(gDigitalSigInChannelsInUse > 0)
|
chris@552
|
399 {
|
chris@552
|
400 unsigned int j, k;
|
chris@552
|
401 float *p0, *p1;
|
chris@552
|
402 const unsigned int gLibpdBlockSize = context->audioFrames;
|
chris@552
|
403 const unsigned int audioFrameBase = 0;
|
chris@552
|
404 float* gInBuf = gHvInputBuffers;
|
chris@552
|
405 // block below copy/pasted from libpd, except
|
chris@552
|
406 // 16 has been replaced with gDigitalSigInChannelsInUse
|
chris@552
|
407 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
|
chris@552
|
408 unsigned int digitalFrame = audioFrameBase + j;
|
chris@552
|
409 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel;
|
chris@552
|
410 k < gDigitalSigInChannelsInUse; ++k, p1 += gLibpdBlockSize) {
|
chris@552
|
411 if(dcm.isSignalRate(k) && dcm.isInput(k)){ // only process input channels that are handled at signal rate
|
chris@552
|
412 *p1 = digitalRead(context, digitalFrame, k);
|
chris@552
|
413 }
|
chris@552
|
414 }
|
chris@552
|
415 }
|
chris@552
|
416 }
|
chris@552
|
417
|
chris@552
|
418
|
chris@552
|
419 // replacement for bang~ object
|
chris@552
|
420 //hv_vscheduleMessageForReceiver(gHeavyContext, "bela_bang", 0.0f, "b");
|
chris@552
|
421
|
chris@552
|
422 hv_bela_process_inline(gHeavyContext, gHvInputBuffers, gHvOutputBuffers, context->audioFrames);
|
chris@552
|
423
|
chris@552
|
424 // Bela digital out
|
chris@552
|
425 // Bela digital out at signal-rate
|
chris@552
|
426 if(gDigitalSigOutChannelsInUse > 0)
|
chris@552
|
427 {
|
chris@552
|
428 unsigned int j, k;
|
chris@552
|
429 float *p0, *p1;
|
chris@552
|
430 const unsigned int gLibpdBlockSize = context->audioFrames;
|
chris@552
|
431 const unsigned int audioFrameBase = 0;
|
chris@552
|
432 float* gOutBuf = gHvOutputBuffers;
|
chris@552
|
433 // block below copy/pasted from libpd, except
|
chris@552
|
434 // context->digitalChannels has been replaced with gDigitalSigOutChannelsInUse
|
chris@552
|
435 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
|
chris@552
|
436 unsigned int digitalFrame = (audioFrameBase + j);
|
chris@552
|
437 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel;
|
chris@552
|
438 k < gDigitalSigOutChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
chris@552
|
439 if(dcm.isSignalRate(k) && dcm.isOutput(k)){ // only process output channels that are handled at signal rate
|
chris@552
|
440 digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5);
|
chris@552
|
441 }
|
chris@552
|
442 }
|
chris@552
|
443 }
|
chris@552
|
444 }
|
chris@552
|
445 // Bela digital out at message-rate
|
chris@552
|
446 dcm.processOutput(context->digital, context->digitalFrames);
|
chris@552
|
447
|
chris@552
|
448 // Bela scope
|
chris@552
|
449 if(gScopeChannelsInUse > 0)
|
chris@552
|
450 {
|
chris@552
|
451 unsigned int j, k;
|
chris@552
|
452 float *p0, *p1;
|
chris@552
|
453 const unsigned int gLibpdBlockSize = context->audioFrames;
|
chris@552
|
454 float* gOutBuf = gHvOutputBuffers;
|
chris@552
|
455
|
chris@552
|
456 // block below copy/pasted from libpd
|
chris@552
|
457 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
|
chris@552
|
458 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstScopeChannel; k < gScopeChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
chris@552
|
459 gScopeOut[k] = *p1;
|
chris@552
|
460 }
|
chris@552
|
461 scope.log(gScopeOut);
|
chris@552
|
462 }
|
chris@552
|
463 }
|
chris@552
|
464
|
chris@552
|
465 // Interleave the output data
|
chris@552
|
466 if(gHvOutputBuffers != NULL) {
|
chris@552
|
467 for(unsigned int n = 0; n < context->audioFrames; n++) {
|
chris@552
|
468
|
chris@552
|
469 /*
|
chris@552
|
470 * MODIFICATION
|
chris@552
|
471 * ------------
|
chris@552
|
472 * Processing for tremolo effect while writing libpd output to Bela output buffer
|
chris@552
|
473 */
|
chris@552
|
474
|
chris@552
|
475 // Generate a sinewave with frequency set by gTremoloRate
|
chris@552
|
476 // and amplitude from -0.5 to 0.5
|
chris@552
|
477 float lfo = sinf(gPhase) * 0.5;
|
chris@552
|
478 // Keep track and wrap the phase of the sinewave
|
chris@552
|
479 gPhase += 2.0 * M_PI * gTremoloRate * gInverseSampleRate;
|
chris@552
|
480 if(gPhase > 2.0 * M_PI)
|
chris@552
|
481 gPhase -= 2.0 * M_PI;
|
chris@552
|
482
|
chris@552
|
483 /*********/
|
chris@552
|
484
|
chris@552
|
485 for(unsigned int ch = 0; ch < gHvOutputChannels; ch++) {
|
chris@552
|
486 if(ch <= context->audioOutChannels+context->analogOutChannels) {
|
chris@552
|
487 if(ch >= context->audioOutChannels) {
|
chris@552
|
488 int m = n/2;
|
chris@552
|
489 context->analogOut[m * context->analogFrames + (ch-context->audioOutChannels)] = constrain(gHvOutputBuffers[ch*context->audioFrames + n],0.0,1.0);
|
chris@552
|
490 } else {
|
chris@552
|
491 context->audioOut[n * context->audioOutChannels + ch] = gHvOutputBuffers[ch * context->audioFrames + n] * lfo; // MODIFICATION (* lfo)
|
chris@552
|
492 }
|
chris@552
|
493 }
|
chris@552
|
494 }
|
chris@552
|
495 }
|
chris@552
|
496 }
|
chris@552
|
497
|
chris@552
|
498 }
|
chris@552
|
499
|
chris@552
|
500
|
chris@552
|
501 void cleanup(BelaContext *context, void *userData)
|
chris@552
|
502 {
|
chris@552
|
503
|
chris@552
|
504 hv_bela_free(gHeavyContext);
|
chris@552
|
505 if(gHvInputBuffers != NULL)
|
chris@552
|
506 free(gHvInputBuffers);
|
chris@552
|
507 if(gHvOutputBuffers != NULL)
|
chris@552
|
508 free(gHvOutputBuffers);
|
chris@552
|
509 delete[] gScopeOut;
|
chris@552
|
510 }
|