String Tuner

By Scott Landry, Dalton Clark, and Young Chang

Overview

The mbed-based guitar/string tuner has two separate modes: a "Tune by ear" mode that outputs a tone to the on-board speaker, and a "Tune by response" mode that shows the user how close a note played is to a selected set of notes

Tune By Ear

This mode will generate a note selected by the user using the joystick to select the note. The note is played by pressing the fire button, and the user can cycle between notes using the up and down controls

Tune By Response

The "Tune by response" option utilizes a microphone attached to a computer to determine how close the current tone or sound is to one of the six notes in standard tuning. The input from the microphone is picked up by a C# application, and a Fast Fourier Transform is applied to the input to determine the frequency of the note that is being played by the user. The frequency is sent across a serial connection to the mbed, and then a graph is displayed in real-time that shows how close the frequency sent across serial is to a particular note (the note displayed is the closest note frequency to the received frequency). The graph will display a blue bar that is either over or under a white line on the uLCD display. This corresponds to the note being either sharper or flatter than the particular note.

Components

A joystick is used to navigate the menus for the program, while the tone selected by the user to play in the "Tune by ear" option is sent on a PwmOut pin to the Class-D amp and on-board speaker. The display for the string tuner uses the uLCD screen. The audio input for the C# component uses a standard computer microphone (for testing purposes, a microphone from a USB webcam was used).

Pinout

Navigation stickmbed
Rp5
Dp6
Lp7
Cp8
Up9
-gnd
uLCD cablembed
Resp29
Gndgnd
Txp27
Rxp26
+5Vexternal (6V) power source
mbedClass D AmpSpeaker
p21IN +
gndIN -, PWR-
External (6V) power sourcePWR +
OUT ++
OUT --

Code

main.cpp

#include "mbed.h"
#include "rtos.h"
#include "uLCD_4DGL.h"
#include "joyNav.h"
#include "menuDraw.h"
uLCD_4DGL uLCD(p28,p27,p29); // serial tx, serial rx, reset pin;
BusOut myleds(LED1,LED2,LED3,LED4);
Nav_Switch myNav( p9, p6, p7, p5, p8);
PwmOut speaker(p21);
Serial pc(USBTX, USBRX); // tx, rx
char *noteDisp[] = {"E2","A2","D3","G3","B3","E4"};
float hertzOut[] ={82.41,110,146.83,196.00,246.94,329.63};
Mutex ulcdMutex;
Mutex waveBufferMutex;
Mutex micMutex;
Mutex spkrMutex;
Mutex menuStateMutex;
int menuState = 1;
int tuneState = 0;
int noteState = 0;
int optionSelect = 0;
bool initialDraw = false;
bool isPlaying = false;

void tunerDisplay(void const *args) {
    int menuStateTuner;
    //thread that controls the uLCD display
    while(true) {
        menuStateMutex.lock();
        menuStateTuner = menuState;
        menuStateMutex.unlock();
        if(menuState & MENU_INIT) {
            //main menu
            if(!initialDraw) {
                //draw initial state of menu
                ulcdMutex.lock();
                uLCD.cls();
                uLCD.color(WHITE);
                uLCD.locate(2,0);
                uLCD.printf("STRING TUNER");
                uLCD.locate(0,5);
                //Tune by response is pre-selected
                uLCD.filled_rectangle(0,40,127,49,WHITE);
                uLCD.color(BLACK);
                uLCD.textbackground_color(WHITE);
                uLCD.printf("> Tune by response");
                //switch text colors back
                uLCD.color(WHITE);
                uLCD.textbackground_color(BLACK);
                uLCD.locate(0,7);
                uLCD.printf("  Tune by ear");
                ulcdMutex.unlock();
                //set initDraw state to true so it won't draw again
                initialDraw = true;
            }
        }
        if(menuStateTuner & MENU_RESP) {
            //response tuning menu
            if(!initialDraw) {
                //draw white bar and "initial tuning" text
                ulcdMutex.lock();
                uLCD.cls();
                drawBar(&uLCD);
                uLCD.text_string("Standard tuning",3,0,FONT_7X8, WHITE);
                ulcdMutex.unlock();
                initialDraw = true;
            }
        }
        if(menuStateTuner & MENU_EAR) {
            //tune by ear menu
            if(!initialDraw) {
                //draw selected note
                ulcdMutex.lock();
                uLCD.cls();
                drawByEarMenu(&uLCD,noteDisp[(tuneState*6)+noteState]);
                ulcdMutex.unlock();
                initialDraw = true;
            }
        }
    }
}
void navControl(void const *args) {
    //thread that controls the navigation control for the tuner
    int menuStateNav;
    while(true) {
        menuStateMutex.lock();
        menuStateNav = menuState;
        menuStateMutex.unlock();
        if(myNav.left()) {
            //go back to main menu if on any other sub-menu
            if(menuStateNav & MENU_EAR) {
                if(!tuneState) {
                    //turn off speaker
                    spkrMutex.lock();
                    speaker = 0;
                    spkrMutex.unlock();
                    isPlaying = false;
                    menuStateMutex.lock();
                    menuState = MENU_INIT;
                    menuStateMutex.unlock();
                    optionSelect = 0;
                    initialDraw = false;
                }
            }
            if(menuStateNav & MENU_RESP) {
                if(!tuneState) {
                    menuStateMutex.lock();
                    menuState = MENU_INIT;
                    menuStateMutex.unlock();
                    initialDraw = false;
                }
            }
        }
        if(myNav.right()) {
        }
        if(myNav.up()) {
            if(menuStateNav & MENU_INIT) {
                //switch selected item if on main menu
                if(optionSelect) {
                    optionSelect &= 0;
                    ulcdMutex.lock();
                    //draw deselected item
                    uLCD.locate(0,7);
                    uLCD.filled_rectangle(0,(7*8)-1,127,(7*8)+9,BLACK);
                    uLCD.printf("  Tune by ear");
                    //draw selected item
                    uLCD.locate(0,5);
                    uLCD.filled_rectangle(0,40,127,49,WHITE);
                    uLCD.color(BLACK);
                    uLCD.textbackground_color(WHITE);
                    uLCD.printf("> Tune by response");
                    //reset text fg/bg color
                    uLCD.textbackground_color(BLACK);
                    uLCD.color(WHITE);
                    ulcdMutex.unlock();
                }
            }
            if(menuStateNav & MENU_RESP) {
            }
            if(menuStateNav & MENU_EAR) {
                //cycle through notes if on Tune by ear menu
                noteState = (noteState > 0 ? noteState - 1 : 0);
                ulcdMutex.lock();
                //blank previous note
                //draw new note
                redrawNote(&uLCD, noteDisp[(tuneState*6)+noteState]);
                ulcdMutex.unlock();
            }
        }
        if(myNav.down()) {
            if(menuStateNav & MENU_INIT) {
                if(!optionSelect) {
                    optionSelect |= 1;
                    //draw menu selection
                    ulcdMutex.lock();
                    //draw deselected item
                    uLCD.locate(0,5);
                    uLCD.filled_rectangle(0,(5*8)-1,127,(5*8)+9,BLACK);
                    uLCD.printf("  Tune by response");
                    //draw selected item
                    uLCD.locate(0,7);
                    uLCD.filled_rectangle(0,(7*8)-1,127,(7*8)+9,WHITE);
                    uLCD.color(BLACK);
                    uLCD.textbackground_color(WHITE);
                    uLCD.printf("> Tune by ear");
                    //reset text fg/bg color
                    uLCD.textbackground_color(BLACK);
                    uLCD.color(WHITE);                   
                    ulcdMutex.unlock();
                }
            }
            if(menuStateNav & MENU_RESP) {
            }
            if(menuStateNav & MENU_EAR) {
                //cycle through notes
                noteState = (noteState + 1) % 6;
                ulcdMutex.lock();
                //blank previous note
                //draw new note
                redrawNote(&uLCD, noteDisp[(tuneState*6)+noteState]);
                ulcdMutex.unlock();
            }
        }
        if(myNav.fire()) {
            if(menuStateNav & MENU_INIT) {
                //if on main menu, go to next menu
                if(!optionSelect) {
                    //go to "Tune by response" screen
                    menuStateMutex.lock();
                    menuState = MENU_RESP;
                    menuStateMutex.unlock();
                    initialDraw = false;
                } else {
                    //go to "Tune by ear" screen
                    menuStateMutex.lock();
                    menuState = MENU_EAR;
                    menuStateMutex.unlock();
                    initialDraw = false;
                }
                Thread::wait(100);
            }
            if(menuStateNav & MENU_EAR) {
                //play note on speaker if Tune by ear menu
                spkrMutex.lock();
                speaker = (isPlaying ? 0 : 0.001);
                speaker.period(1/hertzOut[(6*tuneState)+noteState]);
                isPlaying = !isPlaying;
                spkrMutex.unlock();
            }
            if(menuStateNav & MENU_RESP) {
            }
        }
        Thread::wait(200);
    }
}
/*void speakerPlay(void const *args) {
    while(true) {
        //speaker.period(1/f) sets output tone
        //speaker = v sets volume output
        //if(menuState & MENU_EAR) {
            spkrMutex.lock();
            speaker = 0.001;
            speaker.period(1.0/440.0);
            Thread::wait(1000);
            //speaker = (isPlaying ? 0.7 : 0);
            spkrMutex.unlock();
        //}
    }
}*/
int main() {
    Thread threadDisp(tunerDisplay); //initial display thread
    Thread threadNav(navControl); //navigation control thread
    //Thread threadspkr(speakerPlay);
    char fBuff[10] = {0};
    float freq = 0;
    int frTblIndex = 0;
    int menuStateMain;
    while(true) {
        menuStateMutex.lock();
        menuStateMain = menuState;
        menuStateMutex.unlock();
        if(menuStateMain & MENU_RESP) {
        pc.gets(fBuff,4); //stores the received frequency to the buffer
        freq = atof(fBuff); //atof converts the buffer into an float
        frTblIndex = getClosestDraw(hertzOut, (float)(freq)); //find closest note to one read in from serial
        if(freq != 0.0f) { //ignore read-in value of 0
        ulcdMutex.lock();
        //display result on uLCD
        interpretInd(&uLCD,frTblIndex, hertzOut, (float)freq, noteDisp);
        ulcdMutex.unlock();
        }   
    }
}
}

menuDraw.cpp

#include "mbed.h"
#include "uLCD_4DGL.h"

void drawByEarMenu(uLCD_4DGL *u, char *initNote) {
(*u).locate(2,1);
(*u).printf("Standard tuning");
(*u).locate(2,2);
(*u).printf("Current note:");
(*u).locate(2,3);
(*u).text_width(4);
(*u).text_height(4);
(*u).printf("%s",initNote);
(*u).text_width(1);
(*u).text_height(1);
//draw play/pause icon at bottom
(*u).triangle(24,118,24,126,32,122,WHITE);
}

void redrawNote(uLCD_4DGL *u, char *note) {
    (*u).locate(2,3);
    (*u).filled_rectangle(16,24,47,55,BLACK);
    (*u).text_width(4);
    (*u).text_height(4);
    (*u).printf("%s",note);
    (*u).text_width(1);
    (*u).text_height(1);    
}
void drawHertz(uLCD_4DGL *u, float hz) {
    (*u).locate(2,10);
    (*u).filled_rectangle(0,8*10,127,(8*10)+7,BLACK);
    (*u).printf("%f",hz*1);
}
void drawBar(uLCD_4DGL *u) {
    (*u).locate(0,9);
    (*u).filled_rectangle(0,8*9,127,(8*9)+7,WHITE);
}
//void drawCloseBars(uLCD_4DGL *u) {
//}
//void drawEmpty(uLCD_4DGL *u) {
//}
int getClosestDraw(float *freqArr, float inFreq) {
    float diff = abs(inFreq - freqArr[0]);
    int index = 0;
    for(int i = 0; i < 6; i++) {
        if(diff > abs(inFreq - freqArr[i])) {
            diff = abs(inFreq - freqArr[i]);
            index = i;
        }
    }
    return index;
}
/*
Bar graph for each note is scaled differently for each note
*/
void interpretInd(uLCD_4DGL *u,int index, float *freqArr, float freq, char **noteNameArr) {
    //since the scales may be different for each note, separate them with logic statements
    float scalePixel = 0;
    char freqS[4] = {0}; //used to debug by displaying frequency on bar
    sprintf(freqS,"%f",freq);
    (*u).filled_rectangle(0,8,127,15,BLACK);
    //display note name
    (*u).text_string(noteNameArr[index],0,1,FONT_7X8,WHITE);
    if(index == 0) {
        //drawing bars for E
        //82 Hz
        if(freq <= 64) {
            //all empty
            (*u).filled_rectangle(0,8*4,127,127,BLACK);
        } else if(freq >= 100) {
            //draw full bars
            (*u).filled_rectangle(0,8*4,127,127,BLUE);
        } else {
            scalePixel = freq - 64;
            scalePixel *= (2.5);
            (*u).filled_rectangle(0,127-(int)(scalePixel),127,127,BLUE);
            (*u).filled_rectangle(0,8*4,127,126 - (int)(scalePixel),BLACK);
        }
    } else if(index == 1) {
        //drawing bars for A
        //110Hz
        if(freq <= 90) {
            (*u).filled_rectangle(0,8*4,127,127,BLACK);
        } else if(freq >= 120) {
            (*u).filled_rectangle(0,8*4,127,127,BLUE);
        } else {
            scalePixel = freq - 90;
            scalePixel *= (2.7);
            (*u).filled_rectangle(0,127-(int)(scalePixel),127,127,BLUE);
            (*u).filled_rectangle(0,8*4,127,126 - (int)(scalePixel),BLACK);
        }
    } else if(index == 2) {
        //drawing bars for D
        //146.83Hz
        //accurate
        if(freq <= 118) {
            (*u).filled_rectangle(0,8*4,127,127,BLACK);
        } else if(freq >= 158) {
            (*u).filled_rectangle(0,8*4,127,127,BLUE);
        } else {
            scalePixel = freq - 118;
            scalePixel *= (1.5);
            (*u).filled_rectangle(0,127-(int)(scalePixel),127,127,BLUE);
            (*u).filled_rectangle(0,8*4,127,126 - (int)(scalePixel),BLACK);
        }
    } else if(index == 3) {
        //drawing bars for G
        //196Hz
        if(freq <= 160) {
            (*u).filled_rectangle(0,8*4,127,127,BLACK);
        } else if(freq >= 216) {
            (*u).filled_rectangle(0,8*4,127,127,BLUE);
        } else {
            scalePixel = freq - 160;
            scalePixel *= (8/4.9);
            (*u).filled_rectangle(0,127-(int)(scalePixel),127,127,BLUE);
            (*u).filled_rectangle(0,8*4,127,126 - (int)(scalePixel),BLACK);
        }
    } else if(index == 4) {
        //drawing bars for B
        //246.94Hz
        if(freq <= 218) {
            (*u).filled_rectangle(0,8*4,127,127,BLACK);
        } else if(freq >= 266) {
            (*u).filled_rectangle(0,8*4,127,127,BLUE);
        } else {
            scalePixel = freq - 218;
            scalePixel *= (8/4.5);
            (*u).filled_rectangle(0,127-(int)(scalePixel),127,127,BLUE);
            (*u).filled_rectangle(0,8*4,127,126 - (int)(scalePixel),BLACK);
        }
    } else {
        //drawing bars for E
        //329.63Hz
        if(freq <= 294) {
            (*u).filled_rectangle(0,8*4,127,127,BLACK);
        } else if(freq >= 349) {
            (*u).filled_rectangle(0,8*4,127,127,BLUE);
        } else {
            scalePixel = freq - 294;
            scalePixel *= (1.4);
            (*u).filled_rectangle(0,127-(int)(scalePixel),127,127,BLUE);
            (*u).filled_rectangle(0,8*4,127,126 - (int)(scalePixel),BLACK);
        }
    }
                (*u).filled_rectangle(0,8*9,127,(8*9)+7,WHITE);
                //(*u).text_string(freqS,3,9,FONT_7X8, RED); //display frequency on white line

}

menuDraw.h

void drawByEarMenu(uLCD_4DGL *u, char *initNote);
void redrawNote(uLCD_4DGL *u, char *note);
void drawHertz(uLCD_4DGL *u, float hz);
void drawBar(uLCD_4DGL *u);
int getClosestDraw(float *freqArr, float inFreq);
void interpretInd(uLCD_4DGL *u,int index, float *freqArr, float freq, char **noteNameArr);

joyNav.cpp

#include "mbed.h"
#include "joyNav.h"
Nav_Switch::Nav_Switch (PinName up,PinName down,PinName left,PinName right,PinName fire):
    _pins(up, down, left, right, fire)
{
    _pins.mode(PullUp); //needed if pullups not on board or a bare nav switch is used - delete otherwise
    wait(0.001); //delays just a bit for pullups to pull inputs high
}
bool Nav_Switch::up()
{
    return !(_pins[0]);
}
bool Nav_Switch::down()
{
    return !(_pins[1]);
}
bool Nav_Switch::left()
{
    return !(_pins[2]);
}
bool Nav_Switch::right()
{
    return !(_pins[3]);
}
bool Nav_Switch::fire()
{
    return !(_pins[4]);
}
int Nav_Switch::read()
{
    return _pins.read();
}
Nav_Switch::operator int ()
{
    return _pins.read();
}

joyNav.h

#include "mbed.h"
#define MENU_INIT 1
#define MENU_RESP 2
#define MENU_EAR 4

class Nav_Switch
{
public:
    Nav_Switch(PinName up,PinName down,PinName left,PinName right,PinName fire);
    int read();
//boolean functions to test each switch
    bool up();
    bool down();
    bool left();
    bool right();
    bool fire();
//automatic read on RHS
    operator int ();
//index to any switch array style
    bool operator[](int index) {
        return _pins[index];
    };
private:
    BusIn _pins;
 
};

Import programguitarTuner

Guitar Tuner project for ECE 4180

The C# code that sends the frequency over serial:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Un4seen.Bass;
using Un4seen.BassWasapi;
using Un4seen.BassAsio;
using System.IO.Ports;

namespace ConsoleApplication1
{
    class Program
    {
        static int Main()
        {
            int[] freqDump = new int[4096];
            int hz;
            Int32 record;
            Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_MONO, IntPtr.Zero);
            int a;
            BASS_DEVICEINFO info = new BASS_DEVICEINFO();
            for (int n = 0; Bass.BASS_RecordGetDeviceInfo(n, info); n++)
            {
                Console.WriteLine(info.ToString());
                Console.WriteLine("{0}", n);
            }
            if(!Bass.BASS_RecordInit(-1)) {
                Console.WriteLine("Error initializing recording device");
            }
            if (!Bass.BASS_RecordSetDevice(0))
            {
                Console.WriteLine("Error setting recording device");
            }
            Bass.BASS_RecordSetInput(0, BASSInput.BASS_INPUT_ON, -1);
            record = Bass.BASS_RecordStart(44100, 1, BASSFlag.BASS_SAMPLE_FLOAT, null, IntPtr.Zero);
            Console.WriteLine(record);
            float[] fft = new float[2048];
            string hertzS;
            SerialPort _serialPort = new SerialPort("COM5");
            _serialPort.Open();
            while (true)
            {
                Bass.BASS_ChannelGetData(record, fft, (int)BASSData.BASS_DATA_FFT4096);
                if (Utils.FFTIndex2Frequency(fft.ToList().IndexOf(fft.Max()), 4096, 44100) > 60 && Utils.FFTIndex2Frequency(fft.ToList().IndexOf(fft.Max()), 4096, 44100) < 350)
                {
                    hz = Utils.FFTIndex2Frequency(fft.ToList().IndexOf(fft.Max()), 4096, 44100);
                    //Console.WriteLine("{0}", fft.ToList().IndexOf(fft.Max()));
                    hertzS = hz.ToString().PadLeft(3, '0');
                    Console.WriteLine("{0} Hz",hertzS);
                    _serialPort.WriteLine(hz.ToString().PadLeft(3, '0'));
                    System.Threading.Thread.Sleep(200);
                }
            }
            _serialPort.Close();
            Console.ReadKey();
            return 0;
        }
    }
}

Image

/media/uploads/slandry8/20160428_144033.jpg Guitar tuner project set up on bread board (not pictured: external microphone and computer used for getting frequency)


Please log in to post comments.