Introduction: HummingBox - Electro Acoustic Instrument on Raspberry

This project was developed as a school project at Polytech Paris-UPMC graduate engineering school.


This tutorial will show you step by step how to build an electro acoustic instrument with a Raspberry pi 2 B.

It has to be:

- Autonomous in energy,

- Easy to use (has to come with a manual hosted on a website on the Raspberry)

- Autotune (emitting a sound from a given frequency);

Our instrument receives external sounds through a mic, converts it into a digital signal so that the Raspberry can get the fundamental and, depending on this frequency, creates and plays a sound through the speaker.

What do we need for this tutorial?

- A Raspberry pi 2B

- An electret microphone

- A voltage regulator (5V S7V7F5)

- An analog to digital converter MCP3008

- An opamp LM386 An opamp LM358

- A speaker A 3.5mm jack cord

- Buttons, switches (we use two buttons and a switch)

- A battery holder for 6*1,2V (2500mAh)

/!\ Remember that this is a school project, so a lot can be improved.

Step 1: Microphone

The electret microphone is the equivalent to a permanently polarised condenser from its production.
The mic’s impedance is very high, so you can't connect a low impedance or a high capacity.

We put a FET transistor to lower the exit impedance, but it has to be powered by an external source between 1.5V and 12V.

The microphone is powered with a DC voltage and it transforms this signal into an AC signal corresponding to the received sound. The FET transistor is protected by a resistance in serial with the DC source.The DC source is blocked by a capacitor so it doesn't disturb our audio signal. This circuit has to be able to filter the signal to eliminate parasites and to amplify the signal.We decided we would focus on recognizing a flute’s notes, so a frequency range from 250Hz to 2kHz.The gain should be of maximum 10dB, because the signal from the mic doesn't go over 200mV. In the end, the signal should have a peak to peak voltage of 2V so it can go through the analog to digital converter with an offset of 2.5V and a voltage range from 0V to 5V. You will find a first order high-pass filter with a cut frequency at 50Hz that filters low frequencies (noise). This circuit has a gain of R2/R1 = 10 as we wanted.Then there is a first order bandpass filter with a low cut frequency at 50Hz and a high cut frequency at 3.3kHz and a gain of . Since the signal is between -2.5 and 2.5V and that the analog to digital converter only converts positive voltage, we create a 2.5V offset with a voltage divider, so not to loose information. In the end, at the entrance of the converter, the signal is between 0 and 2V with a 2.5V offset after going through a second order high pass filter at 50Hz and a first order low pass filter at 3.3kHz.

Step 2: Analog to Digital Converter

We want the Raspberry to analyze the signal and get the fundamental.

But the Raspberry doesn’t have any analog pin so we have to convert the analog signal to digital with the MCP3008. This converter can receive up to 8 signals in parallel and digitize them. It creates a signal on 10 bits that the Raspberry has to collect via SPI (Serial Peripheral Interface). Some of the GPIO’s pins are already set as SPI pins (cf picture above), it’s the “Hardware SPI”. We can set the GPIO to use any pin with the SPI, if we want to, but we will use the Hardware SPI.

/!\ Be careful, before connecting anything to your card, always check the GPIO board corresponding to your Raspberry model, the picture above is for Raspberry Pi 2 B. The converter has 8 channels for analog signals and 8 pins to set the SPI communication.

We only need one channel, we will use CH0.

/!\ Our code is written to convert only one signal at a time.

/!\ In our code, a2DChannel = 0, if you use another channel, think about changing the value in the code.

VDD: power supply (5V)

VREF: reference voltage used for digital encoding (5V)

AGND: analog ground

CLK: the clock used for the SPI communication. Choose wisely so your capture time and the start bit recognition are correct. A standard value is 1MHz, like in the test code “mcp3008SPItest.cpp”. It is connected to the pin 23 - SPI SCLK

Dout: Master Input Slave Output transmission channel [that’s the channel from which the converted signal arrives to the Raspberry], connected to the pin 21 - SPI MISO

Din: Master Output Slave Input transmission channel [the Raspberry asks the converter to start converting on a given channel], connected to pin 19 - SPI MOSI

nCS/SHDN: notChipSelect / Shutdown (the second one isn’t used). We have to put nCS at 0 and Din to 1 on a rising edge to create a start bit (to signal the converter it has to start converting), connected to pin 24 - SPI CE0.

The Raspberry has to send 1, so the converter receives 0.

/!\ On the Raspberry, this pin has to go back to 0 before the next start bit, or the converter won’t distinguish the start bit from a simple data on Din.

DGND: digital ground, has to be connected to one the ground pins on the Raspberry.

/!\ Don’t hesitate to read the MCP3008 datasheet for more detail.

Step 3: Speaker

Once a sound is created by the Raspberry, it will go through the Jack.

We decided to connect the Jack cord to a speaker amplifier circuit. Here the speaker’s impedance can be of any value. We are using a 50 Ohm speaker. But you can decide to connect a pre-amplified speaker directly via the Jack to the Raspberry.

/!\ We are having trouble with this circuit, and aren’t able to work on it yet. It might be that the speaker uses too much power or just a disconnected component.

Step 4: Digital Acquisition

Now that the analog to digital converter is connected to the Raspberry, let’s see how to use the SPI.

First, we have to enable the Hardware SPI:

/!\ We do everything in command line via SSH.

We have to be on the latest Raspbian version. If your Raspberry isn’t up to date, it’s time to do it. Warning, it can take time.

Run those commands:

sudo apt-get update

sudo apt-get upgrade

Then we have to modify a system file:

sudo nano /boot/config.txt

See if the following line is in the file:

dtparam=spi=on

You will either find it as it is, or preceded by a # (then it is a comment, delete the #, save and quit the file: ctrl+x, y). If not, add it at the end of the file.

Reboot the card so the modifications on the system are saved.

sudo reboot

OR

sudo shutdown -r now

Finally let’s see if the SPI is enabled:

lsmod | grep spi_

If “spi_” appears followed by the processor’s number, your SPI is enabled. For example, it shows “spi_bcm2835” for us.

Then we just need to program the card :)

Our code is in C++, but you can easily find similar codes in Python online.

Step 5: FIY: Circuit Schematic and PCB

/!\ It’s just for your information, we had to make some minor modifications.

Step 6: Code: Makefile

/!\ We are using several awesome libraries made by awesome people.

-L/usr/local/lib and -I/usr/local/include give access to necessary libraries for the program.

-lwiringPi: so you can assemble using the WiringPi functions in order to communicate on the SPI. (http://wiringpi.com/)

-lasound: alsa sound library to produce sound.

-lpthread: a library with functions to create and manage threads.

-lrt: real time library for timers.

-lm: mathematics library.

-lgsl and -lgslcblas: to calculate the FFT (Fast Fourier Transformation) and get the fundamental.

Step 7: Code : Acquisition Numérique

/!\ Here we explain the test code of the MCP3008 use. See in timerSpi.hh how the functions from the test have been integrated in our program.

The initial code is from here, the “mcp3008Spi::spiWriteRead” and “main” functions have been modified to test the digital acquisition:

mcp3008Spi::spiWriteRead: the first for loop initializes the SPI, the second one reads on the SPI.

We configure the SPI to communicate 8 bits words with a speed of 1MHz. We convert signals only on the channel 0. For each reading, we configure data[] so the MCP3008 knows it has to convert the signal from channel 0. It writes over data the converted signal, then the Raspberry retransforms the encoded value into an integer in a2dVal. a2dVal can then be stored in an array that will be send to calculate the fft.

Step 8: Code : Timer

For timers, we used a code from here: http://quirk.ch/2009/07/how-to-use-posix-timer-within-c-classes

It’s a class that generates a timer that will execute a function every time the timer gets to a given value.
In the code:

private: int memberVariable;

You can declare your variables.
In :

TimerClass() : memberVariable(0) {

// Define the timer specification

// One second till first occurrence

this->timerSpecs.it_value.tv_sec = 1;

this->timerSpecs.it_value.tv_nsec = 0;

// and then all 3 seconds a timer alarm

this->timerSpecs.it_interval.tv_sec = 3;

this->timerSpecs.it_interval.tv_nsec = 0;

// Clear the sa_mask

sigemptyset(&this->SignalAction.sa_mask);

// set the SA_SIGINFO flag to use the extended signal-handler function

this->SignalAction.sa_flags = SA_SIGINFO;

// Define sigaction method

// This function will be called by the signal

this->SignalAction.sa_sigaction = TimerClass::alarmFunction;

// Define sigEvent

// This information will be forwarded to the signal-handler function

memset(&this->signalEvent, 0, sizeof(this->signalEvent));

// With the SIGEV_SIGNAL flag we say that there is sigev_value

this->signalEvent.sigev_notify = SIGEV_SIGNAL;

// Now it's possible to give a pointer to the object

this->signalEvent.sigev_value.sival_ptr = (void*) this;

// Declare this signal as Alarm Signal

this->signalEvent.sigev_signo = SIGALRM;

// Install the Timer

if (timer_create(CLOCK_REALTIME, &this->signalEvent, &this->timerID) != 0) {

// timer id koennte mit private probleme geben

perror("Could not creat the timer"); exit(1); }

Here, we initialize all variables to set the timer and all the class’ variables (memberVariable(0)).
What interests us the most in the initialisation of variables is:

// Define the timer specification

// One second till first occurrence

this->timerSpecs.it_value.tv_sec = 1;

this->timerSpecs.it_value.tv_nsec = 0;

// and then all 3 seconds a timer alarm

this->timerSpecs.it_interval.tv_sec = 3;

this->timerSpecs.it_interval.tv_nsec = 0;

The first two lines are to enable the first interruption. The first one is for seconds, the second one is for nanoseconds. The next two enable all next interruptions. Finally here what will be executed at each interruption:

static void alarmFunction(int sigNumb, siginfo_t *si, void *uc) {

// get the pointer out of the siginfo structure and asign it to a new pointer variable

TimerClass * ptrTimerClass = reinterpret_cast (si->si_value.sival_ptr);

// call the member function

ptrTimerClass->memberAlarmFunction(); }

Here you can change the code as you like it.
We added a few variables, to use the MCP3008 and a structure (a shared memory) so all threads can share data. This structure is composed of:

typedef struct dataThread{

double tab[TAILLE];

int cptFFt;

int cptSon;

int frequence;

int mode;

int freq1;

int freq2;

}dataThread;

An array that compiles all read data from the SPI, counters to manage the wait between each sound emission and each call to the fft, the played frequency, the play mode and two frequencies so to ensure that it’s not noise.

At each interruption:

- the array is filled by integers from the MCP3008

- all values already in the array are shifted so we can add a new value.

Step 9: Code : FFT

gsl_fft_real_wavetable * real;

gsl_fft_real_workspace* work; work = gsl_fft_real_workspace_alloc (TAILLE_TAB); real= gsl_fft_real_wavetable_alloc (TAILLE_TAB); gsl_fft_real_transform(tab,1,TAILLE_TAB,real,work); gsl_fft_real_wavetable_free(real);

The Fast Fourier Transformation is applied to the “tab” array. All values will be used and then replaced by results from the FFT.
Then you want to get the fundamental, so get the index with the highest value in it and use this formula:

( indice * FrequenceEchantillonage / 2 ) / TAILLE_TAB

Step 10: Code : Threads

Our program has five threads that will work simultaneously, that is why we need a shared memory.

rc = pthread_create(&threads[0], NULL, RempliTableau, (void *)&td);

if (rc){

std::cout << "Error:unable to create thread," << rc << std::endl;

exit(-1); }

The pthread_create function has as parameters:
- a specific thread from an array of threads

- options

- the function that will run in the thread

- the shared memory so the thread can access it

The timer interrupts every 0.45ms, each time we get data. Then the FFT is calculated each time the array is filled, so every 220 readings of the MCP3008, so it takes 0.1s to get a frequency. Finally every two frequency or every 0.2s, we emit a sound.

Step 11: Code : Sound

void generate_sine( int volume, char *buffer, int len, double freq, float *_Phase)

With this function, we create a sinusoid depending on the parameters passed and we stock the values in the buffer.

We save where the signal stops so we can restart the reading of the buffer from there when the frequency has changed, so we avoid cuts in the sound played.

void produce_sound(double frequency)

With this function, we initialize the parameters we pass to “generate_sine”.
FIY, PCM stands for Pulse Code Modulation.

Opening verification

int snd_pcm_open ( snd_pcm_t ** pcmp,

const char * name,

snd_pcm_stream_t stream,

int mode )

Verification of all parameters

int snd_pcm_set_params ( snd_pcm_t * pcm,

snd_pcm_format_t format,

snd_pcm_access_t access,

unsigned int channels,

unsigned int rate,

int soft_resample,

unsigned int latency )

Writing

int snd_pcm_sframes_t snd_pcm_writei ( snd_pcm_t * pcm,

const void * buffer,

snd_pcm_uframes_t size )

Step 12: Final Product

Tada! After all this you should have an instrument that can recognize a frequency and play the same note, or shifted up or down an octave.

You can play around with the code to create new modes and create a case to protect it.

/!\ Just in case, try to keep the microphone and the speaker on opposite directions to avoid any feedback.

Make Noise Challenge

Participated in the
Make Noise Challenge

First Time Authors Contest 2016

Participated in the
First Time Authors Contest 2016