Welcome to Freewebmaster.fr
Home > Hardware > Pi - Rhythm Coach
Pi - Rhythm Coach
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:

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: This program generates a .raw file that you could listen by using aplay: aplay -c 1 -f S32_LE -r 44100 click.raw (1 channel in 32 bits (little-endian) at a 44100 Hz sample rate).

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
Output of the ADC.

#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.


[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