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.
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.
The microphone setup consists of a simple 2 pin condenser microphone with a preamp circuit:
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.
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.
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.
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; }
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.