This short experiment is more or less a proof of concept.
My actual target was to turn my Raspberry Pi into an electronic drum kit.
However, given my lack of knowledge of the Pi, I decided to start with a simple rhythm coach.
In this example, the trigger is an accelerometer.
I am still working on a "full" drum kit that uses the Pi, but that will be the purpose of a next project.
How a rhythm coach works
Let's describe how a rhythm coach works before going into more details. A rhythm coach is a device that a drummer uses for training. The drummer sets a tempo, and while he trains, the rhythm coach gives him a score based on the drummer's rhythmic accuracy. So, the rhythm coach measures the delay between the click when the drummer hits a drum pad. This delay can be positive or negative.Usually, a rhythm coach can generate different time signatures and patterns, which can lead to a very hard training, especially when the tempo is high. In principle, this is actually very basic! We just need a click track and a sensor that detects when the drummer hits a pad. A Raspberry Pi can then perform the signal processing in real time, and tell the drummer if he (or she) is in time.
Using an accelerometer as a trigger
As the Raspi has an integrated sound card, no hardware is needed to generate the click track. However, we will definitely need a sensor to detect when the drummer hits the pad. Electronic drum kits commonly use piezoelectric sensors, but signal conditioning is required if we want to connect their output to a Raspi [1].Since I intend to use the GPIO to communicate with the sensor (which will be connected to an ADC, of course), the sensor will have to deliver a voltage between 0 and 3.3V. I spent some time looking for the right sensor, and I decided to use an accelerometer because, among the large number of possibilities (pezos, force sensors,...), accelerometers can be found as breakout boards.
Of course, to choose the right accelerometer, we need to know the value of the acceleration that we will have to measure. Accelerometers manufacturer give their sensors range in "g". As you might know, g is the standard acceleration due to gravity, so, if you put an accelerometer on a table, it will measure 1g on its z axis (the vertical axis). It is then obvious that a drummer will need an accelerometer that can measure at least 10g, but, do wee need 50g, 100g, or even more? Well, actually it's more than 100g, according to that reference: [2] in which accelerations as high as 1400m/s2 are reported. If we consider that g = 9.81m/s2, that leaves us with approximately 143g. Remember that this is the absolute value of the acceleration, hence, the measured acceleration oscillates between +143g and -143g, which gives ±143g. According to Wikipedia, this is more than the acceleration of a Sprint missile.
Analog Devices MEMS accelerometers are quite good, and I found three suitable models: ADXL377, ADXL001, and ADXL193. The latter is actually deprecated, so let's consider only the 377 and the 001. The ADXL001 is available in ±70g, ±250g, and ±500g, which is quite a lot! However, it's a rather expensive device, especially when sold as an evaluation board. Here is why I decided to go for the ADXL377 that can measure accelerations up top ±200g. Apart from that, the difference with the ADXL001 is that the 377 bandwidth is 1.6kHz (22kHz for the ADXL001). Finally, as this is an analogue device, we will need an ADC because the RasPi has only digital inputs.
Turn your RasPi into a rhythm coach
As mentioned before, a rhythm coach needs a click track. I though it might be easier to generate a sound file and play it, using omxplayer for instance, while the rhythm coach is running. So here is how it will work:- A sound file will be generated according to the desired tempo
- It will then be played
- As the sound file is being played, a program will detect when the drummer hits the pad
Click track
OK, so we need to generated a click track first. In order to keep it simple, I developed a simple c code to generate a raw sound file. Well, as this is pretty straightforward, I'll let you take a look at the code, the comments explain it all.#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#define S_RATE (44100)
int main(int argc, char ** argv)
{
char *arg1 = argv[1];
char *arg2 = argv[2];
char *arg3 = argv[3];
char *arg4 = argv[4];
fprintf(stdout, "Params are: %s, %s, %s, %s\n", arg1, arg2, arg3, arg4);
int dur = atoi(arg2); // Sample duration in seconds
int tempo = atoi(arg1); // Set tempo value in BPM
int sig = atoi(arg4); // Time signature (default 4)
int rhythm = atoi(arg3); // Rhythm (default 1)
int nsamp = S_RATE*dur; // Total nb of samples
int* buffer = (int*) malloc(nsamp * sizeof(int));
double amplitude = (~0u)/2; // Max amplitude for double
double phase = 0; // Phase of the sine wave
int freq_Hz = 880; // Frequency of the sine wave
// Frequency of the sine wave
double freq_radians_per_sample = freq_Hz*2*M_PI/S_RATE;
int click_duration = 10; // Click duration in ms
// Time between two clicks
double Dt = 60/((double)tempo)/rhythm;
double Dsamples = S_RATE*Dt; // Nb of samples between two clicks
// Set the click duration in seconds
double dt = (1.0/freq_Hz)*(double)((int)(click_duration*freq_Hz/1000));
double dsamples = S_RATE*dt; // Nb of corresponding samples
// Convert samples to int
int iDsamples = (int)Dsamples;
int idsamples = (int)dsamples;
int n = 1, click, mul, i;
for (i=0; i < nsamp; i++)
{
click = (int) n*Dsamples; // Next click sample nb
// If we are generating a click
if(i > click && i < click + idsamples)
{
if(n % sig == 1) // Acute click
mul = 2;
else
mul = 1; // Normal click
phase += mul*freq_radians_per_sample;
buffer[i] = (int)(amplitude * sin(phase));
}
else // No click, so signal is zero
{
buffer[i] = 0;
phase = 0;
if(i == click + idsamples) n++;
}
}
// Write raw file
FILE* f = fopen("click.raw", "wb+");
fwrite(buffer, nsamp, sizeof(int), f);
fclose(f);
free(buffer);
return 0;
}
To compile this code, you'll need to link the math library. To make it easier, I use the following bash script:
#!/bin/bash
gcc -o mwav mwav.c -lm
./mwav $1 $2 $3 $4
The four arguments that need to be provided are:
- The first argument is used to set the tempo in bpm.
- The second argument sets the sample duration in seconds.
- The third one sets the rhythm. Ex: @60 bpm 1 implies one beat every second, 2 implies 2 beats per seconds, etc.
- The fourth argument is used to change the position of the acute beat (default 4, which implies a 4/4 time signature).
Accelerometer + RasPi = Rhythm Coach
The above equation is kind of wrong, it should be Accelerometer + ADC + RasPi = Rhythm Coach, but the title would be too long... OK, so we have a raw sound that is generated
#include <stdio.h>
#include <stdlib.h>
#include <bcm2835.h>
#include <time.h>
#include <math.h>
unsigned long get_tmp(void);
int main(int argc, char ** argv)
{
char *arg1 = argv[1];
char *arg2 = argv[2];
int dur = atoi(arg2); // Sample duration in seconds
int tempo = atoi(arg1); // Set tempo value in BPM
unsigned long lDur = (unsigned long) 1000000*dur; // Set duration in microseconds
unsigned long start, end;
int i,j, a, Channel = 0;
double dms; // Time difference between hits in ms
unsigned char data[3]; // Data to send to the ADC
int val; // Output value of the ADC
if(!bcm2835_init()) // Init bcm lib
{
printf("Could not init bcm2835...\n");
return 1;
}
bcm2835_spi_begin();
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // The default
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // The default
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_64); // ~ 2 MHz
bcm2835_spi_chipSelect(BCM2835_SPI_CS0); // The default
bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); // The default
data[0] = 0b11100000 | ((char) (Channel << 3)); // Specify Channel
printf("Start...\n");
start = get_tmp(); // Get start time in microseconds
unsigned long beg = start; // Same, but used to set the loop duration
while(get_tmp() - beg < lDur) //Loop while sound file is played
{
uint8_t mosi[3] = { data[0] }; // Data to send to the ADC
uint8_t miso[3] = { 0 }; // Data to read
bcm2835_spi_transfernb(mosi, miso, 3); // Send and get data
// Decode data
val = ((miso[0] & 0x01) << 11) | (miso[1] << 3) | ((miso[2] >> 5) & 0x07);
if( val > 2100 || val < 2000) // Detect hit
{
end = get_tmp(); // Get time of hit in microseconds
dms = (double) (end - start)/1000; // Get time difference between hits in ms
if(dms > 100) // If the time difference is more than 100 ms
{ // Used to avoid rebounds
// Display the time difference between the hit and the reference (tempo)
printf("%f\n", dms-((double)1000.0*60.0/tempo));
start = get_tmp(); // Get hit time in microseconds
}
}
}
printf("Stop...\n"); // End of program
bcm2835_spi_end(); // End SPI communication
bcm2835_close(); // Close bcm lib
return 0;
}
// This function returns the time in microseconds
unsigned long get_tmp()
{
struct timeval tv;
unsigned long time_in_micros;
gettimeofday(&tv,NULL);
time_in_micros = 1000000 * tv.tv_sec + tv.tv_usec;
return time_in_micros;
}
#!/bin/bash
cd click
./gen_wav $1 $2 $3 $4
cd ..
nohup aplay -c 1 -f S32_LE -r 44100 click/click.raw &
gcc -o test_bcm test_bcm.c -lbcm2835 &&
sudo nice -n -20 ./test_bcm $1 $2 &&
killall aplay &&
rm nohup.out
Some results, and some ideas for the future
A quick demo would probably show you how useful this thing can be! You can for instance plot an histogram that shows you your jitter... Take a look at that:Finally, I think that can be a really useful tool to improve your rhythm! Plotting the histogram can show you your strengths and your weaknesses. It can surely help you to improve you technique. Are you often ahead? This very basic rhythm coach will tell you a lot. You can even use it as a drumometer. You just have to count the number of hits per minute...
So this is just a start, and I'm planning to realise a full drum kit based on the same method. My target is to make it work on a raspberry pi, but if it doesn't, I will surely find to a way to make it work. Just like this device, the drum kit will use accelerometers, which makes things easy as they come as breakout board, but any other sensor will work.
References
# | Author | Title | Year | Journal/Proceedings | URL |
---|---|---|---|---|---|
[1] | leucos | Piezo transducer signal conditioning | 2009 | URL | |
[2] | Wagner, A. | Analysis of drumbeats—interaction between Drummer, Drumstick and Instrument | 2006 | KTH Computer Science and Communication. | URL |