I am making a sampler which involves having large arrays for storing samples in.
Heavy has an upload limit of 1Mb and my PD patches are too big with the arrays in them.
If I leave out the arrays then the patches are small enough but of course there are errors because the arrays are missing.
Can anyone suggest a workaround to let me compile the patch with arrays or create the arrays afterwards or something?
Perhaps I should ask at the PD forums?
Large Arrays & Enzien Heavy
I have not tested this, but you can access the content of Pd [table]
objects through the Heavy C API and you can even resize them at runtime.
So you could create a [table]
in your patch and then resize it and populate it from a custom Heavy/render.cpp
file (see example 08-PureData/customRender/
for an example of how to use a custom render file).
Note for future readers: as of now, the available documentation on the Enzien website is up-to-date with the latest API, but we are still using an earlier revision, so some references may be out of date. You will find embedded docs for the Heavy version in use in your project in the Heavy_bela.h
file.
has anybody done this as of now? i also want to create a sampler with 25 samples, some several seconds long, which will make the patch much bigger then 1mb. i am struggling a bit with the mentioned method. if somebody could give me some hints/steps that would be great! basically i just need a way to load the 25 samples into previously created tables and resize them accordingly. bonus points if i can get the table size afterwards in pd (but not absolutely necessary...
how is your C?
Here's the docs for the heavy C API, https://enzienaudio.com/docs/index.html#12.c , at the bottom you find the functions needed to interact with tables:
/*
* Resizes the table to the given length.
*/
bool hv_table_setLength(HeavyContextInterface *c, hv_uint32_t tableHash, hv_uint32_t newSampleLength);
/** Returns a pointer to the raw buffer backing this table. DO NOT free it. */
float *hv_table_getBuffer(HeavyContextInterface *c, unsigned int tableHash);
/** Returns the length of this table in samples. */
unsigned int hv_table_getLength(HeavyContextInterface *c, unsigned int tableHash);
; combining this with a simple API for loading samples into a buffer e.g.: https://github.com/BelaPlatform/Bela/blob/master/examples/04-Audio/sample-piezo-trigger/render.cpp) should be easy if you know some C.
giuliomoro how is your C?
well, i can understand code, when i see it. i can hack existing code to do what i want (most of the time)
i'm not so good at writing code from scratch on a platform i don't know very well. anyways, thanks for the pointers (sic!) will try to get something going and report back.
- Edited
You will also need a "custom render" for your Heavy project , see here: https://github.com/BelaPlatform/Bela/wiki/Puredata-and-C--#providing-custom-c-code
In practice, for prototyping you can:
- add some small tables into your Pd patch, add some code to play them back.
- build once with Heavy
- then go to the IDE and edit the render.cpp
file. Also add the SampleLoader.h
and SampleData.h
files from the example I linked above.
****
BE CAREFUL NOT TO REBUILT WITH HEAVY OR THE CHANGES WILL BE LOST ****
.
- once you get the render.cpp
file to work, download it and copy it in a subfolder heavy/
within the folder containing your Pd patch. Next time you rebuild with Heavy, this file will be used instead of the default one.
As for the changes to do to the default render.cpp
, something along the following untested lines could work for one table:
#include <SampleLoader.h>
#include <SampleData.h>
....
setup() {
...
// the following 4 lines are already there
gHeavyContext = hv_bela_new_with_options(context->audioSampleRate, 10, 2, 0);
gHvInputChannels = hv_getNumInputChannels(gHeavyContext);
gHvOutputChannels = hv_getNumOutputChannels(gHeavyContext);
// add the lines below
char fileName[] = "myFile.wav";
int sampleLen = getNumFrames(fileName);
hv_uint32_t tableHash = hv_stringToHash("myTable");
hv_table_setLength(gHeavyContext, tableHash, sampleLen); // resize the table
float * table = hv_table_getBuffer(gHeavyContext, tableHash); // once resized, get a pointer to the array
int channel = 0; // take the first channel of the file
int startFrame = 0; // start from the beginning
int lastFrame = sampleLen; // until the end of the file
getSamples(fileName, table, channel, startFrame, lastFrame);
//optional bonus: send a message to a `[receive myTableLengthReceiver]` in Heavy with the new table length
hv_sendFloatToReceiver(context, hv_stringToHash("myTableLengthReceiver"), sampleLen);
...
}
- Edited
thanks so much for this!
i have the pd patch running ( a simple version with no sample switching etc.) ctlin 1 (mod wheel) controls the sample speed. it all works with an attached controller, but the sample is not loaded, instead it uses the "default sound" (the content i saved in the table in pd) EDIT: ahem, wait a minute, let me check something, i think i forgot to change the length for my sampler logic when loading the new table...hang on.
i have this render.cpp file:
/*
____ _____ _ _
| __ )| ____| | / \
| _ \| _| | | / _ \
| |_) | |___| |___ / ___ \
|____/|_____|_____/_/ \_\
The platform for ultra-low latency audio and sensor processing
http://bela.io
A project of the Augmented Instruments Laboratory within the
Centre for Digital Music at Queen Mary University of London.
http://www.eecs.qmul.ac.uk/~andrewm
(c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
The Bela software is distributed under the GNU Lesser General Public License
(LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
*/
#include <Bela.h>
#include <Midi.h>
#include <Scope.h>
#include <cmath>
#include <Heavy_bela.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <DigitalChannelManager.h>
#include <SampleLoader.h>
#include <SampleData.h>
// Bela Midi
static Midi midi;
unsigned int hvMidiHashes[7];
unsigned int gScopeChannelsInUse;
float* gScopeOut;
// Bela Scope
static Scope* scope = NULL;
static char multiplexerArray[] = {"bela_multiplexer"};
static int multiplexerArraySize = 0;
static bool pdMultiplexerActive = false;
/*
* HEAVY CONTEXT & BUFFERS
*/
HeavyContextInterface *gHeavyContext;
float *gHvInputBuffers = NULL, *gHvOutputBuffers = NULL;
unsigned int gHvInputChannels = 0, gHvOutputChannels = 0;
uint32_t multiplexerTableHash;
float gInverseSampleRate;
/*
* 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(HeavyContextInterface *context, const char *printLabel, const char *msgString, const HvMessage *msg) {
const double timestampSecs = ((double) hv_msg_getTimestamp(msg)) / hv_getSampleRate(context);
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"}
};
// For a message to be received here, you need to use the following syntax in Pd:
// [send receiverName @hv_param]
static void sendHook(
HeavyContextInterface *context,
const char *receiverName,
hv_uint32_t sendHash,
const HvMessage *m) {
// 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 const 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] - '0') * 10);
receiver += (receiverName[prefixLength+1] - '0');
unsigned int channel = receiver - LIBPD_DIGITAL_OFFSET; // go back to the actual Bela digital channel number
bool value = (hv_msg_getFloat(m, 0) != 0.0f);
if(channel < 16){ //16 is the hardcoded value for the number of digital channels
dcm.setValue(channel, value);
}
}
}
return;
}
// Bela digital initialization messages
switch (sendHash) {
case 0x70418732: { // bela_setDigital
// 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;
if (!(hv_msg_isSymbol(m, 0) && hv_msg_isFloat(m, 1))) return;
const 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(hv_msg_isSymbol(m, 2)){
const char *s = hv_msg_getSymbol(m, 2);
if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){
isMessageRate = false;
}
}
dcm.manage(channel, direction, isMessageRate);
break;
}
case 0xEC6DA2AF: { // bela_noteout
if (!hv_msg_hasFormat(m, "fff")) return;
midi_byte_t pitch = (midi_byte_t) hv_msg_getFloat(m, 0);
midi_byte_t velocity = (midi_byte_t) hv_msg_getFloat(m, 1);
midi_byte_t channel = (midi_byte_t) hv_msg_getFloat(m, 2);
rt_printf("noteon: %d %d %d\n", channel, pitch, velocity);
midi.writeNoteOn(channel, pitch, velocity);
break;
}
case 0xD44F9083: { // bela_ctlout
if (!hv_msg_hasFormat(m, "fff")) return;
midi_byte_t value = (midi_byte_t) hv_msg_getFloat(m, 0);
midi_byte_t controller = (midi_byte_t) hv_msg_getFloat(m, 1);
midi_byte_t channel = (midi_byte_t) hv_msg_getFloat(m, 2);
rt_printf("controlchange: %d %d %d\n", channel, controller, value);
midi.writeControlChange(channel, controller, value);
break;
}
case 0x6A647C44: { // bela_pgmout
if (!hv_msg_hasFormat(m, "ff")) return;
midi_byte_t program = (midi_byte_t) hv_msg_getFloat(m, 0);
midi_byte_t channel = (midi_byte_t) hv_msg_getFloat(m, 1);
rt_printf("programchange: %d %d\n", channel, program);
midi.writeProgramChange(channel, program);
break;
}
case 0x545CDF50: { // bela_bendout
if (!hv_msg_hasFormat(m, "ff")) return;
unsigned int value = ((midi_byte_t) hv_msg_getFloat(m, 0)) + 8192;
midi_byte_t channel = (midi_byte_t) hv_msg_getFloat(m, 1);
rt_printf("pitchbend: %d %d\n", channel, value);
midi.writePitchBend(channel, value);
break;
}
case 0xDE18F543: { // bela_touchout
if (!hv_msg_hasFormat(m, "ff")) return;
midi_byte_t pressure = (midi_byte_t) hv_msg_getFloat(m, 0);
midi_byte_t channel = (midi_byte_t) hv_msg_getFloat(m, 1);
rt_printf("channelPressure: %d %d\n", channel, pressure);
midi.writeChannelPressure(channel, pressure);
break;
}
case 0xAE8E3B2D: { // bela_polytouchout
if (!hv_msg_hasFormat(m, "fff")) return;
midi_byte_t pitch = (midi_byte_t) hv_msg_getFloat(m, 0);
midi_byte_t pressure = (midi_byte_t) hv_msg_getFloat(m, 1);
midi_byte_t channel = (midi_byte_t) hv_msg_getFloat(m, 2);
rt_printf("polytouch: %d %d %d\n", channel, pitch, pressure);
midi.writePolyphonicKeyPressure(channel, pitch, pressure);
break;
}
case 0x51CD8FE2: { // bela_midiout
if (!hv_msg_hasFormat(m, "ff")) return;
midi_byte_t byte = (midi_byte_t) hv_msg_getFloat(m, 0);
int port = (int) hv_msg_getFloat(m, 1);
rt_printf("port: %d, byte: %d\n", port, byte);
midi.writeOutput(byte);
break;
}
default: break;
}
}
/*
* SETUP, RENDER LOOP & CLEANUP
*/
// leaving this here, trying to come up with a coherent interface with libpd.
// commenting them out so the compiler does not warn
// 2 audio + (up to)8 analog + (up to) 16 digital + 4 scope outputs
//static const unsigned int gChannelsInUse = 30;
//static unsigned int gAnalogChannelsInUse = 8; // hard-coded for the moment, TODO: get it at run-time from hv_context
//static const unsigned int gFirstAudioChannel = 0;
//static const unsigned int gFirstAnalogChannel = 2;
static const unsigned int gFirstDigitalChannel = 10;
static const unsigned int gFirstScopeChannel = 26;
static unsigned int gDigitalSigInChannelsInUse;
static unsigned int gDigitalSigOutChannelsInUse;
bool setup(BelaContext *context, void *userData) {
if(context->audioInChannels != context->audioOutChannels ||
context->analogInChannels != context->analogOutChannels){
// It should actually work, but let's test it before releasing it!
fprintf(stderr, "Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
return false;
}
/* 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");
// Note that the ones below are not defined by Heavy, but they are here for (wishing) forward-compatibility
// You need to receive from the corresponding symbol in Pd and unpack the message, e.g.:
//[r __hv_pgmin]
//|
//[unpack f f]
//| |
//| [print pgmin_channel]
//[print pgmin_number]
hvMidiHashes[kmmProgramChange] = hv_stringToHash("__hv_pgmin");
hvMidiHashes[kmmPolyphonicKeyPressure] = hv_stringToHash("__hv_polytouchin");
hvMidiHashes[kmmChannelPressure] = hv_stringToHash("__hv_touchin");
hvMidiHashes[kmmPitchBend] = hv_stringToHash("__hv_bendin");
gHeavyContext = hv_bela_new_with_options(context->audioSampleRate, 10, 2, 0);
gHvInputChannels = hv_getNumInputChannels(gHeavyContext);
gHvOutputChannels = hv_getNumOutputChannels(gHeavyContext);
// add the lines below
char fileName[] = "sample0.wav";
int sampleLen = getNumFrames(fileName);
hv_uint32_t tableHash = hv_stringToHash("sample-table0");
hv_table_setLength(gHeavyContext, tableHash, sampleLen); // resize the table
float * table = hv_table_getBuffer(gHeavyContext, tableHash); // once resized, get a pointer to the array
int channel = 0; // take the first channel of the file
int startFrame = 0; // start from the beginning
int lastFrame = sampleLen; // until the end of the file
getSamples(fileName, table, channel, startFrame, lastFrame);
gScopeChannelsInUse = gHvOutputChannels > gFirstScopeChannel ?
gHvOutputChannels - gFirstScopeChannel : 0;
gDigitalSigInChannelsInUse = gHvInputChannels > gFirstDigitalChannel ?
gHvInputChannels - gFirstDigitalChannel : 0;
gDigitalSigOutChannelsInUse = gHvOutputChannels > gFirstDigitalChannel ?
gHvOutputChannels - gFirstDigitalChannel - gScopeChannelsInUse: 0;
printf("Starting Heavy context with %d input channels and %d output channels\n",
gHvInputChannels, gHvOutputChannels);
printf("Channels in use:\n");
printf("Digital in : %u, Digital out: %u\n", gDigitalSigInChannelsInUse, gDigitalSigOutChannelsInUse);
printf("Scope out: %u\n", gScopeChannelsInUse);
if(gHvInputChannels != 0) {
gHvInputBuffers = (float *)calloc(gHvInputChannels * context->audioFrames,sizeof(float));
}
if(gHvOutputChannels != 0) {
gHvOutputBuffers = (float *)calloc(gHvOutputChannels * context->audioFrames,sizeof(float));
}
gInverseSampleRate = 1.0 / context->audioSampleRate;
// Set heavy print hook
hv_setPrintHook(gHeavyContext, printHook);
// Set heavy send hook
hv_setSendHook(gHeavyContext, sendHook);
midi.readFrom("hw:1,0,0");
midi.writeTo("hw:1,0,0");
midi.enableParser(true);
if(gScopeChannelsInUse > 0){
#if __clang_major__ == 3 && __clang_minor__ == 8
fprintf(stderr, "Scope currently not supported when compiling heavy with clang3.8, see #265 https://github.com/BelaPlatform/Bela/issues/265. You should specify `COMPILER gcc;` in your Makefile options\n");
exit(1);
#endif
scope = new Scope();
scope->setup(gScopeChannelsInUse, context->audioSampleRate);
gScopeOut = new float[gScopeChannelsInUse];
}
// 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
// but make sure you do something like [send receiverName @hv_param]
// when you want to send a message from Heavy to the wrapper.
multiplexerTableHash = hv_stringToHash(multiplexerArray);
if(context->multiplexerChannels > 0){
pdMultiplexerActive = true;
multiplexerArraySize = context->multiplexerChannels * context->analogInChannels;
hv_table_setLength(gHeavyContext, multiplexerTableHash, multiplexerArraySize);
hv_sendFloatToReceiver(gHeavyContext, hv_stringToHash("bela_multiplexerChannels"), context->multiplexerChannels);
}
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();
int noteNumber = message.getDataByte(0);
int velocity = message.getDataByte(1);
int channel = message.getChannel();
// rt_printf("message: noteNumber: %f, velocity: %f, channel: %f\n", noteNumber, velocity, channel);
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff",
(float)noteNumber, (float)velocity, (float)channel+1);
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.
*/
int noteNumber = message.getDataByte(0);
// int velocity = message.getDataByte(1); // would be ignored by Pd
int channel = message.getChannel();
// note we are sending the below to hvHashes[kmmNoteOn] !!
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff",
(float)noteNumber, (float)0, (float)channel+1);
break;
}
case kmmControlChange: {
int channel = message.getChannel();
int controller = message.getDataByte(0);
int value = message.getDataByte(1);
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmControlChange], 0, "fff",
(float)value, (float)controller, (float)channel+1);
break;
}
case kmmProgramChange: {
int channel = message.getChannel();
int program = message.getDataByte(0);
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmProgramChange], 0, "ff",
(float)program, (float)channel+1);
break;
}
case kmmPolyphonicKeyPressure: {
//TODO: untested, I do not have anything with polyTouch... who does, anyhow?
int channel = message.getChannel();
int pitch = message.getDataByte(0);
int value = message.getDataByte(1);
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmPolyphonicKeyPressure], 0, "fff",
(float)channel+1, (float)pitch, (float)value);
break;
}
case kmmChannelPressure:
{
int channel = message.getChannel();
int value = message.getDataByte(0);
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmChannelPressure], 0, "ff",
(float)value, (float)channel+1);
break;
}
case kmmPitchBend:
{
int channel = message.getChannel();
int value = ((message.getDataByte(1) << 7) | message.getDataByte(0));
hv_sendMessageToReceiverV(gHeavyContext, hvMidiHashes[kmmPitchBend], 0, "ff",
(float)value, (float)channel+1);
break;
}
case kmmNone:
case kmmAny:
break;
}
}
}
// De-interleave the data
if(gHvInputBuffers != NULL) {
for(unsigned int n = 0; n < context->audioFrames; n++) {
for(unsigned int ch = 0; ch < gHvInputChannels; ch++) {
if(ch >= context->audioInChannels+context->analogInChannels) {
// THESE ARE PARAMETER INPUT 'CHANNELS' USED FOR ROUTING
// 'sensor' outputs from routing channels of dac~ are passed through here
break;
} else {
// If more than 2 ADC inputs are used in the pd patch, route the analog inputs
// i.e. ADC3->analogIn0 etc. (first two are always audio inputs)
if(ch >= context->audioInChannels) {
int m = n/2;
float mIn = context->analogIn[m*context->analogInChannels + (ch-context->audioInChannels)];
gHvInputBuffers[ch * context->audioFrames + n] = mIn;
} else {
gHvInputBuffers[ch * context->audioFrames + n] = context->audioIn[n * context->audioInChannels + ch];
}
}
}
}
}
if(pdMultiplexerActive){
static int lastMuxerUpdate = 0;
if(++lastMuxerUpdate == multiplexerArraySize){
lastMuxerUpdate = 0;
memcpy(hv_table_getBuffer(gHeavyContext, multiplexerTableHash), (float *const)context->multiplexerAnalogIn, multiplexerArraySize * sizeof(float));
}
}
// Bela digital in
// note: in multiple places below we assume that the number of digital frames is same as number of audio
// Bela digital in at message-rate
dcm.processInput(context->digital, context->digitalFrames);
// Bela digital in at signal-rate
if(gDigitalSigInChannelsInUse > 0)
{
unsigned int j, k;
float *p0, *p1;
const unsigned int gLibpdBlockSize = context->audioFrames;
const unsigned int audioFrameBase = 0;
float* gInBuf = gHvInputBuffers;
// block below copy/pasted from libpd, except
// 16 has been replaced with gDigitalSigInChannelsInUse
for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
unsigned int digitalFrame = audioFrameBase + j;
for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel;
k < gDigitalSigInChannelsInUse; ++k, p1 += gLibpdBlockSize) {
if(dcm.isSignalRate(k) && dcm.isInput(k)){ // only process input channels that are handled at signal rate
*p1 = digitalRead(context, digitalFrame, k);
}
}
}
}
// replacement for bang~ object
//hv_sendMessageToReceiverV(gHeavyContext, "bela_bang", 0.0f, "b");
hv_processInline(gHeavyContext, gHvInputBuffers, gHvOutputBuffers, context->audioFrames);
/*
for(int n = 0; n < context->audioFrames*gHvOutputChannels; ++n)
{
printf("%.3f, ", gHvOutputBuffers[n]);
if(n % context->audioFrames == context->audioFrames - 1)
printf("\n");
}
*/
// Bela digital out
// Bela digital out at signal-rate
if(gDigitalSigOutChannelsInUse > 0)
{
unsigned int j, k;
float *p0, *p1;
const unsigned int gLibpdBlockSize = context->audioFrames;
const unsigned int audioFrameBase = 0;
float* gOutBuf = gHvOutputBuffers;
// block below copy/pasted from libpd, except
// context->digitalChannels has been replaced with gDigitalSigOutChannelsInUse
for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
unsigned int digitalFrame = (audioFrameBase + j);
for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel;
k < gDigitalSigOutChannelsInUse; k++, p1 += gLibpdBlockSize) {
if(dcm.isSignalRate(k) && dcm.isOutput(k)){ // only process output channels that are handled at signal rate
digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5);
}
}
}
}
// Bela digital out at message-rate
dcm.processOutput(context->digital, context->digitalFrames);
// Bela scope
if(gScopeChannelsInUse > 0)
{
unsigned int j, k;
float *p0, *p1;
const unsigned int gLibpdBlockSize = context->audioFrames;
float* gOutBuf = gHvOutputBuffers;
// block below copy/pasted from libpd
for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstScopeChannel; k < gScopeChannelsInUse; k++, p1 += gLibpdBlockSize) {
gScopeOut[k] = *p1;
}
scope->log(gScopeOut);
}
}
// Interleave the output data
if(gHvOutputBuffers != NULL) {
for(unsigned int n = 0; n < context->audioFrames; n++) {
for(unsigned int ch = 0; ch < gHvOutputChannels; ch++) {
if(ch >= context->audioOutChannels+context->analogOutChannels) {
// THESE ARE SENSOR OUTPUT 'CHANNELS' USED FOR ROUTING
// they are the content of the 'sensor output' dac~ channels
} else {
if(ch >= context->audioOutChannels) {
int m = n/2;
context->analogOut[m * context->analogOutChannels + (ch-context->audioOutChannels)] = gHvOutputBuffers[ch*context->audioFrames + n];
} else {
context->audioOut[n * context->audioOutChannels + ch] = gHvOutputBuffers[ch * context->audioFrames + n];
}
}
}
}
}
}
void cleanup(BelaContext *context, void *userData)
{
hv_delete(gHeavyContext);
free(gHvInputBuffers);
free(gHvOutputBuffers);
delete[] gScopeOut;
delete scope;
}
i have uploaded the sample0.wav file to resources in the IDE and it compiles fine. any ideas?
here is the PD-Patch:
#N canvas 262 157 1019 709 12;
#X obj 84 457 hip~ 5;
#X obj 2 124 phasor~;
#X obj 34 167 *~;
#X floatatom 4 6 4 0 0 0 speed - -, f 4;
#X obj 79 483 dac~;
#X obj 34 65 / 44100;
#X obj 0 99 /;
#X obj 84 402 +~;
#X obj 42 32 t b f;
#X obj 5 33 f;
#X obj 241 113 *;
#X obj 221 74 f;
#X floatatom 212 -30 5 0 127 0 endpos - -, f 5;
#X obj 211 12 t b f, f 7;
#X obj 128 139 -;
#X obj 117 116 f;
#X obj 151 116 t b f;
#X obj 117 90 *;
#X obj 109 62 f;
#X floatatom 94 -29 5 0 127 0 startpos - -, f 5;
#X obj 107 31 t b f, f 7;
#X obj 164 -84 r samplelength;
#X obj 302 -106 loadbang;
#X obj 84 425 tabread4~ sample-table0;
#X obj 302 -23 s samplelength;
#X msg 303 -50 100;
#X obj 212 -11 / 127;
#X obj 108 8 / 127;
#X msg 301 -82 127;
#X obj 9 -51 / 20;
#X obj 10 -90 ctlin 1 1;
#X obj 515 -2 table sample-table0;
#X connect 0 0 4 0;
#X connect 0 0 4 1;
#X connect 1 0 2 0;
#X connect 2 0 7 0;
#X connect 3 0 9 0;
#X connect 5 0 6 1;
#X connect 6 0 1 0;
#X connect 7 0 23 0;
#X connect 8 0 9 0;
#X connect 8 1 5 0;
#X connect 9 0 6 0;
#X connect 10 0 7 1;
#X connect 10 0 16 0;
#X connect 11 0 10 0;
#X connect 12 0 26 0;
#X connect 13 0 11 0;
#X connect 13 1 10 1;
#X connect 14 0 8 0;
#X connect 14 0 2 1;
#X connect 15 0 14 0;
#X connect 16 0 15 0;
#X connect 16 1 14 1;
#X connect 17 0 15 0;
#X connect 18 0 17 0;
#X connect 19 0 27 0;
#X connect 20 0 18 0;
#X connect 20 1 17 1;
#X connect 21 0 11 0;
#X connect 21 0 18 0;
#X connect 22 0 28 0;
#X connect 22 0 25 0;
#X connect 23 0 0 0;
#X connect 25 0 24 0;
#X connect 26 0 13 0;
#X connect 27 0 20 0;
#X connect 28 0 12 0;
#X connect 29 0 3 0;
#X connect 30 0 29 0;
thanks so much for your help!
yep, that was it! loading works, genius!
there is still one thing not working though:
when i try to feed the sample length to the receive object with the line you suggested i get the following error:
/root/Bela/projects/samplebela/render.cpp: In function ‘bool setup(BelaContext*, void*)’:
/root/Bela/projects/samplebela/render.cpp:274:75: error: cannot convert ‘BelaContext*’ to ‘HeavyContextInterface*’ for argument ‘1’ to ‘bool hv_sendFloatToReceiver(HeavyContextInterface*, uint32_t, float)’
hv_sendFloatToReceiver(context, hv_stringToHash("samplelength"), sampleLen);
do you have any idea why this might be?
thanks heaps!!
lokki do you have any idea why this might be?
As the compiler error tells you, type checking fails at compile time. This is because in my code I was passing the BelaContext* context
instead of HeavyContextInterface* gHeavyContext
(thanks compiler for spotting this and prevent a tragedy at runtime!).
So the fix is: in hv_sendFloatToReceiver(context, hv_stringToHash("myTableLengthReceiver"), sampleLen);
replace context
with gHeavyContext
.
ah yes, should have spotted that one, especially since it is used in many other places. cool, now on to writing the code for 25 samples! thanks again!