Audio Spectrum Analyzer for a Bluetooth Speaker using MBED and Raspberry Pi

Matt Szelc, Ben Thornbloom, Latifah Almaghrabi

Setup

This project has four main system functions: sound measurement and spectrum analysis, LED array spectrum output, LED array color and brightness control, and Bluetooth speaker control.

/media/uploads/mszelc4180/project_diagram.jpg

Spectrum Analyzer System

The spectrum analyzer portion of the system consists of a microphone with a simple preamp circuit and the MSGEQ7 7-band Graphic Equalizer IC. Input from the microphone is sent to the input of the MSGEQ7 which reads out amplitude values for 7 different frequency bands based on the input spectral components.

MSGEQ7

/media/uploads/mszelc4180/equalizerschematic.png

Datasheet

Setup Tutorial

The microphone setup consists of a simple 2 pin condenser microphone with a preamp circuit:

/media/uploads/mszelc4180/mic_circuit.png

The microphone output maxes out at about 300 mV for a loud, nearby sound, but a more complex preamp circuit could be built to improve the output amplitude.

LED Array:

The 16x32 LED array from Adafruit can be chained with more arrays to create a large LED display. For this project, only one board was used.

/media/uploads/mszelc4180/raspberry_pi_rgbmatrixpanel_med.jpg

LED Array Info

C++ library for the array

Touch Potentiometer Control Features:

Two touch potentiometers are used with pushbutton input to control the brightness and color of the LED board. A circular touch pot controls color and a linear strip pot controls the brightness. To select a new RGB value, the R, G, or B pushbutton is compressed, a value is touched on the circular pot, and the pushbutton is released. To select the brightness level, the strip pot is touched or a finger can be run along it to linearly decrease and increase the LED brightness.

Circular Touch Pot

Linear Strip Pot

Bluetooth Speaker:

A Bluetooth speaker can be connected to the raspberry pi using PulseAudio. After PulseAudio is installed, the PulseAudio Volume Control GUI can be used to disable the Raspberry Pi default sound card. If the sound card is not turned off, omxplayer (built in Pi media player) will not play audio through the speaker and instead will default to the 3.5mm jack.

PulseAudio Tutorial

Start Song, Pause/Play, Stop pushbutton control:

Pushbuttons are used to start and stop a song, skip to the next song, and increase or decrease the volume. A startup script is used to read the .mp3 files off a flash drive and create a “playlist” text file. The script for the “Start Song” button then creates an array from the text file and cycles through the array. The “Pause/Play” and “Stop” push buttons make use of the python uinput library to map to the built in pause/play and stop keyboard commands for omxplayer.


Software

MBED code

The following code runs on the mbed. It reads data that gets sent from the MSGEQ7 as a result of input from the mic and stores the amplitudes for each frequency channel in an array. It also reads in values from the potentiometers and associated push buttons to store brightness and color values. The 11 values - 7 channel amplitudes, 3 colors, 1 brightness - are then sent in a delineated form on the serial bus to the Raspberry Pi where they are parsed and used to change what shows up on the LED array board.

main.cpp

#include "mbed.h"
#include <math.h>
#include <string>
#include "MSGEQ7.h"


RawSerial pi(USBTX, USBRX);

MSGEQ7 eq(p15, p16, p17);

AnalogIn vol(p20);
AnalogIn RGB(p19);
AnalogIn brightness(p18);

DigitalIn Red(p23);
DigitalIn Green(p22);
DigitalIn Blue(p21);

DigitalOut led1(LED1);
DigitalOut led2(LED2);

int volatile briLevel = 0;

// RGB Color Select Read Variables
float volatile R_F = 0.0f;
int volatile R = 0;
float volatile G_F = 0.0f;
int volatile G = 0;
float volatile B_F = 0.0f;
int volatile B = 0;


int soft_pot(float softPot)
{
    const float A =  637.5;
    const float B = -191.25;
    float anIn = A*softPot+B;
    int out = floor(anIn);
    if (!(out>=0 && out<256)) {
        out = 0;
    }
    return out;
}

void RGBSel()
{
    if ((Red==1)&&(Green ==1)&&(Blue==1)) {
        R_F = RGB;
        R = soft_pot(R_F);
        G = R;
        B = R;
    } else if ((Red==1)&&(Green ==1)&&(Blue==0)) {
        R_F = RGB;
        R = soft_pot(R_F);
        G = R;
        B = B;
    } else if ((Red==1)&&(Green ==0)&&(Blue==1)) {
        R_F = RGB;
        R = soft_pot(R_F);
        G = G;
        B = R;
    } else if ((Red==0)&&(Green ==1)&&(Blue==1)) {
        G_F = RGB;
        R = R;
        G = soft_pot(G_F);
        B = G;
    } else if ((Red==0)&&(Green ==1)&&(Blue==0)) {
        G_F = RGB;
        R = R;
        G = soft_pot(G_F);
        B = B;
    } else if ((Red==0)&&(Green ==0)&&(Blue==1)) {
        B_F = RGB;
        R = R;
        G = G;
        B = soft_pot(B_F);
    } else if ((Red==1)&&(Green ==0)&&(Blue==0)) {
        R_F = RGB;
        R = soft_pot(R_F);
        G = G;
        B = B;
    }
    else if ((Red==0)&&(Green ==0)&&(Blue==0)) {
        R = R;
        G = G;
        B = B;
    }
}

int storeBright(int b){
   briLevel = soft_pot(brightness);
   if(briLevel > 0) b = (int)(briLevel/255.0*100.0);
   return b;
} 

int main()
{
    int b = 50;
    pi.baud(9600);
    pi.putc('|');
    pi.putc(' ');
    while(1) {
        RGBSel();
        briLevel = storeBright(b);
        b = briLevel;
        eq.readByte();
        for(int j=6; j>-1; j--)
        {
            if(eq.freqDataByte[j]<40) eq.freqDataByte[j]=8;
            eq.freqDataByte[j] = (int)(eq.freqDataByte[j]/8);
            pi.printf("%d", eq.freqDataByte[j]);
            pi.putc(' ');
        }
        pi.putc('^');
        pi.putc(' ');
        char buf[4] = {briLevel, R, G, B};
        for(int i=0; i<4; i++)
        {
            pi.printf("%d", buf[i]);
            pi.putc(' ');
        }
        pi.putc('|');
    }
}

The MSGEQ7 library is necessary to compile this code.

Import libraryMSGEQ7

Library used to interface to the 7-band Graphic Equalizer Chip MSGEQ7, made by company Mixed Signal Integration

Raspberry Pi Code

The following code runs on the Raspberry Pi to receive values from the mbed, parse them, and create output on the LED array. The code was stored in the example programs folder of the downloaded LED array library previously linked. The code also used the wiringPi library which had to be downloaded and necessitated additional changes to the Makefile in the LED array library.

minimal-example.cc

#include "led-matrix.h"
#include <unistd.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>
#include <wiringSerial.h>
#include <wiringPi.h>

using rgb_matrix::GPIO;
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
using namespace rgb_matrix;

volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
	interrupt_received = true;
}

static void draw_bar(Canvas *canvas, int bar, int tw, int w, int h, uint8_t r, uint8_t g, uint8_t b){
	for (int x=tw; x<tw+w; ++x){
		for (int y=0; y<h; ++y){
			canvas->SetPixel(x,y,r,g,b);
		}
	}
}

// parse values read in from mbed
int readIn(int fd){
	int nIn;
	int num = 0;
	int buf = serialGetchar(fd);
	if(buf == 32) buf = serialGetchar(fd);
	while(buf != 32){
		nIn = buf - '0';
		num = (num*10) + nIn;
		buf = serialGetchar(fd);
	}
	return num;
}

int main(int argc, char *argv[]) {
	int fd, n;
	int r=0, g=250, b=250, bright = 50;
	int bar_width[] {5,5,4,4,4,5,5};
	int bar_height [7] = {};
	int t_width = 0;
	int VBRGB[4];
  
	wiringPiSetup();
	
	RGBMatrix::Options defaults;
	rgb_matrix::RuntimeOptions runtime_opt;
	defaults.hardware_mapping = "regular";
	defaults.rows = 16;
	defaults.brightness = 50;
	defaults.chain_length = 1;
	defaults.parallel = 1;
	RGBMatrix *matrix = CreateMatrixFromOptions(defaults, runtime_opt);
	Canvas *canvas = matrix;
	if (canvas == NULL)
		return 1;

	signal(SIGTERM, InterruptHandler);
	signal(SIGINT, InterruptHandler);
  
	// open serial device
	fd = serialOpen("/dev/ttyACM0", 9600);  
	if (fd == -1) {  
		perror("open_port: Unable to open /dev/ttyACM0 - ");
		return(-1);
	}
	sleep(1);

	// set advanced options
	struct termios options; 
	tcgetattr(fd,&options);   		//Get current serial settings in structure
	cfsetspeed(&options, B9600);    
	options.c_cflag &= ~CSTOPB;
	options.c_cflag |= CLOCAL;
	options.c_cflag |= CREAD;
	cfmakeraw(&options);
	tcsetattr(fd,TCSANOW,&options);
	
	while(!interrupt_received){
		n = serialDataAvail(fd);
		if(n>0)
		{
			n = serialGetchar(fd);
			while(n != 124) n=serialGetchar(fd);
			for(int i=0; i<7; i++)
			{
				bar_height[i] = readIn(fd);
				draw_bar(canvas,i,t_width,bar_width[i],bar_height[i],r,g,b);
				t_width += bar_width[i];
			}
			n = serialGetchar(fd);
			while(n != 94) n=serialGetchar(fd);
			for(int k=0; k<4; k++)
			{
				VBRGB[k] = readIn(fd);
			}
		}
		t_width = 0;
		usleep(10000);
		for(int j=0; j<7; j++)
		{
			draw_bar(canvas,j,t_width,bar_width[j],bar_height[j],0,0,0);
			t_width += bar_width[j];
		}
		t_width = 0;
		bright = VBRGB[0]; r = VBRGB[1]; g = VBRGB[2]; b = VBRGB[3];
		matrix->SetBrightness(bright);
	}
  
	canvas->Clear();
	delete canvas;
	
	serialFlush(fd);
	serialClose(fd);

	return 0;
}

WiringPi Library Website

Bluetooth Speaker Control

The following startup script reads .mp3 files off of a flash drive and creates a “playlist” text file which can be cycled through with a series of pushbuttons.

createlist.sh

#! /bin/bash
cd /media/pi/80CB-6949/Music

IFS='
'
types="*\.mp3|*\.mp3"
playlist=(`find -name "*" | egrep -i "$types" | sort`)
length=${#playlist[*]}

if [ "x$1" != "x" ]; then
addtoplaylist=false;
else
addtoplaylist=true;
fi

touch playlist.txt
for (( i=0 ; $i < $length ; i=$i+1 ))
do
line=${playlist[i]}
if [ "x`echo ${playlist[i]} | grep "$1"`" != "x" ]; then
addtoplaylist=true
fi
if [ "x$addtoplaylist" = "xtrue" ]; then
echo ${playlist[i]} >> playlist.txt
fi

done

The following python script allows the user to use pushbuttons to start a song, stop it, pause it, play it, and alter the volume output to the Bluetooth speaker.

buttons.py

from multiprocessing import Process
import sys
import uinput
import RPi.GPIO as GPIO
import time
import os

def volctl():
	GPIO.setmode(GPIO.BCM)
	GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP) #volume up
	GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_UP) #volume down
	
	device = uinput.Device([uinput.KEY_EQUAL,uinput.KEY_MINUS])
	
	try:
		while True:
			input_state_vu = GPIO.input(23)
			input_state_vd = GPIO.input(24)
			
			if input_state_vu == False:	# Increase Volume
				device.emit_click(uinput.KEY_EQUAL)
				time.sleep(0.5)
			if input_state_vd == False:	# Decrease Volume
				device.emit_click(uinput.KEY_MINUS)
				time.sleep(0.5)
	
	except:
		GPIO.cleanup()

def ppstop():
	GPIO.setmode(GPIO.BCM)
	GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP) #pauses/resumes current song
	GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_UP) #stops omxplayer
	
	device = uinput.Device([uinput.KEY_P,uinput.KEY_Q])
	
	try:
		while True:
			input_state_pp = GPIO.input(20)
			input_state_q = GPIO.input(21)
			
			if input_state_pp == False:	# Pause/Play
				device.emit_click(uinput.KEY_P)
				time.sleep(0.5)
			if input_state_q == False:	# Stop
				device.emit_click(uinput.KEY_Q)
				time.sleep(0.5)
	
	except:
		GPIO.cleanup()

def nextsong():
	GPIO.setmode(GPIO.BCM)
	GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_UP)	#next song
	
	#Code to read the playlist.txt file with the available .mp3 files 
	#on the USB
	text_file = open("/media/pi/80CB-6949/Music/playlist.txt", "r")
	
	song_list = []
	lines = text_file.readline()
	while(lines):
		song_list.append(lines)
		lines = text_file.readline()
	
	text_file.close()
	
	#Code for the button push
	i = 0
	try:
		while True:
			# Code block to play the next song in the playlist
			button_stateSkip = GPIO.input(16)
			if button_stateSkip == False:
				song = "/media/pi/80CB-6949/Music/"+song_list[i][2:-1]
				os.system("omxplayer -o alsa:bluespeak %s" %song)
				time.sleep(0.2)
				i += 1
				if i>= len(song_list):
					i = 0
			
	except:
		GPIO.cleanup()
		
f1 = Process(target=ppstop)
f1.start()
f2 = Process(target=nextsong)
f2.start()
f3 = Process(target=volctl)
f3.start()

Results

Color Control Demo:

Brightness Control Demo:

Pushbutton Speaker Control Demo:

Room for improvement

There are significant areas in which this design could be improved. Firstly, using a more powerful single board computer with analog to digital conversion would allow the entire system to be controlled with one board. Additionally, the function of the spectrum analysis could be improved by using a more sensitive microphone with a better frequency response in order to get a more realistic output spectrum. Finally, more software features could be added such as volume control of the speaker itself rather than through changing signal amplitude, improved color profiles of the spectrum for more illustrative output, or inclusion of music streaming services such as Spotify for input.


Please log in to post comments.