chris@160: /* chris@160: * render.cpp chris@160: * chris@160: * Template render.cpp file for on-board heavy compiling chris@160: * chris@160: * N.B. this is currently *not* compatible with foleyDesigner source files! chris@160: * chris@160: * Created on: November 5, 2015 chris@160: * chris@160: * Christian Heinrichs chris@160: * chris@160: */ chris@160: giuliomoro@329: #include giuliomoro@198: #include giuliomoro@482: #include chris@160: #include giuliomoro@492: #include chris@190: #include chris@190: #include chris@190: #include giuliomoro@480: #include giuliomoro@480: chris@160: /* chris@160: * HEAVY CONTEXT & BUFFERS chris@160: */ chris@160: giuliomoro@492: Hv_bela *gHeavyContext; chris@160: float *gHvInputBuffers = NULL, *gHvOutputBuffers = NULL; giuliomoro@480: unsigned int gHvInputChannels = 0, gHvOutputChannels = 0; chris@160: chris@160: float gInverseSampleRate; chris@160: chris@160: /* chris@160: * HEAVY FUNCTIONS chris@160: */ chris@160: giuliomoro@480: // TODO: rename this giuliomoro@480: #define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs) giuliomoro@480: chris@160: void printHook(double timestampSecs, const char *printLabel, const char *msgString, void *userData) { giuliomoro@480: rt_printf("Message from Heavy patch: [@ %.3f] %s: %s\n", timestampSecs, printLabel, msgString); chris@160: } chris@160: giuliomoro@480: giuliomoro@480: // digitals giuliomoro@480: static DigitalChannelManager dcm; giuliomoro@480: giuliomoro@480: void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){ giuliomoro@480: hv_sendFloatToReceiver(gHeavyContext, hv_stringToHash((char*)receiverName), (float)state); giuliomoro@480: // rt_printf("%s: %d\n", (char*)receiverName, state); giuliomoro@480: } giuliomoro@480: giuliomoro@480: // TODO: turn them into hv hashes and adjust sendDigitalMessage accordingly giuliomoro@480: char hvDigitalInHashes[16][21]={ giuliomoro@480: {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"}, giuliomoro@480: {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"}, giuliomoro@480: {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"}, giuliomoro@480: {"bela_digitalIn26"} giuliomoro@480: }; giuliomoro@480: chris@160: static void sendHook( giuliomoro@480: double timestamp, // in milliseconds giuliomoro@480: const char *receiverName, giuliomoro@480: const HvMessage *const m, giuliomoro@480: void *userData) { chris@160: giuliomoro@480: // Bela digital giuliomoro@480: giuliomoro@480: // Bela digital run-time messages chris@160: giuliomoro@480: // TODO: this first block is almost an exact copy of libpd's code, should we add this to the class? giuliomoro@480: // let's make this as optimized as possible for built-in digital Out parsing giuliomoro@480: // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26 giuliomoro@480: static int prefixLength = 15; // strlen("bela_digitalOut") giuliomoro@480: if(strncmp(receiverName, "bela_digitalOut", prefixLength)==0){ giuliomoro@480: if(receiverName[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2) giuliomoro@480: if(receiverName[prefixLength + 1] != 0){ giuliomoro@480: // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi giuliomoro@480: int receiver = ((receiverName[prefixLength] - 48) * 10); giuliomoro@480: receiver += (receiverName[prefixLength+1] - 48); giuliomoro@480: unsigned int channel = receiver - LIBPD_DIGITAL_OFFSET; // go back to the actual Bela digital channel number giuliomoro@480: bool value = hv_msg_getFloat(m, 0); giuliomoro@480: if(channel < 16){ //16 is the hardcoded value for the number of digital channels giuliomoro@480: dcm.setValue(channel, value); giuliomoro@480: } giuliomoro@480: } giuliomoro@480: } giuliomoro@480: } giuliomoro@480: giuliomoro@480: // Bela digital initialization messages giuliomoro@480: if(strcmp(receiverName, "bela_setDigital") == 0){ giuliomoro@480: // Third argument (optional) can be ~ or sig for signal-rate, message-rate otherwise. giuliomoro@480: // [in 14 ~( giuliomoro@480: // | giuliomoro@480: // [s bela_setDigital] giuliomoro@480: // is signal("sig" or "~") or message("message", default) rate giuliomoro@480: bool isMessageRate = true; // defaults to message rate giuliomoro@480: bool direction = 0; // initialize it just to avoid the compiler's warning giuliomoro@480: bool disable = false; giuliomoro@480: int numArgs = hv_msg_getNumElements(m); giuliomoro@480: if(numArgs < 2 || numArgs > 3 || !hv_msg_isSymbol(m, 0) || !hv_msg_isFloat(m, 1)) giuliomoro@480: return; giuliomoro@480: if(numArgs == 3 && !hv_msg_isSymbol(m,2)) giuliomoro@480: return; giuliomoro@480: char * symbol = hv_msg_getSymbol(m, 0); giuliomoro@480: giuliomoro@480: if(strcmp(symbol, "in") == 0){ giuliomoro@480: direction = INPUT; giuliomoro@480: } else if(strcmp(symbol, "out") == 0){ giuliomoro@480: direction = OUTPUT; giuliomoro@480: } else if(strcmp(symbol, "disable") == 0){ giuliomoro@480: disable = true; giuliomoro@480: } else { giuliomoro@480: return; giuliomoro@480: } giuliomoro@480: int channel = hv_msg_getFloat(m, 1) - LIBPD_DIGITAL_OFFSET; giuliomoro@480: if(disable == true){ giuliomoro@480: dcm.unmanage(channel); giuliomoro@480: return; giuliomoro@480: } giuliomoro@480: if(numArgs >= 3){ giuliomoro@480: char* s = hv_msg_getSymbol(m, 2); giuliomoro@480: if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){ giuliomoro@480: isMessageRate = false; giuliomoro@480: } giuliomoro@480: } giuliomoro@480: dcm.manage(channel, direction, isMessageRate); giuliomoro@480: } chris@160: } chris@160: giuliomoro@480: chris@160: /* chris@166: * SETUP, RENDER LOOP & CLEANUP chris@160: */ chris@160: giuliomoro@489: // leaving this here, trying to come up with a coherent interface with libpd. giuliomoro@489: // commenting them out so the compiler does not warn giuliomoro@482: // 2 audio + (up to)8 analog + (up to) 16 digital + 4 scope outputs giuliomoro@489: //static const unsigned int gChannelsInUse = 30; giuliomoro@489: //static unsigned int gAnalogChannelsInUse = 8; // hard-coded for the moment, TODO: get it at run-time from hv_context giuliomoro@482: //static const unsigned int gFirstAudioChannel = 0; giuliomoro@489: //static const unsigned int gFirstAnalogChannel = 2; giuliomoro@482: static const unsigned int gFirstDigitalChannel = 10; giuliomoro@482: static const unsigned int gFirstScopeChannel = 26; giuliomoro@482: static unsigned int gDigitalSigInChannelsInUse; giuliomoro@482: static unsigned int gDigitalSigOutChannelsInUse; giuliomoro@480: giuliomoro@482: // Bela Midi giuliomoro@198: Midi midi; giuliomoro@480: unsigned int hvMidiHashes[7]; giuliomoro@482: // Bela Scope giuliomoro@482: Scope scope; giuliomoro@482: unsigned int gScopeChannelsInUse; giuliomoro@482: float* gScopeOut; giuliomoro@480: giuliomoro@480: giuliomoro@329: bool setup(BelaContext *context, void *userData) { chris@160: /* HEAVY */ giuliomoro@480: hvMidiHashes[kmmNoteOn] = hv_stringToHash("__hv_notein"); giuliomoro@487: // hvMidiHashes[kmmNoteOff] = hv_stringToHash("noteoff"); // this is handled differently, see the render function giuliomoro@480: hvMidiHashes[kmmControlChange] = hv_stringToHash("__hv_ctlin"); giuliomoro@487: // Note that the ones below are not defined by Heavy, but they are here for (wishing) forward-compatibility giuliomoro@487: // You need to receive from the corresponding symbol in Pd and unpack the message, e.g.: giuliomoro@487: //[r __hv_pgmin] giuliomoro@487: //| giuliomoro@487: //[unpack f f] giuliomoro@487: //| | giuliomoro@487: //| [print pgmin_channel] giuliomoro@487: //[print pgmin_number] giuliomoro@487: hvMidiHashes[kmmProgramChange] = hv_stringToHash("__hv_pgmin"); giuliomoro@487: hvMidiHashes[kmmPolyphonicKeyPressure] = hv_stringToHash("__hv_polytouchin"); giuliomoro@496: hvMidiHashes[kmmChannelPressure] = hv_stringToHash("__hv_touchin"); giuliomoro@487: hvMidiHashes[kmmPitchBend] = hv_stringToHash("__hv_bendin"); chris@160: giuliomoro@492: gHeavyContext = hv_bela_new(context->audioSampleRate); chris@160: chris@160: gHvInputChannels = hv_getNumInputChannels(gHeavyContext); chris@160: gHvOutputChannels = hv_getNumOutputChannels(gHeavyContext); chris@160: giuliomoro@482: gScopeChannelsInUse = gHvOutputChannels > gFirstScopeChannel ? giuliomoro@482: gHvOutputChannels - gFirstScopeChannel : 0; giuliomoro@482: gDigitalSigInChannelsInUse = gHvInputChannels > gFirstDigitalChannel ? giuliomoro@482: gHvInputChannels - gFirstDigitalChannel : 0; giuliomoro@482: gDigitalSigOutChannelsInUse = gHvOutputChannels > gFirstDigitalChannel ? giuliomoro@482: gHvOutputChannels - gFirstDigitalChannel - gScopeChannelsInUse: 0; giuliomoro@482: giuliomoro@482: printf("Starting Heavy context with %d input channels and %d output channels\n", chris@160: gHvInputChannels, gHvOutputChannels); giuliomoro@482: printf("Channels in use:\n"); giuliomoro@482: printf("Digital in : %u, Digital out: %u\n", gDigitalSigInChannelsInUse, gDigitalSigOutChannelsInUse); giuliomoro@482: printf("Scope out: %u\n", gScopeChannelsInUse); chris@160: chris@160: if(gHvInputChannels != 0) { chris@160: gHvInputBuffers = (float *)calloc(gHvInputChannels * context->audioFrames,sizeof(float)); chris@160: } chris@160: if(gHvOutputChannels != 0) { chris@160: gHvOutputBuffers = (float *)calloc(gHvOutputChannels * context->audioFrames,sizeof(float)); chris@160: } chris@160: chris@160: gInverseSampleRate = 1.0 / context->audioSampleRate; chris@160: chris@160: // Set heavy print hook giuliomoro@480: hv_setPrintHook(gHeavyContext, printHook); chris@160: // Set heavy send hook chris@160: hv_setSendHook(gHeavyContext, sendHook); chris@160: giuliomoro@480: // TODO: change these hardcoded port values and actually change them in the Midi class giuliomoro@198: midi.readFrom(0); giuliomoro@198: midi.writeTo(0); giuliomoro@198: midi.enableParser(true); giuliomoro@480: giuliomoro@482: if(gScopeChannelsInUse > 0){ giuliomoro@482: // block below copy/pasted from libpd, except giuliomoro@482: scope.setup(gScopeChannelsInUse, context->audioSampleRate); giuliomoro@482: gScopeOut = new float[gScopeChannelsInUse]; giuliomoro@482: } giuliomoro@480: // Bela digital giuliomoro@480: dcm.setCallback(sendDigitalMessage); giuliomoro@480: if(context->digitalChannels > 0){ giuliomoro@480: for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){ giuliomoro@480: dcm.setCallbackArgument(ch, hvDigitalInHashes[ch]); giuliomoro@480: } giuliomoro@480: } giuliomoro@480: // unlike libpd, no need here to bind the bela_digitalOut.. receivers giuliomoro@480: chris@160: return true; chris@160: } chris@160: chris@160: giuliomoro@329: void render(BelaContext *context, void *userData) chris@160: { giuliomoro@480: { giuliomoro@480: int num; giuliomoro@480: while((num = midi.getParser()->numAvailableMessages()) > 0){ giuliomoro@480: static MidiChannelMessage message; giuliomoro@480: message = midi.getParser()->getNextChannelMessage(); giuliomoro@480: switch(message.getType()){ giuliomoro@480: case kmmNoteOn: { giuliomoro@487: //message.prettyPrint(); giuliomoro@487: int noteNumber = message.getDataByte(0); giuliomoro@487: int velocity = message.getDataByte(1); giuliomoro@487: int channel = message.getChannel(); giuliomoro@480: // rt_printf("message: noteNumber: %f, velocity: %f, channel: %f\n", noteNumber, velocity, channel); giuliomoro@487: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff", giuliomoro@496: (float)noteNumber, (float)velocity, (float)channel+1); giuliomoro@480: break; giuliomoro@480: } giuliomoro@487: case kmmNoteOff: { giuliomoro@480: /* PureData does not seem to handle noteoff messages as per the MIDI specs, giuliomoro@480: * so that the noteoff velocity is ignored. Here we convert them to noteon giuliomoro@480: * with a velocity of 0. giuliomoro@480: */ giuliomoro@487: int noteNumber = message.getDataByte(0); giuliomoro@480: // int velocity = message.getDataByte(1); // would be ignored by Pd giuliomoro@487: int channel = message.getChannel(); giuliomoro@480: // note we are sending the below to hvHashes[kmmNoteOn] !! giuliomoro@487: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff", giuliomoro@496: (float)noteNumber, (float)0, (float)channel+1); giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: case kmmControlChange: { giuliomoro@480: int channel = message.getChannel(); giuliomoro@480: int controller = message.getDataByte(0); giuliomoro@480: int value = message.getDataByte(1); giuliomoro@480: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmControlChange], 0, "fff", giuliomoro@496: (float)value, (float)controller, (float)channel+1); giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: case kmmProgramChange: { giuliomoro@496: message.prettyPrint(); giuliomoro@480: int channel = message.getChannel(); giuliomoro@480: int program = message.getDataByte(0); giuliomoro@480: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmProgramChange], 0, "ff", giuliomoro@496: (float)program, (float)channel+1); giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: case kmmPolyphonicKeyPressure: { giuliomoro@496: message.prettyPrint(); giuliomoro@496: giuliomoro@487: //TODO: untested, I do not have anything with polyTouch... who does, anyhow? giuliomoro@480: int channel = message.getChannel(); giuliomoro@480: int pitch = message.getDataByte(0); giuliomoro@480: int value = message.getDataByte(1); giuliomoro@480: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPolyphonicKeyPressure], 0, "fff", giuliomoro@496: (float)channel+1, (float)pitch, (float)value); giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: case kmmChannelPressure: giuliomoro@480: { giuliomoro@480: int channel = message.getChannel(); giuliomoro@480: int value = message.getDataByte(0); giuliomoro@480: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmChannelPressure], 0, "ff", giuliomoro@496: (float)value, (float)channel+1); giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: case kmmPitchBend: giuliomoro@480: { giuliomoro@480: int channel = message.getChannel(); giuliomoro@498: int value = ((message.getDataByte(1) << 7) | message.getDataByte(0)); giuliomoro@480: hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPitchBend], 0, "ff", giuliomoro@496: (float)channel+1, (float)value); giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: case kmmNone: giuliomoro@480: case kmmAny: giuliomoro@480: break; giuliomoro@480: } giuliomoro@480: } giuliomoro@480: } chris@160: chris@160: // De-interleave the data chris@160: if(gHvInputBuffers != NULL) { giuliomoro@480: for(unsigned int n = 0; n < context->audioFrames; n++) { giuliomoro@480: for(unsigned int ch = 0; ch < gHvInputChannels; ch++) { chris@160: if(ch >= context->audioChannels+context->analogChannels) { chris@160: // THESE ARE PARAMETER INPUT 'CHANNELS' USED FOR ROUTING chris@160: // 'sensor' outputs from routing channels of dac~ are passed through here chris@160: break; chris@160: } else { chris@160: // If more than 2 ADC inputs are used in the pd patch, route the analog inputs chris@160: // i.e. ADC3->analogIn0 etc. (first two are always audio inputs) chris@160: if(ch >= context->audioChannels) { chris@160: int m = n/2; chris@160: float mIn = context->analogIn[m*context->analogChannels + (ch-context->audioChannels)]; chris@160: gHvInputBuffers[ch * context->audioFrames + n] = mIn; chris@160: } else { chris@160: gHvInputBuffers[ch * context->audioFrames + n] = context->audioIn[n * context->audioChannels + ch]; chris@160: } chris@160: } chris@160: } chris@160: } chris@160: } chris@160: giuliomoro@480: // Bela digital in giuliomoro@480: // note: in multiple places below we assume that the number of digital frames is same as number of audio giuliomoro@482: // Bela digital in at message-rate giuliomoro@480: dcm.processInput(context->digital, context->digitalFrames); giuliomoro@480: giuliomoro@480: // Bela digital in at signal-rate giuliomoro@482: if(gDigitalSigInChannelsInUse > 0) giuliomoro@482: { giuliomoro@482: unsigned int j, k; giuliomoro@482: float *p0, *p1; giuliomoro@482: const unsigned int gLibpdBlockSize = context->audioFrames; giuliomoro@482: const unsigned int audioFrameBase = 0; giuliomoro@482: float* gInBuf = gHvInputBuffers; giuliomoro@482: // block below copy/pasted from libpd, except giuliomoro@482: // 16 has been replaced with gDigitalSigInChannelsInUse giuliomoro@482: for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) { giuliomoro@482: unsigned int digitalFrame = audioFrameBase + j; giuliomoro@482: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel; giuliomoro@482: k < gDigitalSigInChannelsInUse; ++k, p1 += gLibpdBlockSize) { giuliomoro@482: if(dcm.isSignalRate(k) && dcm.isInput(k)){ // only process input channels that are handled at signal rate giuliomoro@482: *p1 = digitalRead(context, digitalFrame, k); giuliomoro@482: } giuliomoro@482: } giuliomoro@482: } giuliomoro@482: } giuliomoro@482: giuliomoro@480: chris@160: // replacement for bang~ object giuliomoro@492: //hv_vscheduleMessageForReceiver(gHeavyContext, "bela_bang", 0.0f, "b"); chris@160: giuliomoro@492: hv_bela_process_inline(gHeavyContext, gHvInputBuffers, gHvOutputBuffers, context->audioFrames); chris@160: giuliomoro@480: // Bela digital out giuliomoro@482: // Bela digital out at signal-rate giuliomoro@482: if(gDigitalSigOutChannelsInUse > 0) giuliomoro@482: { giuliomoro@482: unsigned int j, k; giuliomoro@482: float *p0, *p1; giuliomoro@482: const unsigned int gLibpdBlockSize = context->audioFrames; giuliomoro@482: const unsigned int audioFrameBase = 0; giuliomoro@482: float* gOutBuf = gHvOutputBuffers; giuliomoro@482: // block below copy/pasted from libpd, except giuliomoro@482: // context->digitalChannels has been replaced with gDigitalSigOutChannelsInUse giuliomoro@482: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) { giuliomoro@482: unsigned int digitalFrame = (audioFrameBase + j); giuliomoro@482: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel; giuliomoro@482: k < gDigitalSigOutChannelsInUse; k++, p1 += gLibpdBlockSize) { giuliomoro@482: if(dcm.isSignalRate(k) && dcm.isOutput(k)){ // only process output channels that are handled at signal rate giuliomoro@482: digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5); giuliomoro@482: } giuliomoro@482: } giuliomoro@482: } giuliomoro@482: } giuliomoro@482: // Bela digital out at message-rate giuliomoro@482: dcm.processOutput(context->digital, context->digitalFrames); giuliomoro@480: giuliomoro@482: // Bela scope giuliomoro@482: if(gScopeChannelsInUse > 0) giuliomoro@482: { giuliomoro@482: unsigned int j, k; giuliomoro@482: float *p0, *p1; giuliomoro@482: const unsigned int gLibpdBlockSize = context->audioFrames; giuliomoro@482: float* gOutBuf = gHvOutputBuffers; giuliomoro@482: giuliomoro@482: // block below copy/pasted from libpd giuliomoro@482: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) { giuliomoro@482: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstScopeChannel; k < gScopeChannelsInUse; k++, p1 += gLibpdBlockSize) { giuliomoro@482: gScopeOut[k] = *p1; giuliomoro@482: } giuliomoro@486: scope.log(gScopeOut); giuliomoro@482: } giuliomoro@482: } giuliomoro@480: chris@160: // Interleave the output data chris@160: if(gHvOutputBuffers != NULL) { giuliomoro@480: for(unsigned int n = 0; n < context->audioFrames; n++) { chris@160: giuliomoro@480: for(unsigned int ch = 0; ch < gHvOutputChannels; ch++) { chris@160: if(ch >= context->audioChannels+context->analogChannels) { chris@160: // THESE ARE SENSOR OUTPUT 'CHANNELS' USED FOR ROUTING chris@160: // they are the content of the 'sensor output' dac~ channels chris@160: } else { chris@160: if(ch >= context->audioChannels) { chris@160: int m = n/2; chris@160: context->analogOut[m * context->analogFrames + (ch-context->audioChannels)] = constrain(gHvOutputBuffers[ch*context->audioFrames + n],0.0,1.0); chris@160: } else { chris@160: context->audioOut[n * context->audioChannels + ch] = gHvOutputBuffers[ch * context->audioFrames + n]; chris@160: } chris@160: } chris@160: } chris@160: } chris@160: } chris@160: chris@160: } chris@160: chris@160: giuliomoro@329: void cleanup(BelaContext *context, void *userData) chris@160: { chris@160: giuliomoro@492: hv_bela_free(gHeavyContext); chris@160: if(gHvInputBuffers != NULL) chris@160: free(gHvInputBuffers); chris@160: if(gHvOutputBuffers != NULL) chris@160: free(gHvOutputBuffers); giuliomoro@482: delete[] gScopeOut; chris@160: }