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