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 }