I am not sure I understand exactly. If you want to save the preset table from within Pd, it's as easy as ticking the "Save contents" box in the array in Pd. This will be the same every time you run the patch (as it will be compiled inside the patch). If instead you want to change the presets every time you start the patch (e.g.: to reload whatever the settings were when you last used it), then things are much more complicated. You will have to use a custom render.cpp
to wrap your heavy patch, and there you'll have to manually save and load the (text?) file where you'll store the presets. Once you load the data, you can use the hv_table_
interface to access the array directly (there is an example in the Bela wrapper for heavy already, where we use it for the data coming in from the multiplexer capelet).
pd heavy preset handling (with tables)
"unfortunately" it is the second case you describe. i want to create and adjust presets while running the heavy program, and of course save them.
i think i could get the loading of tables to work since that is essentially like loading a sound file into a table via a custom render.cpp (i did that once in the very beginning of my Bela experience with your help)
what i am struggling with is the concept of saving the altered table from within the heavy program. what would be the command from within PD (heavy specific) to write the table? the docs i can find on the enzien GitHub are a bit fuzzy.
i am thinking that every time i press a button for saving a preset i would just save the whole 176 entry table (16 presets of 11 settings) to a file on the emmc. loading would only have to happen once at startup.
lokki what would be the command from within PD (heavy specific) to write the table?
One option is to generate the command in "Pd" (heavy). In that case, you'd send out a message with something like [send save_table @hv_param]
. The C++ wrapper would then have to handle a case for that in the sendHook()
function.
Another option is to handle the whole thing in C++. E.g.: with adigitalRead()
inside render()
.
- Edited
EDIT: i will try the first approach then since i will be using a midi controller as UI
lokki
i used this code back in the days to load 16 samples into tables on startup of a heavy program. i can simply adapt this to load only one file.
my preset-table has a format of integers between 0 and 127 (midi messages). can i still get the data from a text file via the getSamples function? or is this only working with wav files? note that i cannot try this right now, since my bela is in my atelier. or is there a similar function to getSamples to read data from a text file. either way, i don't mind what format the preset file will be in since i will only save and recall it from within heavy. so it can also be a pseudo wav file if that is the easiest solution.
char fileName[12];
char tableName[14];
char lengthName[14];
for (int k = 0; k < 16; k++) {
sprintf(fileName, "sample%d.wav", k);
sprintf(tableName, "sample-table%d", k);
sprintf(lengthName, "samplelength%d", k);
int sampleLen = getNumFrames(fileName);
hv_uint32_t tableHash = hv_stringToHash(tableName);
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(gHeavyContext, hv_stringToHash(lengthName), sampleLen);
}
getSamples()
is using libsndfile
under the hood to retrieve that data from the audio file. A pseudo audio file could be a good option, because you could reuse this function. Note that the function is currently (probably) using sf_read_float()
, which returns values between -1 and 1. You may want instead to use sf_read_short()
to obtain integer values. See the note 1 at the bottom of the libsndfile
documentation here. You would then have to write a writeSamples()
to use libsndfile
's sf_write_short()
to write back to the disk.
giuliomoro or i can simply divide my 0-127 range by 127 and multiply within heavy when i receive the data again. the less fuzz the better...
i will try to write the writeSamples() function and see if i can get something going :-)
giuliomoro ok, i got the reading and scaling part sorted out. that was pretty basic, since i just had to copy from the previously posted render.cpp and adjust it a bit. for the writing bit, i looked at the SampleLoader.h file to see how getSamples()
is constructed. do you have any reference for the writeSamples()
function i should write? who wrote the SampleLoader.h? i found this: https://github.com/erikd/libsndfile/blob/master/examples/sndfilehandle.cc but the format of the functions is quite different to what is in SampleLoader.h
- Edited
this is better i think:
and i added this to SampleLoader.h:
int writeSamples(string file, float *buf,int frames)
{
SNDFILE *sndfile ;
SF_INFO sfinfo ;
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
sfinfo.channels = 1;
sndfile = sf_open(file.c_str(), SFM_WRITE, &sfinfo);
sf_write_float(sndfile,buf, frames);
sf_close(sndfile);
return 0;
}
probably this is not right, but it could be a start :-)
so i add to the switch statement here like so for example??
switch (sendHash) {
case save_table: {
writeSamples(bla,bla,bla);
break;
}
on another note, would removing these: rt_printf("noteon: %d %d %d\n", channel, pitch, velocity);
from the render.cpp make midi faster, or less cpu hungry? or is the rt_printf not affecting performance when the IDE is not running?
or here, just for my own reference: http://www.labbookpages.co.uk/audio/wavFiles.html#writingC
lokki who wrote the SampleLoader.h?
I think we did
lokki i found this: https://github.com/erikd/libsndfile/blob/master/examples/sndfilehandle.cc but the format of the functions is quite different to what is in SampleLoader.h
this is using a C++ API (note the .hh
in #include <sndfile.hh>
) which I have never used (or heard of).
lokki this is better i think:
looks like a very good starting point
lokki and i added this to SampleLoader.h:
that looks good. Does it work?
lokki switch (sendHash) {
case save_table: {
writeSamples(bla,bla,bla);
break;
}
Assuming that save_table
is something you computed with hv_stringToHash("save_table")
, then yes. Note that because of how switch
is implemented, save_table
needs to be constant (i.e.: you need to compute it once, then hardcode the value, see the existing examples in the code).
lokki on another note, would removing these: rt_printf("noteon: %d %d %d\n", channel, pitch, velocity);from the render.cpp make midi faster, or less cpu hungry? or is the rt_printf not affecting performance when the IDE is not running?
It would indeed save some CPU time. Good thing to comment it out. It was meant to be optional: https://github.com/BelaPlatform/Bela/issues/373
- Edited
giuliomoro that looks good. Does it work?
kind of. i implemented it this way in the switch case:
case 0x6E64CDC1: { //save_table custom
char fileName[12];
char tableName[14];
// char lengthName[14];
sprintf(fileName, "presets2.wav");
sprintf(tableName, "presets");
//sprintf(lengthName, "samplelength%d", k);
hv_uint32_t tableHash = hv_stringToHash(tableName);
float * table = hv_table_getBuffer(gHeavyContext, tableHash);
writeSamples(fileName, table, 4);
break;
}
for clarity i used another name, presets2.wav to compare to my presets.wav file i read from (which is working nicely)
i can confirm that there is a file written to bela, yes! so my save_table hash generation worked out!
i get 4 dropped blocks, but that is to be expected i guess. for the moment i am only writing 4 samples as a test. the file ends up being called presets2.wavpresets, i have no idea where that extra presets is coming from. also the written file cannot be opened by audacity, since it does not seem to be an ordinary wav file.
the SampleLoader.h code is the one i posted above, can you spot why the filename gets corrupted in this way?
is it a problem to use "4" in the writeSamples()
for the frames input? i don't know how to get the size of the table from PD (instead of getting it from the file presets.wav, which i do successfully)
ok when i change the sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
to: sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
the name is correct. however there is no data written in that case, file is 0.0kb and audacity can still not open it.
lokki i get 4 dropped blocks, but that is to be expected i guess
yes, expected.
lokki g called presets2.wavpresets, i have no idea where that extra presets is coming from.
that is a C thing, where strings do not actually exists, but are sequences of char
s terminated by a null character (\0
). The length of the string "presets2.wav" is therefore 12+1 = 13
. When you call
sprintf(fileName, "presets2.wav");
sprintf(tableName, "presets");
the first string actually exceeds the space allocated in fileName
, and effectively writes past the end of the array, causing undefined behaviour. In this case, the behaviour is that fileName
and tableName
are allocated contiguously, so that the \0
terminating character for the fileName
string actually ends up being the first element of the tableName
string, and the second line overwrites that. Remember that strings are \0
-terminated, which means that when you start reading a string from a given position in memory, the end of the string will be the first \0
character encountered. As a result, if you start reading from tableName
, you get presets
, but if you start reading from fileName
, you will read presets2.wavpresets
. Whatever, just increase the size of those char
arrays. You should also use snprintf
instead, which is safer (check man snprintf
). But ultimately, in case you don't actually need to change your strings dynamically, there is no need to do any of that. This will do:
const char fileName[] = "presets2.wav";
const char tableName[] = "presets";
lokki is it a problem to use "4" in the writeSamples() for the frames input?
It is not a problem.
lokki i don't know how to get the size of the table from PD
According to this there is a unsigned int hv_table_getLength(HeavyContextInterface *c, unsigned int tableHash);
that should do it.
lokki ok when i change the sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
to: sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
the name is correct. however there is no data written in that case, file is 0.0kb and audacity can still not open it.
not sure what is happening there. Can you not go for PCM_16
? Note 1 at the bottom of the docs is a bit confusing for me as to what float
formats do, and my hint would be: stay away from them unless you actually need them (not that this explains why your code stops working when you do that).
giuliomoro
thanks as always for your support!! i replaced the name variables with constants and indeed it works now, great! actually that 12 in there was a typo in the first place.
and, i got bitten by the "heavy or IDE not updating all the files it should" bug... once i deleted the whole heavy destination folder and recompiled everything the data is now correctly written out into the wav file, great! i did not have to change anything in the code...
thanks again, i will now try to implement it into my actual patch.
lokki heavy or IDE not updating all the files it should" bug... once i deleted the whole heavy destination folder
This may happen if you have a file open in the IDE and then you change that file from the terminal: the IDE doesn't actually monitor file changes on disk. It assumes it has full control
lokki thanks again, i will now try to implement it into my actual patch.
and it all works flawless! i was wondering in the process if it is worth the trouble to use heavy, but first of all my CPU dropped from 80 % to 28 % and second the latency is much smaller, and since this is a vocal live processor this was very important! thanks again.
ok, coming back to this...
there is a problem with my code or the way it is executed.
every now and then the presets.wav file gets garbage data when i write to it which makes the presets useless in the long run.
could it be that sometimes the data cannot be written right because it runs in the audio-thread? at this point i am writing 400 samples into a wav file and i get 14 blocks dropped when i do so. 9 out of 10 times it works but if it doesn't the generated wav file has not much to do with my presets anymore. is there a way to write the samples in an auxiliary task to avoid this problem?
i am running the system off the internal memory and also save on there, could this be the problem? (i can switch to an sdcard if that is the issue)
- Edited
lokki o. 9 out of 10 times it works but if it doesn't the generated wav file has not much to do with my presets anymore.
can you describe what is in the generated "corrupted" wav file? Try and save several copies of the file without changing the preset of the content, so that you would expect it to generate exactly the same file over and over again. Set aside a "good" one, and a "corrupted" one.
First off, check if the size is exactly the same in both cases. Then use hexdump
to print a hex dump of the files and see where the differences are. You would expect that the header of the two .wav
files would be the same, and that there is some difference in the data part, perhaps.
lokki could it be that sometimes the data cannot be written right because it runs in the audio-thread?
This shouldn't be a problem, unless you suddenly kill the board without shutting down gracefully. If that is the case, when the file is corrupted you'd expect it to be shorter, or empty. You should probably add a call to sync()
(see man page ) after sf_close()
, to ensure the file is correctly synced to disk and not stored in a buffer by the OS.
this gives you an idea of the difference between close()
(which implies flush()
) and the data being actually written to disk, which should happen normally after a while, or can be forced withsync()
.