comparison examples/08-PureData/customRender/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 * Created on: Oct 24, 2014
5 * Author: parallels
6 */
7
8 #include <Bela.h>
9 #include <DigitalChannelManager.h>
10 #include <cmath>
11 #include <I2c_Codec.h>
12 #include <PRU.h>
13 #include <stdio.h>
14 #include <libpd/z_libpd.h>
15 #include <libpd/s_stuff.h>
16 #include <UdpServer.h>
17 #include <Midi.h>
18 #include <Scope.h>
19
20 /*
21 * MODIFICATION
22 * ------------
23 * Global variables for tremolo effect applied to libpd output
24 */
25
26 float gTremoloRate = 4.0;
27 float gPhase;
28 float gInverseSampleRate;
29
30 /*********/
31
32 // if you are 100% sure of what value was used to compile libpd/puredata, then
33 // you could #define gBufLength instead of getting it at runtime. It has proved to give some 0.3%
34 // performance boost when it is 8 (thanks to vectorize optimizations I guess).
35 int gBufLength;
36
37 float* gInBuf;
38 float* gOutBuf;
39
40 void pdnoteon(int ch, int pitch, int vel) {
41 printf("noteon: %d %d %d\n", ch, pitch, vel);
42 }
43
44 void Bela_printHook(const char *recv){
45 rt_printf("%s", recv);
46 }
47 #define PARSE_MIDI
48 static Midi midi;
49 static DigitalChannelManager dcm;
50
51 void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){
52 libpd_float((char*)receiverName, (float)state);
53 // rt_printf("%s: %d\n", (char*)receiverName, state);
54 }
55
56 #define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs)
57
58 void Bela_messageHook(const char *source, const char *symbol, int argc, t_atom *argv){
59 if(strcmp(source, "bela_setDigital") == 0){
60 // symbol is the direction, argv[0] is the channel, argv[1] (optional)
61 // is signal("sig" or "~") or message("message", default) rate
62 bool isMessageRate = true; // defaults to message rate
63 bool direction = 0; // initialize it just to avoid the compiler's warning
64 bool disable = false;
65 if(strcmp(symbol, "in") == 0){
66 direction = INPUT;
67 } else if(strcmp(symbol, "out") == 0){
68 direction = OUTPUT;
69 } else if(strcmp(symbol, "disable") == 0){
70 disable = true;
71 } else {
72 return;
73 }
74 if(argc == 0){
75 return;
76 } else if (libpd_is_float(&argv[0]) == false){
77 return;
78 }
79 int channel = libpd_get_float(&argv[0]) - LIBPD_DIGITAL_OFFSET;
80 if(disable == true){
81 dcm.unmanage(channel);
82 return;
83 }
84 if(argc >= 2){
85 t_atom* a = &argv[1];
86 if(libpd_is_symbol(a)){
87 char *s = libpd_get_symbol(a);
88 if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){
89 isMessageRate = false;
90 }
91 }
92 }
93 dcm.manage(channel, direction, isMessageRate);
94 }
95 }
96
97 void Bela_floatHook(const char *source, float value){
98
99 /*
100 * MODIFICATION
101 * ------------
102 * Parse float sent to receiver 'tremoloRate' and assign it to a global variable
103 * N.B. When using libpd receiver names need to be registered (see setup() function below)
104 */
105 if(strncmp(source, "tremoloRate", 11) == 0){
106 gTremoloRate = value;
107 }
108
109 /*********/
110
111 // let's make this as optimized as possible for built-in digital Out parsing
112 // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26
113 static int prefixLength = 15; // strlen("bela_digitalOut")
114 if(strncmp(source, "bela_digitalOut", prefixLength)==0){
115 if(source[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2)
116 if(source[prefixLength + 1] != 0){
117 // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi
118 int receiver = ((source[prefixLength] - 48) * 10);
119 receiver += (source[prefixLength+1] - 48);
120 unsigned int channel = receiver - 11; // go back to the actual Bela digital channel number
121 if(channel < 16){ //16 is the hardcoded value for the number of digital channels
122 dcm.setValue(channel, value);
123 }
124 }
125 }
126 }
127 }
128
129 char receiverNames[16][21]={
130 {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"},
131 {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"},
132 {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"},
133 {"bela_digitalIn26"}
134 };
135
136 static unsigned int gAnalogChannelsInUse;
137 static unsigned int gLibpdBlockSize;
138 // 2 audio + (up to)8 analog + (up to) 16 digital + 4 scope outputs
139 static const unsigned int gChannelsInUse = 30;
140 //static const unsigned int gFirstAudioChannel = 0;
141 static const unsigned int gFirstAnalogChannel = 2;
142 static const unsigned int gFirstDigitalChannel = 10;
143 static const unsigned int gFirstScopeChannel = 26;
144
145 Scope scope;
146 unsigned int gScopeChannelsInUse = 4;
147 float* gScopeOut;
148
149 bool setup(BelaContext *context, void *userData)
150 {
151
152 /*
153 * MODIFICATION
154 * ------------
155 * Initialise variables for tremolo effect
156 */
157
158 gInverseSampleRate = 1.0 / context->audioSampleRate;
159 gPhase = 0.0;
160
161 /*********/
162
163 scope.setup(gScopeChannelsInUse, context->audioSampleRate);
164 gScopeOut = new float[gScopeChannelsInUse];
165
166 // Check first of all if file exists. Will actually open it later.
167 char file[] = "_main.pd";
168 char folder[] = "./";
169 unsigned int strSize = strlen(file) + strlen(folder) + 1;
170 char* str = (char*)malloc(sizeof(char) * strSize);
171 snprintf(str, strSize, "%s%s", folder, file);
172 if(access(str, F_OK) == -1 ) {
173 printf("Error file %s/%s not found. The %s file should be your main patch.\n", folder, file, file);
174 return false;
175 }
176 if(context->analogInChannels != context->analogOutChannels ||
177 context->audioInChannels != context->audioOutChannels){
178 printf("This project requires the number of inputs and the number of outputs to be the same\n");
179 return false;
180 }
181 // analog setup
182 gAnalogChannelsInUse = context->analogInChannels;
183
184 // digital setup
185 dcm.setCallback(sendDigitalMessage);
186 if(context->digitalChannels > 0){
187 for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){
188 dcm.setCallbackArgument(ch, receiverNames[ch]);
189 }
190 }
191
192 midi.readFrom(0);
193 midi.writeTo(0);
194 #ifdef PARSE_MIDI
195 midi.enableParser(true);
196 #else
197 midi.enableParser(false);
198 #endif /* PARSE_MIDI */
199 // udpServer.bindToPort(1234);
200
201 gLibpdBlockSize = libpd_blocksize();
202 // check that we are not running with a blocksize smaller than gLibPdBlockSize
203 // We could still make it work, but the load would be executed unevenly between calls to render
204 if(context->audioFrames < gLibpdBlockSize){
205 fprintf(stderr, "Error: minimum block size must be %d\n", gLibpdBlockSize);
206 return false;
207 }
208 // set hooks before calling libpd_init
209 libpd_set_printhook(Bela_printHook);
210 libpd_set_floathook(Bela_floatHook);
211 libpd_set_messagehook(Bela_messageHook);
212 libpd_set_noteonhook(pdnoteon);
213 //TODO: add hooks for other midi events and generate MIDI output appropriately
214 libpd_init();
215 //TODO: ideally, we would analyse the ASCII of the patch file and find out which in/outs to use
216 libpd_init_audio(gChannelsInUse, gChannelsInUse, context->audioSampleRate);
217 gInBuf = libpd_get_sys_soundin();
218 gOutBuf = libpd_get_sys_soundout();
219
220 libpd_start_message(1); // one entry in list
221 libpd_add_float(1.0f);
222 libpd_finish_message("pd", "dsp");
223
224 gBufLength = max(gLibpdBlockSize, context->audioFrames);
225
226
227 // bind your receivers here
228 libpd_bind("bela_digitalOut11");
229 libpd_bind("bela_digitalOut12");
230 libpd_bind("bela_digitalOut13");
231 libpd_bind("bela_digitalOut14");
232 libpd_bind("bela_digitalOut15");
233 libpd_bind("bela_digitalOut16");
234 libpd_bind("bela_digitalOut17");
235 libpd_bind("bela_digitalOut18");
236 libpd_bind("bela_digitalOut19");
237 libpd_bind("bela_digitalOut20");
238 libpd_bind("bela_digitalOut21");
239 libpd_bind("bela_digitalOut22");
240 libpd_bind("bela_digitalOut23");
241 libpd_bind("bela_digitalOut24");
242 libpd_bind("bela_digitalOut25");
243 libpd_bind("bela_digitalOut26");
244 libpd_bind("bela_setDigital");
245 /*
246 * MODIFICATION
247 * ------------
248 * Bind an additional receiver for the tremoloRate parameter
249 */
250 libpd_bind("tremoloRate");
251 /*********/
252
253 // open patch [; pd open file folder(
254 void* patch = libpd_openfile(file, folder);
255 if(patch == NULL){
256 printf("Error: file %s/%s is corrupted.\n", folder, file);
257 return false;
258 }
259 return true;
260 }
261
262 // render() is called regularly at the highest priority by the audio engine.
263 // Input and output are given from the audio hardware and the other
264 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
265 // will be 0.
266
267 void render(BelaContext *context, void *userData)
268 {
269 int num;
270 // the safest thread-safe option to handle MIDI input is to process the MIDI buffer
271 // from the audio thread.
272 #ifdef PARSE_MIDI
273 while((num = midi.getParser()->numAvailableMessages()) > 0){
274 static MidiChannelMessage message;
275 message = midi.getParser()->getNextChannelMessage();
276 //message.prettyPrint(); // use this to print beautified message (channel, data bytes)
277 switch(message.getType()){
278 case kmmNoteOn:
279 {
280 int noteNumber = message.getDataByte(0);
281 int velocity = message.getDataByte(1);
282 int channel = message.getChannel();
283 libpd_noteon(channel, noteNumber, velocity);
284 break;
285 }
286 case kmmNoteOff:
287 {
288 /* PureData does not seem to handle noteoff messages as per the MIDI specs,
289 * so that the noteoff velocity is ignored. Here we convert them to noteon
290 * with a velocity of 0.
291 */
292 int noteNumber = message.getDataByte(0);
293 // int velocity = message.getDataByte(1); // would be ignored by Pd
294 int channel = message.getChannel();
295 libpd_noteon(channel, noteNumber, 0);
296 break;
297 }
298 case kmmControlChange:
299 {
300 int channel = message.getChannel();
301 int controller = message.getDataByte(0);
302 int value = message.getDataByte(1);
303 libpd_controlchange(channel, controller, value);
304 break;
305 }
306 case kmmProgramChange:
307 {
308 int channel = message.getChannel();
309 int program = message.getDataByte(0);
310 libpd_programchange(channel, program);
311 break;
312 }
313 case kmmPolyphonicKeyPressure:
314 {
315 int channel = message.getChannel();
316 int pitch = message.getDataByte(0);
317 int value = message.getDataByte(1);
318 libpd_polyaftertouch(channel, pitch, value);
319 break;
320 }
321 case kmmChannelPressure:
322 {
323 int channel = message.getChannel();
324 int value = message.getDataByte(0);
325 libpd_aftertouch(channel, value);
326 break;
327 }
328 case kmmPitchBend:
329 {
330 int channel = message.getChannel();
331 int value = ((message.getDataByte(1) << 7)| message.getDataByte(0)) - 8192;
332 libpd_pitchbend(channel, value);
333 break;
334 }
335 case kmmNone:
336 case kmmAny:
337 break;
338 }
339 }
340 #else
341 int input;
342 while((input = midi.getInput()) >= 0){
343 libpd_midibyte(0, input);
344 }
345 #endif /* PARSE_MIDI */
346
347 static unsigned int numberOfPdBlocksToProcess = gBufLength / gLibpdBlockSize;
348
349 for(unsigned int tick = 0; tick < numberOfPdBlocksToProcess; ++tick){
350 unsigned int audioFrameBase = gLibpdBlockSize * tick;
351 unsigned int j;
352 unsigned int k;
353 float* p0;
354 float* p1;
355 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
356 for (k = 0, p1 = p0; k < context->audioInChannels; k++, p1 += gLibpdBlockSize) {
357 *p1 = audioRead(context, audioFrameBase + j, k);
358 }
359 }
360 // then analogs
361 // this loop resamples by ZOH, as needed, using m
362 if(context->analogInChannels == 8 ){ //hold the value for two frames
363 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
364 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) {
365 unsigned int analogFrame = (audioFrameBase + j) / 2;
366 *p1 = analogRead(context, analogFrame, k);
367 }
368 }
369 } else if(context->analogInChannels == 4){ //write every frame
370 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
371 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) {
372 unsigned int analogFrame = audioFrameBase + j;
373 *p1 = analogRead(context, analogFrame, k);
374 }
375 }
376 } else if(context->analogInChannels == 2){ //drop every other frame
377 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
378 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) {
379 unsigned int analogFrame = (audioFrameBase + j) * 2;
380 *p1 = analogRead(context, analogFrame, k);
381 }
382 }
383 }
384
385 // Bela digital input
386 // note: in multiple places below we assume that the number of digitals is same as number of audio
387 // digital in at message-rate
388 dcm.processInput(&context->digital[audioFrameBase], gLibpdBlockSize);
389
390 // digital in at signal-rate
391 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
392 unsigned int digitalFrame = audioFrameBase + j;
393 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel;
394 k < 16; ++k, p1 += gLibpdBlockSize) {
395 if(dcm.isSignalRate(k) && dcm.isInput(k)){ // only process input channels that are handled at signal rate
396 *p1 = digitalRead(context, digitalFrame, k);
397 }
398 }
399 }
400
401 libpd_process_sys(); // process the block
402
403 //digital out
404 // digital out at signal-rate
405 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
406 unsigned int digitalFrame = (audioFrameBase + j);
407 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel;
408 k < context->digitalChannels; k++, p1 += gLibpdBlockSize) {
409 if(dcm.isSignalRate(k) && dcm.isOutput(k)){ // only process output channels that are handled at signal rate
410 digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5);
411 }
412 }
413 }
414
415 // digital out at message-rate
416 dcm.processOutput(&context->digital[audioFrameBase], gLibpdBlockSize);
417
418 //audio
419 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
420
421 /*
422 * MODIFICATION
423 * ------------
424 * Processing for tremolo effect while writing libpd output to Bela output buffer
425 */
426
427 // Generate a sinewave with frequency set by gTremoloRate
428 // and amplitude from -0.5 to 0.5
429 float lfo = sinf(gPhase) * 0.5;
430 // Keep track and wrap the phase of the sinewave
431 gPhase += 2.0 * M_PI * gTremoloRate * gInverseSampleRate;
432 if(gPhase > 2.0 * M_PI)
433 gPhase -= 2.0 * M_PI;
434
435 /*********/
436
437 for (k = 0, p1 = p0; k < context->audioOutChannels; k++, p1 += gLibpdBlockSize) {
438 audioWrite(context, audioFrameBase + j, k, *p1 * lfo); // MODIFICATION (* lfo)
439 }
440 }
441
442 //scope
443 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
444 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstScopeChannel; k < gScopeChannelsInUse; k++, p1 += gLibpdBlockSize) {
445 gScopeOut[k] = *p1;
446 }
447 scope.log(gScopeOut[0], gScopeOut[1], gScopeOut[2], gScopeOut[3]);
448 }
449
450
451 //analog
452 if(context->analogOutChannels == 8){
453 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j += 2, p0 += 2) { //write every two frames
454 unsigned int analogFrame = (audioFrameBase + j) / 2;
455 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
456 analogWriteOnce(context, analogFrame, k, *p1);
457 }
458 }
459 } else if(context->analogOutChannels == 4){ //write every frame
460 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
461 unsigned int analogFrame = (audioFrameBase + j);
462 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
463 analogWriteOnce(context, analogFrame, k, *p1);
464 }
465 }
466 } else if(context->analogOutChannels == 2){ //write every frame twice
467 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
468 for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
469 int analogFrame = audioFrameBase * 2 + j * 2;
470 analogWriteOnce(context, analogFrame, k, *p1);
471 analogWriteOnce(context, analogFrame + 1, k, *p1);
472 }
473 }
474 }
475 }
476 }
477
478 // cleanup() is called once at the end, after the audio has stopped.
479 // Release any resources that were allocated in setup().
480
481 void cleanup(BelaContext *context, void *userData)
482 {
483 delete [] gScopeOut;
484 }