I've seen a few people wondering about using soundfonts on Bela. I found an easy way to do this that works very well. The key is to use TinySoundFont, a soundfont synthesizer written by Bernhard Schelling. It's a single header file written in C/C++ with zero dependencies! It was easy to incorporate into Bela and it worked almost immediately when I first tried it. The only downside I see is that the he did not implement a few more advanced features and he is no longer working on it. But what he created has worked very well for me.
[TinySoundFont]<https://github.com/schellingb/TinySoundFont>
At the beginning of the tsf.h header file is a long comments section explaining how to use it and giving examples. My code is derived from those examples and I list all the pieces you need to use it with Bela below.
The first part goes at the beginning of the Bela program (before Setup)
You need to set the soundfont filename to whatever soundfont you wish to use and upload that soundfont file into the resources section of your project. Also set the preset to whatever preset you wish to use from the soundfont.
// Setting up Tiny Sound Font synthesizer ---
#define TSF_IMPLEMENTATION
#include "tsf.h"
int max_voices = 32; // This affects the number of notes it can keep up with.
int samplerate = 44100;
float tsf_gain_db = 10; // tsf output gain in dB
float tsf_bufferptr[64]; // buffer for 160 samples
const char *filename = "Piano.sf2"; // soundfont filename
const char *presetNames[25];
int preset = 2; // preset from the soundfont (often 0 if there is only one preset)
tsf* fontptr;
This next part goes in the setup section
// setup section for tsf -----
fontptr = tsf_load_filename(filename);
tsf_reset(fontptr);
tsf_set_max_voices(fontptr, max_voices);
tsf_set_volume(fontptr, 1.0);
tsf_set_output(fontptr, TSF_MONO, samplerate, tsf_gain_db);
Then in the Render section, in response to inputs whenever you decide to start a note you can use the following. There are many other commands available as well, this is just the simplest starting point. You need to understand that this just tells tsf that you want a note to stop or start, so the next time it is called to do rendering is when these changes will take effect.
tsf_note_off_all(fontptr); // turn off any previous notes
tsf_note_on(fontptr, preset, pitch, velocity); // start a new note: pitch is midi note number, velocity is 0-1.0
Lastly this section goes at the end of the Render section (after the close of the n loop over all the frames but before the close of Render) so it is only executed once per render call. I tried calling tsf_render for each sample but that took too much time and Bela complained of mode switches and it sounded terrible. But doing with one call per block has worked very well.
// Call tsf to render a block of samples in one call and then write them all to Bela's audio output.
int samples = context->audioFrames; // number of audio samples in a block
tsf_render_float(fontptr, tsf_bufferptr, samples, 0); // tsf outputs block of samples into its buffer
for (int i2 = 0; i2 < context->audioFrames; i2++){ // copy from tsf buffer to Bela output
float output = tsf_bufferptr[i2];
audioWrite(context, i2, 0, output); // 0=left 1=right
audioWrite(context, i2, 1, output); // 0=left 1=right
}
This worked for me. I hope it can be useful to someone else.
-John