This project uses Bela GUI library to build a drum sample sequencer, and it is a good example of sending data back and forth between Bela (render.cpp) and the GUI (sketch.js). The GUI displays an 8x8 matrix of rectangles, in which each column is a beat and each row corresponds to a speceific WAV sound file that is loaded to the project and stored in a buffer. The user can activate and deactivate the rectangles by clicking on them. For each beat, Bela will receive from the GUI the sound files for that column which are active and it will play them. At the same time, Bela will send a message back to the GUI on each beat to update what is shown.
Note that for each beat we can have up to eight different sounds playing simultaneously (more if a sound is still playing from the previous beat!) so we need multiple read pointers reading from multiple buffers at the same time. An easy way to achieve this is to generate a series of read pointers (i.e., an array of ints where each int will indicate the position of the file sound where we should read).
When a sound starts playing we will assign a new read pointer to the specific buffer. This will tell in the position where we are reading. For example let's say sample number 5 (i.e. gDrumSampleBuffers[4]) starts playing when no other sound is playing. Then, we will assign the first available read pointer to that buffer. As no other sound is playing, then gReadPointers[0] will keep track of where in the buffer we are reading. Additionally we need an array to store the fact that gReadPointers[0] corresponds to gDrumSampleBuffers[4]. Thus, the array gDrumBufferForReadPointer[] will indicate this. This means that gDrumBufferForReadPointer[0]=4;
#include <iostream>
#include <libraries/sndfile/sndfile.h>
#include <libraries/Gui/Gui.h>
#include <cmath>
#include "drums.h"
using namespace std;
float *gDrumSampleBuffers[NUMBER_OF_DRUMS];
int gDrumSampleBufferLengths[NUMBER_OF_DRUMS];
int gDrumBufferForReadPointer[NUMBER_OF_READPOINTERS];
int gReadPointers[NUMBER_OF_READPOINTERS];
int gPattern[NUMBER_OF_DRUMS][PATTERN_LENGTH];
int gPatternLength = PATTERN_LENGTH;
int gEventIntervalMilliseconds = 150;
int gCounter = 0;
int gCurrentBeat = 0;
int gIsPlaying = 0;
{
if(initDrums()) {
printf("Unable to load drum sounds. Check that you have all the WAV files!\n");
return -1;
}
for (int i = 0; i<NUMBER_OF_DRUMS; i++){
for (int j = 0; j<PATTERN_LENGTH; j++){
gPattern[i][j]= 0;
}
}
return true;
}
{
for (
unsigned int n = 0; n < context->
audioFrames; n++) {
gCounter++;
if (gCounter * 1000 / context->
audioSampleRate >= gEventIntervalMilliseconds ){
gCounter=0;
updatePattern();
if ((int) gIsPlaying == 1) {
startNextBeat();
}
}
for (int i=0;i<16;i++) {
if (gDrumBufferForReadPointer[i]>=0){
if (gReadPointers[i] < gDrumSampleBufferLengths[gDrumBufferForReadPointer[i]]) {
gReadPointers[i]++;
}
else {
gReadPointers[i]=0;
gDrumBufferForReadPointer[i]=-1;
}
}
}
float out = 0;
for (int i=0;i<16;i++){
if (gDrumBufferForReadPointer[i]>=0)
out += gDrumSampleBuffers[gDrumBufferForReadPointer[i]][gReadPointers[i]];
}
out /= 2;
}
}
void updatePattern() {
if ((int)data[10]==gCurrentBeat){
for (unsigned int i = 0; i < 8; i++){
gPattern[i][gCurrentBeat] = (int)data[i];
}
gEventIntervalMilliseconds=400-(int)data[8];
gIsPlaying = (int)data[9];
}
}
void startNextBeat() {
for (int i = 0; i < NUMBER_OF_DRUMS; i++){
if (gPattern[i][gCurrentBeat]){
startPlayingDrum(i);
}
}
gCurrentBeat++;
if(gCurrentBeat > 7)
gCurrentBeat = 0;
}
void startPlayingDrum(int drumIndex) {
for (int i=0; i < 16;i++){
if (gDrumBufferForReadPointer[i] == -1) {
gDrumBufferForReadPointer[i]=drumIndex;
gReadPointers[i]=0;
break;
}
}
}
int initDrums() {
SNDFILE *sndfile ;
SF_INFO sfinfo ;
char filename[64];
for(int i = 0; i < NUMBER_OF_DRUMS; i++) {
snprintf(filename, 64, "./drum%d.wav", i);
if (!(sndfile = sf_open (filename, SFM_READ, &sfinfo))) {
printf("Couldn't open file %s\n", filename);
for(int j = 0; j < i; j++)
free(gDrumSampleBuffers[j]);
return 1;
}
if (sfinfo.channels != 1) {
printf("Error: %s is not a mono file\n", filename);
for(int j = 0; j < i; j++)
free(gDrumSampleBuffers[j]);
return 1;
}
gDrumSampleBufferLengths[i] = sfinfo.frames;
gDrumSampleBuffers[i] = (float *)malloc(gDrumSampleBufferLengths[i] * sizeof(float));
if(gDrumSampleBuffers[i] == NULL) {
printf("Error: couldn't allocate buffer for %s\n", filename);
for(int j = 0; j < i; j++)
free(gDrumSampleBuffers[j]);
return 1;
}
int subformat = sfinfo.format & SF_FORMAT_SUBMASK;
int readcount = sf_read_float(sndfile, gDrumSampleBuffers[i], gDrumSampleBufferLengths[i]);
for(int k = readcount; k < gDrumSampleBufferLengths[i]; k++)
gDrumSampleBuffers[i][k] = 0;
if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE) {
double scale ;
int m ;
sf_command (sndfile, SFC_CALC_SIGNAL_MAX, &scale, sizeof (scale)) ;
if (scale < 1e-10)
scale = 1.0 ;
else
scale = 32700.0 / scale ;
printf("Scale = %f\n", scale);
for (m = 0; m < gDrumSampleBufferLengths[i]; m++)
gDrumSampleBuffers[i][m] *= scale;
}
sf_close(sndfile);
}
return 0;
}
{
cleanupDrums();
}
void cleanupDrums() {
for(int i = 0; i < NUMBER_OF_DRUMS; i++)
free(gDrumSampleBuffers[i]);
}