piano 4 octaves, 8 notes, 3 threads, 2 outputs, no bug, clean code, comments, remove old .h

Dependencies:   TextLCD

Fork of Nucleo_piano_CS435 by karine aknin

main.cpp

Committer:
aknin001
Date:
2018-07-05
Revision:
9:9a36b66869fa
Parent:
8:781b03221397

File content as of revision 9:9a36b66869fa:

#include "mbed.h"
#include "TextLCD.h"
#include <string.h>
#include <math.h>
 
// Define screen
TextLCD lcd(PA_8, PA_9, PC_7, PB_6, PA_7, PA_6, PA_5); // RS, RW, E, D4-D7
 
// Define Bus In for Buttons (Do, Re, Mi, Fa, Sol, La, Si, Do)
BusIn Bus_In(PA_0, PA_1, PA_4, PB_0, PA_10, PB_3, PB_5, PB_4);
 
// Define the PWM speaker
PwmOut speaker(PB_10);
 
// Define Bus In for Octave Button
BusIn Bus_In_Octave(PB_9);
 
// Define Serial
Serial pc(PA_2, PA_3);
   
//Define variables for sound
struct  NoteReference { // struct note reference
    char    name[4]; // note name
    int     mask; // note mask for pressed of not
    double  frequency; // note base frequency (octave 0)
    double  add[4]; // add value for octave balancing (from octave 0 to 3)
    };

// NoteReference tab for 8 notes (1 octave)    
NoteReference   noteReference[8] = {    {"Do", 0x1, 262.0, {0.0, -1.0, -1.5, -3.0}},
                                        {"Re", 0x2, 294.0, {0.0, -1.0, -1.0, -3.0 }},
                                        {"Mi", 0x4, 330.0, {0.0, -1.0, -1.5, -3.0}},
                                        {"Fa", 0x8, 349.0, {0.0, 0.5, -1.0, 2.0}},
                                        {"Sol", 0x10, 392.0, {0.0, 0.0, 0.0, 0.0}},
                                        {"La", 0x20, 440.0, {0.0, 0.0, 0.0, 0.0}},
                                        {"Si", 0x40, 494.0, {0.0, 0.0, -1.0, -1.0}},
                                        {"Do", 0x80, 523.0, {0.0, 0.0, 0.0, 0.0}}
                                    };
volatile int    stateButtons = 0; // State of notes buttons
volatile int    stateButtonOctave = 0; // State of octave button
volatile int    octave = 0; // octave value
 
//Define variable for display
char     bufferOutput[30] = ""; // buffer for notes and octave
 
//Define variables for synchronization
Mutex   lockBufferOutput; // Mutex for BufferOutput variable
Mutex   lockOctave; // Mutex for octave variable

/**
    Function that refresh the value of
    stateButtons depending on data input from
    Bus_In.
**/ 
void    refresh_state_buttons()
{
   stateButtons = Bus_In & Bus_In.mask(); // read the bus and mask out bits not being used
}

/**
    Function that output notes on the buzzer.
**/ 
void        play_music(int notes, double frequency)
{
   speaker.period(1.0 / frequency); // set the period to 1 / frequency of notes
   while (stateButtons == notes) { // while button is pressed
     refresh_state_buttons(); // refresh stateButtons
   }
}

/**
    Function that generate a frequency depending on pressed notes
    and actual octave.
**/ 
double      generate_frequency(double frequency, int actualOctave)
{
    frequency = 0.0; // init frequency to 0
    lockBufferOutput.lock(); // lock lockBufferOutput Mutex for writing new notes on bufferOutput variable
    strcpy(bufferOutput, ""); // init bufferOuput to empty string
    for (int i = 0; i < 8; i++) { // iterate on all element of noteReference tab
        if (!(stateButtons & noteReference[i].mask)) // if note is pressed
        {
            // add note frequency to frequency (base frequency * 2^octave + add)
            frequency += noteReference[i].frequency * pow(2.0, (double)actualOctave) + noteReference[i].add[actualOctave];
            strcat(bufferOutput, noteReference[i].name); // concatenate bufferOutput and new note name
            strcat(bufferOutput, " "); // concatenate with space separator
        }
    }
    lockBufferOutput.unlock(); // unlock bufferOutput at the end of the loop, end of writing
    return (frequency); // return the frequency to play on buzzer
}

/**
    Check if buttons if pressed and play notes
    otherwise set duty cycle to 0.
**/
void        check_buttons(double frequency, int actualOctave)
{
    if (stateButtons == 0xFF) // if nothing pressed
    {
        speaker = 0; // duty cycle equal to 0
        lockBufferOutput.lock(); // lock BufferOutput Mutex for writing on bufferOutput variable
        strcpy(bufferOutput, ""); // set bufferOutput to empty string
        lockBufferOutput.unlock(); // unlock BufferOutput Mutex
    }
    else { // if buttons pressed
        lockOctave.lock(); // lock lockOctave Mutex for reading octave value
        actualOctave = octave; // read octave value
        lockOctave.unlock(); // unlock lockOctave Mutex
        frequency = generate_frequency(frequency, actualOctave); // generate frequency depending on notes and octave
        speaker = 0.5; // set duty cycle to 0.5 for volume
        play_music(stateButtons, frequency); // play notes on buzzer
    }
}

/**
    Thread for playing notes.
    check states of buttons notes and play music.
**/ 
void    run_music()
{
    double  frequency = 0.0; // double for total frequency
    int     actualOctave = 0; // int for actual octave
    
    while (true) // Infinte loop to catch buttons notes events
    {
        refresh_state_buttons(); // refresh state_buttons
        check_buttons(frequency, actualOctave); // check if buttons are pressed and play music
        Thread::wait(100); // Thread wait 100 for scheduling
    }
}

/**
    Function that will display notes and gammes on LCD screen and
    PC device.
    Only called when a change on BufferOutput occurs.
**/
void    refresh_display_notes(char *old_buffer)
{
    lcd.cls(); // clear LCD screen
    lockOctave.lock(); // lock lockOctave Mutex for reading octave variable 
    if (strcmp(bufferOutput, "")) // if buffer not empty
    {
        lcd.printf("%s- O[%d]", bufferOutput, octave); // display notes and actual octave on LCD screen
        pc.printf("Play notes: %s with octave %d\n", bufferOutput, octave); // display notes and actual octave on PC device
    }
    else 
    {
        lcd.printf("Octave = %d", octave); // only display octave on screen if no button pressed
        pc.printf("Release notes\n");  // display release message on PC device
    }
    lockOctave.unlock(); // unlock lockOctave
    strcpy(old_buffer, bufferOutput); // save value bufferOutput in old_buffer
}

/**
    Function that will display the octave on screen
    if a change in octave occurs.
**/
int    refresh_display_octave(int old_octave)
{
    lockOctave.lock(); // lock lockOctave for reading octave variable
    if (old_octave != octave) // if change occurs
    {
        lcd.cls(); // clear screen
        lcd.printf("Octave = %d", octave); // display new octave on LCD screen
        pc.printf("[INFO] - Change octave %d to octave %d\n", old_octave, octave); // display new octave on PC device 
        old_octave = octave; // save the new value to old_octave
    }
    lockOctave.unlock(); // Unlock lockOctave
    return old_octave; // return old_octave
}

/**
    Thread display function that output
    notes and octaves on the LCD screen
    and the PC device by Serial.
**/ 
void    run_display()
{
    char    old_buffer[30] = ""; // buffer of old value BufferOutput variable, init to empty string by default
    int     old_octave = 0; // value for old octave variable, init to zero by default
    
    lockOctave.lock(); // Lock lockOctave Mutex for reading octave variable
    lcd.printf("Octave = %d", octave); // Display default octave on screen until event occurs
    lockOctave.unlock(); // Unlock lockOctave Mutex
    while(true) // Infinite loop to run display
    {
        lockBufferOutput.lock(); // Lock lockBufferOutput Mutex for reading BufferOutput
        if (strcmp(old_buffer, bufferOutput)) // if bufferOutput has change
            refresh_display_notes(old_buffer); // output notes 
        else
            old_octave = refresh_display_octave(old_octave); // else output gamme if change
        lockBufferOutput.unlock(); // Unlock lockBufferOutput Mutex
        Thread::wait(100); // Thread wait 100 for synchronization
    }
}
 
/**
    Function that refresh the value of 
    stateButtonOctave variable based on the
    input data from Bus_In_Octave.
**/
void     check_state_button_octave()
{
    stateButtonOctave = Bus_In_Octave & Bus_In_Octave.mask();
}

/**
    Thread for Octave.
    
    Catch octave button state and increment
    the octave value if button has been pressed.
**/
void    run_octave()
{
    while(true) // Infinite loop for octave Thread
    {
        check_state_button_octave(); // refresh state_button_octave
        if (stateButtonOctave == 0x0) // if button is pressed
        {
            lockOctave.lock(); // lock lockOctave Mutex, enter in critical section
            octave = (octave == 3) ? 0 : octave + 1; // increment octave or set to 0
            lockOctave.unlock(); // unlock lockOctave Mutex, end of critical section
            while (stateButtonOctave == 0x0) // continue check button state until it is unpressed
                check_state_button_octave(); // refresh state_button_octave
        }
        Thread::wait(100); // Thread wait 100 for scheduling
    }
}
 
/**
    Main function Piano Project.
    
    Launch 3 threads for playing music, display notes
    and octaves.
**/
int main()
{
    Thread  music_thread; // Thread for playing music
    Thread  display_thread; // Thread for display notes and octave
    Thread  octave_thread; // Thread for octave
    
    lcd.printf("CS435\nPiano Project"); // Print welcome string on LCD screen
    wait(3);
    lcd.cls(); // Clear the screen
 
    music_thread.start(run_music); // Start music thread
    display_thread.start(run_display); // Start display thread
    octave_thread.start(run_octave); // Start octave thread
    
    while(1); // Infinite loop for main thread
}