Mercurial > hg > beaglert
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 } |