annotate examples/08-PureData/basic_libpd/render.cpp @ 464:8fcfbfb32aa0 prerelease

Examples reorder with subdirectories. Added header to each project. Moved Doxygen to bottom of render.cpp.
author Robert Jack <robert.h.jack@gmail.com>
date Mon, 20 Jun 2016 16:20:38 +0100
parents
children
rev   line source
robert@464 1 /*
robert@464 2 * render.cpp
robert@464 3 *
robert@464 4 * Created on: Oct 24, 2014
robert@464 5 * Author: parallels
robert@464 6 */
robert@464 7
robert@464 8 #include <Bela.h>
robert@464 9 #include <DigitalChannelManager.h>
robert@464 10 #include <cmath>
robert@464 11 #include <I2c_Codec.h>
robert@464 12 #include <PRU.h>
robert@464 13 #include <stdio.h>
robert@464 14 #include "z_libpd.h"
robert@464 15 #include "s_stuff.h"
robert@464 16 #include <UdpServer.h>
robert@464 17 #include <Midi.h>
robert@464 18 //extern t_sample* sys_soundin;
robert@464 19 //extern t_sample* sys_soundout;
robert@464 20 // if you are 100% sure of what value was used to compile libpd/puredata, then
robert@464 21 // you could #define this instead of getting it at runtime. It has proved to give some 0.3%
robert@464 22 // performance boost when it is 8 (thanks to vectorize optimizations I guess).
robert@464 23 int gBufLength;
robert@464 24
robert@464 25 float* gInBuf;
robert@464 26 float* gOutBuf;
robert@464 27
robert@464 28 void pdnoteon(int ch, int pitch, int vel) {
robert@464 29 printf("noteon: %d %d %d\n", ch, pitch, vel);
robert@464 30 }
robert@464 31
robert@464 32 void Bela_printHook(const char *recv){
robert@464 33 rt_printf("%s", recv);
robert@464 34 }
robert@464 35
robert@464 36 void libpdReadFilesLoop(){
robert@464 37 while(!gShouldStop){
robert@464 38 // check for modified sockets/file descriptors
robert@464 39 // (libpd would normally do this every block WITHIN the audio thread)
robert@464 40 // not sure if this is thread-safe at the moment
robert@464 41 libpd_sys_microsleep(0);
robert@464 42 usleep(1000);
robert@464 43 }
robert@464 44 }
robert@464 45
robert@464 46 #define PARSE_MIDI
robert@464 47 static AuxiliaryTask libpdReadFilesTask;
robert@464 48 static Midi midi;
robert@464 49 static DigitalChannelManager dcm;
robert@464 50 //UdpServer udpServer;
robert@464 51
robert@464 52 void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){
robert@464 53 libpd_float((char*)receiverName, (float)state);
robert@464 54 // rt_printf("%s: %d\n", (char*)receiverName, state);
robert@464 55 }
robert@464 56
robert@464 57 #define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs)
robert@464 58
robert@464 59 void Bela_messageHook(const char *source, const char *symbol, int argc, t_atom *argv){
robert@464 60 if(strcmp(source, "bela_setDigital") == 0){
robert@464 61 // symbol is the direction, argv[0] is the channel, argv[1] (optional)
robert@464 62 // is signal("sig" or "~") or message("message", default) rate
robert@464 63 bool isMessageRate = true; // defaults to message rate
robert@464 64 bool direction = 0; // initialize it just to avoid the compiler's warning
robert@464 65 bool disable = false;
robert@464 66 if(strcmp(symbol, "in") == 0){
robert@464 67 direction = INPUT;
robert@464 68 } else if(strcmp(symbol, "out") == 0){
robert@464 69 direction = OUTPUT;
robert@464 70 } else if(strcmp(symbol, "disable") == 0){
robert@464 71 disable = true;
robert@464 72 } else {
robert@464 73 return;
robert@464 74 }
robert@464 75 if(argc == 0){
robert@464 76 return;
robert@464 77 } else if (libpd_is_float(&argv[0]) == false){
robert@464 78 return;
robert@464 79 }
robert@464 80 int channel = libpd_get_float(&argv[0]) - LIBPD_DIGITAL_OFFSET;
robert@464 81 if(disable == true){
robert@464 82 dcm.unmanage(channel);
robert@464 83 return;
robert@464 84 }
robert@464 85 if(argc >= 2){
robert@464 86 t_atom* a = &argv[1];
robert@464 87 if(libpd_is_symbol(a)){
robert@464 88 char *s = libpd_get_symbol(a);
robert@464 89 if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){
robert@464 90 isMessageRate = false;
robert@464 91 }
robert@464 92 }
robert@464 93 }
robert@464 94 dcm.manage(channel, direction, isMessageRate);
robert@464 95 }
robert@464 96 }
robert@464 97
robert@464 98 void Bela_floatHook(const char *source, float value){
robert@464 99 // let's make this as optimized as possible for built-in digital Out parsing
robert@464 100 // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26
robert@464 101 static int prefixLength = 15; // strlen("bela_digitalOut")
robert@464 102 if(strncmp(source, "bela_digitalOut", prefixLength)==0){
robert@464 103 if(source[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2)
robert@464 104 if(source[prefixLength + 1] != 0){
robert@464 105 // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi
robert@464 106 int receiver = ((source[prefixLength] - 48) * 10);
robert@464 107 receiver += (source[prefixLength+1] - 48);
robert@464 108 unsigned int channel = receiver - 11; // go back to the actual Bela digital channel number
robert@464 109 if(channel >= 0 && channel < 16){ //16 is the hardcoded value for the number of digital channels
robert@464 110 dcm.setValue(channel, value);
robert@464 111 }
robert@464 112 }
robert@464 113 }
robert@464 114 }
robert@464 115 }
robert@464 116
robert@464 117 char receiverNames[16][21]={
robert@464 118 {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"},
robert@464 119 {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"},
robert@464 120 {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"},
robert@464 121 {"bela_digitalIn26"}
robert@464 122 };
robert@464 123
robert@464 124 static unsigned int analogChannelsInUse;
robert@464 125 static unsigned int gLibpdBlockSize;
robert@464 126 static unsigned int gChannelsInUse = 26;
robert@464 127
robert@464 128 bool setup(BelaContext *context, void *userData)
robert@464 129 {
robert@464 130 dcm.setCallback(sendDigitalMessage);
robert@464 131 analogChannelsInUse = min(context->analogChannels, gChannelsInUse - context->audioChannels - context->digitalChannels);
robert@464 132 if(context->digitalChannels > 0){
robert@464 133 for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){
robert@464 134 dcm.setCallbackArgument(ch, receiverNames[ch]);
robert@464 135 }
robert@464 136 }
robert@464 137 midi.readFrom(0);
robert@464 138 midi.writeTo(0);
robert@464 139 #ifdef PARSE_MIDI
robert@464 140 midi.enableParser(true);
robert@464 141 #else
robert@464 142 midi.enableParser(false);
robert@464 143 #endif /* PARSE_MIDI */
robert@464 144 // gChannelsInUse = min((int)(context->analogChannels+context->audioChannels), (int)gChannelsInUse);
robert@464 145 // udpServer.bindToPort(1234);
robert@464 146
robert@464 147 gLibpdBlockSize = libpd_blocksize();
robert@464 148 // check that we are not running with a blocksize smaller than gLibPdBlockSize
robert@464 149 // it would still work, but the load would be executed unevenly between calls to render
robert@464 150 if(context->audioFrames < gLibpdBlockSize){
robert@464 151 fprintf(stderr, "Error: minimum block size must be %d\n", gLibpdBlockSize);
robert@464 152 return false;
robert@464 153 }
robert@464 154 // set hooks before calling libpd_init
robert@464 155 libpd_set_printhook(Bela_printHook);
robert@464 156 libpd_set_floathook(Bela_floatHook);
robert@464 157 libpd_set_messagehook(Bela_messageHook);
robert@464 158 libpd_set_noteonhook(pdnoteon);
robert@464 159 //TODO: add hooks for other midi events and generate MIDI output appropriately
robert@464 160 libpd_init();
robert@464 161 //TODO: ideally, we would analyse the ASCII of the patch file and find out which in/outs to use
robert@464 162 libpd_init_audio(gChannelsInUse, gChannelsInUse, context->audioSampleRate);
robert@464 163 gInBuf = libpd_get_sys_soundin();
robert@464 164 gOutBuf = libpd_get_sys_soundout();
robert@464 165
robert@464 166 libpd_start_message(1); // one entry in list
robert@464 167 libpd_add_float(1.0f);
robert@464 168 libpd_finish_message("pd", "dsp");
robert@464 169
robert@464 170 gBufLength = max(gLibpdBlockSize, context->audioFrames);
robert@464 171
robert@464 172
robert@464 173 // bind your receivers here
robert@464 174 libpd_bind("bela_digitalOut11");
robert@464 175 libpd_bind("bela_digitalOut12");
robert@464 176 libpd_bind("bela_digitalOut13");
robert@464 177 libpd_bind("bela_digitalOut14");
robert@464 178 libpd_bind("bela_digitalOut15");
robert@464 179 libpd_bind("bela_digitalOut16");
robert@464 180 libpd_bind("bela_digitalOut17");
robert@464 181 libpd_bind("bela_digitalOut18");
robert@464 182 libpd_bind("bela_digitalOut19");
robert@464 183 libpd_bind("bela_digitalOut20");
robert@464 184 libpd_bind("bela_digitalOut21");
robert@464 185 libpd_bind("bela_digitalOut22");
robert@464 186 libpd_bind("bela_digitalOut23");
robert@464 187 libpd_bind("bela_digitalOut24");
robert@464 188 libpd_bind("bela_digitalOut25");
robert@464 189 libpd_bind("bela_digitalOut26");
robert@464 190 libpd_bind("bela_setDigital");
robert@464 191
robert@464 192 char file[] = "_main.pd";
robert@464 193 char folder[] = "./";
robert@464 194 // open patch [; pd open file folder(
robert@464 195 libpd_openfile(file, folder);
robert@464 196 libpdReadFilesTask = Bela_createAuxiliaryTask(libpdReadFilesLoop, 60, "libpdReadFiles");
robert@464 197 Bela_scheduleAuxiliaryTask(libpdReadFilesTask);
robert@464 198
robert@464 199
robert@464 200 return true;
robert@464 201 }
robert@464 202
robert@464 203 // render() is called regularly at the highest priority by the audio engine.
robert@464 204 // Input and output are given from the audio hardware and the other
robert@464 205 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
robert@464 206 // will be 0.
robert@464 207
robert@464 208 void render(BelaContext *context, void *userData)
robert@464 209 {
robert@464 210 int num;
robert@464 211 // the safest thread-safe option to handle MIDI input is to process the MIDI buffer
robert@464 212 // from the audio thread.
robert@464 213 #ifdef PARSE_MIDI
robert@464 214 while((num = midi.getParser()->numAvailableMessages()) > 0){
robert@464 215 static MidiChannelMessage message;
robert@464 216 message = midi.getParser()->getNextChannelMessage();
robert@464 217 //message.prettyPrint(); // use this to print beautified message (channel, data bytes)
robert@464 218 switch(message.getType()){
robert@464 219 case kmmNoteOn:
robert@464 220 {
robert@464 221 int noteNumber = message.getDataByte(0);
robert@464 222 int velocity = message.getDataByte(1);
robert@464 223 int channel = message.getChannel();
robert@464 224 libpd_noteon(channel, noteNumber, velocity);
robert@464 225 break;
robert@464 226 }
robert@464 227 case kmmNoteOff:
robert@464 228 {
robert@464 229 /* PureData does not seem to handle noteoff messages as per the MIDI specs,
robert@464 230 * so that the noteoff velocity is ignored. Here we convert them to noteon
robert@464 231 * with a velocity of 0.
robert@464 232 */
robert@464 233 int noteNumber = message.getDataByte(0);
robert@464 234 // int velocity = message.getDataByte(1); // would be ignored by Pd
robert@464 235 int channel = message.getChannel();
robert@464 236 libpd_noteon(channel, noteNumber, 0);
robert@464 237 break;
robert@464 238 }
robert@464 239 case kmmControlChange:
robert@464 240 {
robert@464 241 int channel = message.getChannel();
robert@464 242 int controller = message.getDataByte(0);
robert@464 243 int value = message.getDataByte(1);
robert@464 244 libpd_controlchange(channel, controller, value);
robert@464 245 break;
robert@464 246 }
robert@464 247 case kmmProgramChange:
robert@464 248 {
robert@464 249 int channel = message.getChannel();
robert@464 250 int program = message.getDataByte(0);
robert@464 251 libpd_programchange(channel, program);
robert@464 252 break;
robert@464 253 }
robert@464 254 case kmmPolyphonicKeyPressure:
robert@464 255 {
robert@464 256 int channel = message.getChannel();
robert@464 257 int pitch = message.getDataByte(0);
robert@464 258 int value = message.getDataByte(1);
robert@464 259 libpd_polyaftertouch(channel, pitch, value);
robert@464 260 break;
robert@464 261 }
robert@464 262 case kmmChannelPressure:
robert@464 263 {
robert@464 264 int channel = message.getChannel();
robert@464 265 int value = message.getDataByte(0);
robert@464 266 libpd_aftertouch(channel, value);
robert@464 267 break;
robert@464 268 }
robert@464 269 case kmmPitchBend:
robert@464 270 {
robert@464 271 int channel = message.getChannel();
robert@464 272 int value = (message.getDataByte(1) << 7)| message.getDataByte(0);
robert@464 273 libpd_pitchbend(channel, value);
robert@464 274 break;
robert@464 275 }
robert@464 276 case kmmNone:
robert@464 277 case kmmAny:
robert@464 278 break;
robert@464 279 }
robert@464 280 }
robert@464 281 #else
robert@464 282 int input;
robert@464 283 while((input = midi.getInput()) >= 0){
robert@464 284 libpd_midibyte(0, input);
robert@464 285 }
robert@464 286 #endif /* PARSE_MIDI */
robert@464 287
robert@464 288 static unsigned int numberOfPdBlocksToProcess = gBufLength / gLibpdBlockSize;
robert@464 289
robert@464 290 // these are reset at every audio callback. Persistence across audio callbacks
robert@464 291 // is handled by the core code.
robert@464 292 // setDataOut = 0;
robert@464 293 // clearDataOut = 0;
robert@464 294
robert@464 295 for(unsigned int tick = 0; tick < numberOfPdBlocksToProcess; ++tick){
robert@464 296 unsigned int audioFrameBase = gLibpdBlockSize * tick;
robert@464 297 unsigned int j;
robert@464 298 unsigned int k;
robert@464 299 float* p0;
robert@464 300 float* p1;
robert@464 301 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 302 for (k = 0, p1 = p0; k < context->audioChannels; k++, p1 += gLibpdBlockSize) {
robert@464 303 *p1 = audioRead(context, audioFrameBase + j, k);
robert@464 304 }
robert@464 305 }
robert@464 306 // then analogs
robert@464 307 // this loop resamples by ZOH, as needed, using m
robert@464 308 if(context->analogChannels == 8 ){ //hold the value for two frames
robert@464 309 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 310 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
robert@464 311 unsigned int analogFrame = (audioFrameBase + j) / 2;
robert@464 312 *p1 = analogRead(context, analogFrame, k);
robert@464 313 }
robert@464 314 }
robert@464 315 } else if(context->analogChannels == 4){ //write every frame
robert@464 316 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 317 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
robert@464 318 unsigned int analogFrame = audioFrameBase + j;
robert@464 319 *p1 = analogRead(context, analogFrame, k);
robert@464 320 }
robert@464 321 }
robert@464 322 } else if(context->analogChannels == 2){ //drop every other frame
robert@464 323 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 324 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
robert@464 325 unsigned int analogFrame = (audioFrameBase + j) * 2;
robert@464 326 *p1 = analogRead(context, analogFrame, k);
robert@464 327 }
robert@464 328 }
robert@464 329 }
robert@464 330
robert@464 331 //then digital
robert@464 332 // note: in multiple places below we assume that the number of digitals is same as number of audio
robert@464 333 // digital in at message-rate
robert@464 334 dcm.processInput(&context->digital[audioFrameBase], gLibpdBlockSize);
robert@464 335
robert@464 336 // digital in at signal-rate
robert@464 337 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 338 unsigned int digitalFrame = audioFrameBase + j;
robert@464 339 for (k = 0, p1 = p0 + gLibpdBlockSize * (context->audioChannels + 8);
robert@464 340 k < 16; ++k, p1 += gLibpdBlockSize) {
robert@464 341 if(dcm.isSignalRate(k) && dcm.isInput(k)){ // only process input channels that are handled at signal rate
robert@464 342 *p1 = digitalRead(context, digitalFrame, k);
robert@464 343 }
robert@464 344 }
robert@464 345 }
robert@464 346
robert@464 347 libpd_process_sys(); // process the block
robert@464 348
robert@464 349 //digital out
robert@464 350 // digital out at signal-rate
robert@464 351 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
robert@464 352 unsigned int digitalFrame = (audioFrameBase + j);
robert@464 353 for (k = 0, p1 = p0 + gLibpdBlockSize * (context->audioChannels + 8);
robert@464 354 k < context->digitalChannels; k++, p1 += gLibpdBlockSize) {
robert@464 355 if(dcm.isSignalRate(k) && dcm.isOutput(k)){ // only process output channels that are handled at signal rate
robert@464 356 digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5);
robert@464 357 }
robert@464 358 }
robert@464 359 }
robert@464 360
robert@464 361 // digital out at message-rate
robert@464 362 dcm.processOutput(&context->digital[audioFrameBase], gLibpdBlockSize);
robert@464 363
robert@464 364 //audio
robert@464 365 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 366 for (k = 0, p1 = p0; k < context->audioChannels; k++, p1 += gLibpdBlockSize) {
robert@464 367 audioWrite(context, audioFrameBase + j, k, *p1);
robert@464 368 }
robert@464 369 }
robert@464 370
robert@464 371 //analog
robert@464 372 if(context->analogChannels == 8){
robert@464 373 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j += 2, p0 += 2) { //write every two frames
robert@464 374 unsigned int analogFrame = (audioFrameBase + j) / 2;
robert@464 375 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
robert@464 376 analogWriteOnce(context, analogFrame, k, *p1);
robert@464 377 }
robert@464 378 }
robert@464 379 } else if(context->analogChannels == 4){ //write every frame
robert@464 380 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
robert@464 381 unsigned int analogFrame = (audioFrameBase + j);
robert@464 382 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
robert@464 383 analogWriteOnce(context, analogFrame, k, *p1);
robert@464 384 }
robert@464 385 }
robert@464 386 } else if(context->analogChannels == 2){ //write every frame twice
robert@464 387 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
robert@464 388 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
robert@464 389 int analogFrame = audioFrameBase * 2 + j * 2;
robert@464 390 analogWriteOnce(context, analogFrame, k, *p1);
robert@464 391 analogWriteOnce(context, analogFrame + 1, k, *p1);
robert@464 392 }
robert@464 393 }
robert@464 394 }
robert@464 395 }
robert@464 396 }
robert@464 397
robert@464 398 // cleanup() is called once at the end, after the audio has stopped.
robert@464 399 // Release any resources that were allocated in setup().
robert@464 400
robert@464 401 void cleanup(BelaContext *context, void *userData)
robert@464 402 {
robert@464 403 }