Mercurial > hg > beaglert
changeset 480:4ff80956c27a prerelease
heavy supports digitals at message rate
author | Giulio Moro <giuliomoro@yahoo.it> |
---|---|
date | Tue, 21 Jun 2016 05:44:21 +0100 |
parents | bfc60429cca6 |
children | def3b8cf9749 |
files | core/default_libpd_render.cpp examples/08-PureData/digital/_main.pd examples/08-PureData/digital/digital_example.pd scripts/hvresources/heavy_render.cpp |
diffstat | 4 files changed, 332 insertions(+), 138 deletions(-) [+] |
line wrap: on
line diff
--- a/core/default_libpd_render.cpp Tue Jun 21 05:03:53 2016 +0100 +++ b/core/default_libpd_render.cpp Tue Jun 21 05:44:21 2016 +0100 @@ -150,13 +150,18 @@ printf("Error file %s/%s not found. The %s file should be your main patch.\n", folder, file, file); return false; } + + // analog setup + gAnalogChannelsInUse = context->analogChannels; + + // digital setup dcm.setCallback(sendDigitalMessage); - gAnalogChannelsInUse = context->analogChannels; if(context->digitalChannels > 0){ for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){ dcm.setCallbackArgument(ch, receiverNames[ch]); } } + midi.readFrom(0); midi.writeTo(0); #ifdef PARSE_MIDI @@ -347,7 +352,7 @@ } } - //then digital + // Bela digital input // note: in multiple places below we assume that the number of digitals is same as number of audio // digital in at message-rate dcm.processInput(&context->digital[audioFrameBase], gLibpdBlockSize);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/08-PureData/digital/_main.pd Tue Jun 21 05:44:21 2016 +0100 @@ -0,0 +1,105 @@ +#N canvas 279 78 857 690 10; +#X obj 18 477 osc~ 200; +#X obj 57 522 *~; +#X obj 112 405 line~ 1; +#X msg 116 372 1 \, 0 200; +#X obj 205 472 line~ 1; +#X msg 209 439 1 \, 0 200; +#X obj 210 405 select 1; +#X obj 140 531 *~; +#X obj 484 552 s bela_setDigital; +#X obj 484 483 loadbang; +#X msg 65 445 0; +#X obj 116 332 r bela_digitalIn12; +#X obj 422 217 r bela_digitalIn14; +#X obj 422 243 s bela_digitalOut13; +#X obj 285 218 r bela_digitalIn12; +#X obj 285 243 s bela_digitalOut11; +#X obj 374 339 timer; +#X msg 404 304 bang; +#X msg 368 304 bang; +#X obj 556 160 metro 1000; +#X obj 575 304 s bela_digitalOut15; +#X obj 354 380 print sourceto12; +#X obj 488 351 timer; +#X msg 518 316 bang; +#X msg 482 316 bang; +#X obj 487 380 print sourceto14; +#X obj 650 146 r bela_digitalIn16; +#X msg 553 265 1; +#X obj 577 214 delay 500; +#X obj 480 275 select 1; +#X obj 346 268 select 1; +#X msg 584 281 0; +#X obj 637 221 select 1 0; +#X obj 80 663 dac~ 3 4; +#X msg 388 503 disable 12; +#X obj 158 595 *~ 0.5; +#X obj 157 622 +~ 0.5; +#X obj 53 584 *~ 0.5; +#X obj 52 611 +~ 0.5; +#X text 183 37 This is just a stub ...; +#X obj 308 643 dac~ 1 2; +#X obj 311 606 *~ 0.1; +#X obj 303 576 osc~ 120; +#X msg 585 438 0; +#X obj 649 185 print digitalIn16; +#X obj 672 373 print digitalIn14; +#X text 185 103 This section requires some hardware loopback between +15 and 14 \, 13 and 12 Have fun figuring out the pins on P8 (or P9 +???) If you do it properly \, you can measure roundtrip latency with +multiple digital/analog feed-forward networs; +#X msg 484 514 out 11 \, in 12 \, out 13 \, in 14 \, out 15 \, in 16 +\, in 17 ~; +#X obj 552 112 loadbang; +#X obj 206 291 print digitalIn12; +#X text 548 130 connect a switch to digitalIn16 or turn on the metro +here; +#X connect 0 0 1 0; +#X connect 0 0 7 0; +#X connect 1 0 37 0; +#X connect 2 0 1 1; +#X connect 3 0 2 0; +#X connect 4 0 7 1; +#X connect 5 0 4 0; +#X connect 6 0 5 0; +#X connect 6 0 10 0; +#X connect 7 0 35 0; +#X connect 9 0 47 0; +#X connect 10 0 0 1; +#X connect 11 0 3 0; +#X connect 12 0 13 0; +#X connect 12 0 29 0; +#X connect 12 0 45 0; +#X connect 14 0 15 0; +#X connect 14 0 30 0; +#X connect 14 0 49 0; +#X connect 16 0 21 0; +#X connect 17 0 16 1; +#X connect 18 0 16 0; +#X connect 19 0 27 0; +#X connect 19 0 28 0; +#X connect 22 0 25 0; +#X connect 23 0 22 1; +#X connect 24 0 22 0; +#X connect 26 0 32 0; +#X connect 26 0 43 0; +#X connect 26 0 44 0; +#X connect 27 0 20 0; +#X connect 27 0 24 0; +#X connect 27 0 18 0; +#X connect 28 0 31 0; +#X connect 29 0 23 0; +#X connect 30 0 17 0; +#X connect 31 0 20 0; +#X connect 32 0 27 0; +#X connect 32 1 31 0; +#X connect 34 0 8 0; +#X connect 35 0 36 0; +#X connect 36 0 33 1; +#X connect 37 0 38 0; +#X connect 38 0 33 0; +#X connect 41 0 40 0; +#X connect 42 0 41 0; +#X connect 43 0 42 1; +#X connect 47 0 8 0;
--- a/examples/08-PureData/digital/digital_example.pd Tue Jun 21 05:03:53 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -#N canvas 279 78 857 690 10; -#X obj 18 477 osc~ 200; -#X obj 57 522 *~; -#X obj 112 405 line~ 1; -#X msg 116 372 1 \, 0 200; -#X obj 205 472 line~ 1; -#X msg 209 439 1 \, 0 200; -#X obj 210 405 select 1; -#X obj 140 531 *~; -#X obj 484 601 s bela_setDigital; -#X obj 484 532 loadbang; -#X msg 65 445 0; -#X obj 116 332 r bela_digitalIn12; -#X obj 422 217 r bela_digitalIn14; -#X obj 422 243 s bela_digitalOut13; -#X obj 285 218 r bela_digitalIn12; -#X obj 285 243 s bela_digitalOut11; -#X obj 374 339 timer; -#X msg 404 304 bang; -#X msg 368 304 bang; -#X obj 555 184 metro 1000; -#X obj 575 304 s bela_digitalOut15; -#X obj 374 366 print sourceto12; -#X obj 488 351 timer; -#X msg 518 316 bang; -#X msg 482 316 bang; -#X obj 487 380 print sourceto14; -#X obj 663 15 r bela_digitalIn16; -#X msg 553 265 1; -#X obj 577 214 delay 500; -#X obj 480 275 select 1; -#X obj 346 268 select 1; -#X msg 584 281 0; -#X obj 634 251 select 1 0; -#X obj 80 622 dac~ 3 4; -#X obj 65 566 expr~ $v1*0.5 + 0.5; -#X obj 138 588 expr~ $v1*0.5 + 0.5; -#X obj 65 202 dac~ 11; -#X obj 114 83 osc~ 300; -#X obj 176 179 dac~ 1 3; -#X obj 34 136 adc~ 17; -#X obj 423 504 delay 3000; -#X msg 388 552 disable 12; -#X msg 484 552 out 11 ~ \, out 13 \, in 14 \, out 15 \, in 16 \, in -17 ~; -#X connect 0 0 1 0; -#X connect 0 0 7 0; -#X connect 1 0 34 0; -#X connect 2 0 1 1; -#X connect 3 0 2 0; -#X connect 4 0 7 1; -#X connect 5 0 4 0; -#X connect 6 0 5 0; -#X connect 6 0 10 0; -#X connect 7 0 35 0; -#X connect 9 0 42 0; -#X connect 9 0 40 0; -#X connect 10 0 0 1; -#X connect 11 0 3 0; -#X connect 12 0 13 0; -#X connect 12 0 29 0; -#X connect 14 0 15 0; -#X connect 14 0 30 0; -#X connect 16 0 21 0; -#X connect 17 0 16 1; -#X connect 18 0 16 0; -#X connect 19 0 27 0; -#X connect 19 0 28 0; -#X connect 22 0 25 0; -#X connect 23 0 22 1; -#X connect 24 0 22 0; -#X connect 26 0 32 0; -#X connect 27 0 20 0; -#X connect 27 0 24 0; -#X connect 27 0 18 0; -#X connect 28 0 31 0; -#X connect 29 0 23 0; -#X connect 30 0 17 0; -#X connect 31 0 20 0; -#X connect 32 0 27 0; -#X connect 32 1 31 0; -#X connect 34 0 33 0; -#X connect 35 0 33 1; -#X connect 37 0 38 0; -#X connect 37 0 38 1; -#X connect 39 0 36 0; -#X connect 40 0 41 0; -#X connect 41 0 8 0; -#X connect 42 0 8 0;
--- a/scripts/hvresources/heavy_render.cpp Tue Jun 21 05:03:53 2016 +0100 +++ b/scripts/hvresources/heavy_render.cpp Tue Jun 21 05:44:21 2016 +0100 @@ -18,13 +18,15 @@ #include <string.h> #include <stdlib.h> #include <string.h> +#include <DigitalChannelManager.h> + /* * HEAVY CONTEXT & BUFFERS */ Hv_bbb *gHeavyContext; float *gHvInputBuffers = NULL, *gHvOutputBuffers = NULL; -int gHvInputChannels = 0, gHvOutputChannels = 0; +unsigned int gHvInputChannels = 0, gHvOutputChannels = 0; float gInverseSampleRate; @@ -32,33 +34,128 @@ * HEAVY FUNCTIONS */ +// TODO: rename this +#define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs) + void printHook(double timestampSecs, const char *printLabel, const char *msgString, void *userData) { - printf("Message from Heavy patch: [@ %.3f] %s: %s\n", timestampSecs, printLabel, msgString); + rt_printf("Message from Heavy patch: [@ %.3f] %s: %s\n", timestampSecs, printLabel, msgString); } + +// digitals +static DigitalChannelManager dcm; + +void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){ + hv_sendFloatToReceiver(gHeavyContext, hv_stringToHash((char*)receiverName), (float)state); +// rt_printf("%s: %d\n", (char*)receiverName, state); +} + +// TODO: turn them into hv hashes and adjust sendDigitalMessage accordingly +char hvDigitalInHashes[16][21]={ + {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"}, + {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"}, + {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"}, + {"bela_digitalIn26"} +}; + static void sendHook( - double timestamp, // in milliseconds - const char *receiverName, - const HvMessage *const m, - void *userData) { + double timestamp, // in milliseconds + const char *receiverName, + const HvMessage *const m, + void *userData) { - // only react to messages sent to receivers named "hello" - if (!strncmp(receiverName, "hello", 5)) { - } + // Bela digital + + // Bela digital run-time messages + // TODO: this first block is almost an exact copy of libpd's code, should we add this to the class? + // let's make this as optimized as possible for built-in digital Out parsing + // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26 + static int prefixLength = 15; // strlen("bela_digitalOut") + if(strncmp(receiverName, "bela_digitalOut", prefixLength)==0){ + if(receiverName[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2) + if(receiverName[prefixLength + 1] != 0){ + // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi + int receiver = ((receiverName[prefixLength] - 48) * 10); + receiver += (receiverName[prefixLength+1] - 48); + unsigned int channel = receiver - LIBPD_DIGITAL_OFFSET; // go back to the actual Bela digital channel number + bool value = hv_msg_getFloat(m, 0); + if(channel < 16){ //16 is the hardcoded value for the number of digital channels + dcm.setValue(channel, value); + } + } + } + } + + // Bela digital initialization messages + if(strcmp(receiverName, "bela_setDigital") == 0){ + // Third argument (optional) can be ~ or sig for signal-rate, message-rate otherwise. + // [in 14 ~( + // | + // [s bela_setDigital] + // is signal("sig" or "~") or message("message", default) rate + bool isMessageRate = true; // defaults to message rate + bool direction = 0; // initialize it just to avoid the compiler's warning + bool disable = false; + int numArgs = hv_msg_getNumElements(m); + if(numArgs < 2 || numArgs > 3 || !hv_msg_isSymbol(m, 0) || !hv_msg_isFloat(m, 1)) + return; + if(numArgs == 3 && !hv_msg_isSymbol(m,2)) + return; + char * symbol = hv_msg_getSymbol(m, 0); + + if(strcmp(symbol, "in") == 0){ + direction = INPUT; + } else if(strcmp(symbol, "out") == 0){ + direction = OUTPUT; + } else if(strcmp(symbol, "disable") == 0){ + disable = true; + } else { + return; + } + int channel = hv_msg_getFloat(m, 1) - LIBPD_DIGITAL_OFFSET; + if(disable == true){ + dcm.unmanage(channel); + return; + } + if(numArgs >= 3){ + char* s = hv_msg_getSymbol(m, 2); + if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){ + isMessageRate = false; + } + } + dcm.manage(channel, direction, isMessageRate); + } } + /* * SETUP, RENDER LOOP & CLEANUP */ + + +// Midi Midi midi; +unsigned int hvMidiHashes[7]; + + bool setup(BelaContext *context, void *userData) { + printf("top o setup\n"); /* HEAVY */ + hvMidiHashes[kmmNoteOn] = hv_stringToHash("__hv_notein"); + hvMidiHashes[kmmNoteOff] = hv_stringToHash("noteoff"); // this is handled differently, see the render function + hvMidiHashes[kmmControlChange] = hv_stringToHash("__hv_ctlin"); + hvMidiHashes[kmmProgramChange] = hv_stringToHash("pgmin"); + hvMidiHashes[kmmPolyphonicKeyPressure] = hv_stringToHash("polytouchin"); + hvMidiHashes[kmmChannelPressure] = hv_stringToHash("touchin"); + hvMidiHashes[kmmPitchBend] = hv_stringToHash("bendin"); + printf("after midi o setup\n"); gHeavyContext = hv_bbb_new(context->audioSampleRate); + printf("aftet new o setup\n"); gHvInputChannels = hv_getNumInputChannels(gHeavyContext); gHvOutputChannels = hv_getNumOutputChannels(gHeavyContext); @@ -71,28 +168,120 @@ if(gHvOutputChannels != 0) { gHvOutputBuffers = (float *)calloc(gHvOutputChannels * context->audioFrames,sizeof(float)); } + printf("mid o setup\n"); gInverseSampleRate = 1.0 / context->audioSampleRate; // Set heavy print hook - hv_setPrintHook(gHeavyContext, &printHook); + hv_setPrintHook(gHeavyContext, printHook); // Set heavy send hook hv_setSendHook(gHeavyContext, sendHook); + // TODO: change these hardcoded port values and actually change them in the Midi class midi.readFrom(0); midi.writeTo(0); midi.enableParser(true); + + // Bela digital + dcm.setCallback(sendDigitalMessage); + if(context->digitalChannels > 0){ + for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){ + dcm.setCallbackArgument(ch, hvDigitalInHashes[ch]); + } + } + // unlike libpd, no need here to bind the bela_digitalOut.. receivers + + printf("end o setup\n"); return true; } void render(BelaContext *context, void *userData) { + { + int num; + while((num = midi.getParser()->numAvailableMessages()) > 0){ + static MidiChannelMessage message; + message = midi.getParser()->getNextChannelMessage(); + switch(message.getType()){ + case kmmNoteOn: { + // message.prettyPrint(); + float noteNumber = message.getDataByte(0); + float velocity = message.getDataByte(1); + float channel = message.getChannel(); + // rt_printf("message: noteNumber: %f, velocity: %f, channel: %f\n", noteNumber, velocity, channel); + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff", noteNumber, velocity, channel); + break; + } + case kmmNoteOff: + { + /* PureData does not seem to handle noteoff messages as per the MIDI specs, + * so that the noteoff velocity is ignored. Here we convert them to noteon + * with a velocity of 0. + */ + float noteNumber = message.getDataByte(0); + // int velocity = message.getDataByte(1); // would be ignored by Pd + float channel = message.getChannel(); + // note we are sending the below to hvHashes[kmmNoteOn] !! + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff", noteNumber, 0, channel); + break; + } + case kmmControlChange: { + int channel = message.getChannel(); + int controller = message.getDataByte(0); + int value = message.getDataByte(1); + //TODO: maybe the order of the arguments is wrong here? + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmControlChange], 0, "fff", + value, controller, channel); + break; + } + case kmmProgramChange: { + int channel = message.getChannel(); + int program = message.getDataByte(0); + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmProgramChange], 0, "ff", + program, channel); + //TODO: maybe the order of the arguments is wrong here? + break; + } + case kmmPolyphonicKeyPressure: { + int channel = message.getChannel(); + int pitch = message.getDataByte(0); + int value = message.getDataByte(1); + //TODO: maybe the order of the arguments is wrong here? + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPolyphonicKeyPressure], 0, "fff", + channel, pitch, value); + break; + } + case kmmChannelPressure: + { + int channel = message.getChannel(); + int value = message.getDataByte(0); + //TODO: maybe the order of the arguments is wrong here? + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmChannelPressure], 0, "ff", + channel, value); + break; + } + case kmmPitchBend: + { + int channel = message.getChannel(); + int value = ((message.getDataByte(1) << 7) | message.getDataByte(0)) + 8192; + //TODO: is the value range correct? + //TODO: maybe the order of the arguments is wrong here? + hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPitchBend], 0, "ff", + channel, value); + break; + } + case kmmNone: + case kmmAny: + break; + } + } + } // De-interleave the data if(gHvInputBuffers != NULL) { - for(int n = 0; n < context->audioFrames; n++) { - for(int ch = 0; ch < gHvInputChannels; ch++) { + for(unsigned int n = 0; n < context->audioFrames; n++) { + for(unsigned int ch = 0; ch < gHvInputChannels; ch++) { if(ch >= context->audioChannels+context->analogChannels) { // THESE ARE PARAMETER INPUT 'CHANNELS' USED FOR ROUTING // 'sensor' outputs from routing channels of dac~ are passed through here @@ -112,49 +301,33 @@ } } + // Bela digital in + // note: in multiple places below we assume that the number of digital frames is same as number of audio + // digital in at message-rate + dcm.processInput(context->digital, context->digitalFrames); + + // Bela digital in at signal-rate + // TODO: not really straightforward to implement as Heavy determines the number of channels in use at compile time + // on the basis of the number of adc~ / dac~ of the patch ... Maybe we should always include + // a dummy [adc~ 27] [dac~ 27] to make sure all channels are always allocated and then leave them all unprocessed ? + // replacement for bang~ object //hv_vscheduleMessageForReceiver(gHeavyContext, "bbb_bang", 0.0f, "b"); - { - int num; - unsigned int hvHashes[3]; - hvHashes[0] = hv_stringToHash("bela_notein"); - hvHashes[1] = hv_stringToHash("bela_ctlin"); - hvHashes[2] = hv_stringToHash("bela_pgmin"); - while((num = midi.getParser()->numAvailableMessages()) > 0){ - static MidiChannelMessage message; - message = midi.getParser()->getNextChannelMessage(); - switch(message.getType()){ - case kmmNoteOn: { -// message.prettyPrint(); - float noteNumber = message.getDataByte(0); - float velocity = message.getDataByte(1); - float channel = message.getChannel(); -// rt_printf("message: noteNumber: %f, velocity: %f, channel: %f\n", noteNumber, velocity, channel); - hv_vscheduleMessageForReceiver(gHeavyContext, hvHashes[0], 0, "fff", noteNumber, velocity, channel); - } - break; - case kmmControlChange: { - hv_vscheduleMessageForReceiver(gHeavyContext, hvHashes[1], 0, "fff", - (float)message.getDataByte(1), (float)message.getDataByte(0), (float)message.getChannel()); - } - break; - case kmmProgramChange: - hv_vscheduleMessageForReceiver(gHeavyContext, hvHashes[2], 0, "ff", - (float)message.getDataByte(0), (float)message.getChannel()); - break; - } - } - } -// hv_sendFloatToReceiver(gHeavyContext, "notein", 1.123f); - hv_bbb_process_inline(gHeavyContext, gHvInputBuffers, gHvOutputBuffers, context->audioFrames); + // Bela digital out + // digital out at signal-rate + // TODO: see note above. + + // digital out at message-rate + dcm.processOutput(context->digital, context->digitalFrames); + // Interleave the output data if(gHvOutputBuffers != NULL) { - for(int n = 0; n < context->audioFrames; n++) { + for(unsigned int n = 0; n < context->audioFrames; n++) { - for(int ch = 0; ch < gHvOutputChannels; ch++) { + for(unsigned int ch = 0; ch < gHvOutputChannels; ch++) { if(ch >= context->audioChannels+context->analogChannels) { // THESE ARE SENSOR OUTPUT 'CHANNELS' USED FOR ROUTING // they are the content of the 'sensor output' dac~ channels