Code to detect and choose which speaker is free.

I am working on making an electric piano that can play two notes at once.

I am using this tone library GitHub - bhagman/Tone: A Wiring Library to produce square wave tones on arbitrary pins. because it is designed to allow the playing of more than one tone at a time by a single arduino board, on separate speakers. I have already figured out how to get two tones playing on separate speakers at the same time, but my difficulty is in making this responsive to buttons (keys).

Right now I am trying to code two buttons, where when a button is pressed, it plays a tone on the first speaker that is free. For example, button 1 is pressed and both speakers are free, so a note is played on speaker 1, then button 2 is pressed with button 1 still held down, so button 2 plays a note on speaker 2.
Or vice versa; button 2 is pressed while both speakers are free, so it plays a note on speaker 1, and then button 1 is pressed while button 2 is still held down, so it plays a note on speaker 2. (Sorry if this is confusing!)

I attached my code for this.

From troubleshooting I have learned that:

  • Right now when either button is individually pressed after the code is compiled, it simply plays its note on both speakers, and then both speakers continue playing that note.
    -Having the sound start & stop with the pressing of the button does work when only using 1 tone on 1 speaker with 1 button.
  • My logic for checking when a button is pressed vs not pressed is working correctly; the if statements correctly execute according to the state of the button
  • The code to check the state of each speaker is working correctly as well; when tested in isolation the tone.isPlaying() is correct

My question:

  • If the state of the button and the state of the speaker are correct, could this issue be caused by something related to a race condition in the code? Or any other ideas?

Also, if anyone has ideas for a better way to implement this functionality I am open to them! Thank you so much for reading this & for your help!

Piano_Buttons.ino (2.49 KB)

Compiles, untested. Any luck with it? If it works, does it give you any ideas?

#include <Tone.h>

Tone tone1;
Tone tone2;

const int button1Pin = 2;
const int button2Pin = 6;

int button2State = 0;
int button1State = 0;

const uint16_t
    grNotes[] = 
    {
        NOTE_A3,        //corresponds to button 1
        NOTE_CS4        //and this is button 2
    };
    
uint8_t
    spkrMask;

typedef struct structButtonInfo
{
    uint8_t     pin;            //pin button
    uint8_t     usedMask;       //mask used to re-set speaker bit
    Tone        *pTone;         //pointer to the tone output that will play this button's note
    uint8_t     lastRead;       //last read value
    uint32_t    timeRead;       //used for timing uS between reads
    
}sButtonInfo_t;

sButtonInfo_t
    ButtonInfo[2] = 
    {
        {
            //button 1
            .pin = button1Pin,
            .usedMask = 0,
            .pTone = NULL,
            .lastRead = LOW,
            .timeRead = 0ul
        },
        {
            //button 2
            .pin = button2Pin,
            .usedMask = 0,
            .pTone = NULL,
            .lastRead = LOW,
            .timeRead = 0ul
        }
    };
   

void setup( void )
{
    Serial.begin(9600);
    
    // tone1 plays sound on the speaker connected to Pin 13,
    // tone 2 for the speaker on pin 11.
    tone1.begin(13);
    tone2.begin(11);
    
    // Using INPUT_PULLUP to make configuring the buttons work- this makes their default state
    // HIGH, so to "press" them they are connected to ground, and switch to LOW
    pinMode(button1Pin, INPUT_PULLUP);
    pinMode(button2Pin, INPUT_PULLUP);

    //in spkrMask, bits 0 and 1 are used to indicate speaker status
    //if bit 0 == 1, spkr 1 is free; if bit 0 == 0, spkr 1 is in use
    //if bit 1 == 1, spkr 2 is free; if bit 1 == 0, spkr 2 is in use
    //init to 0x03 to indicate both speakers free
    spkrMask = 0x03;

    //establish the "last" value of the buttons with this initial read
    for( uint8_t i=0; i<2; i++ )
        ButtonInfo[i].lastRead = digitalRead( ButtonInfo[i].pin );
    
}//setup

void loop( void ) 
{
    PlayNotes();
            
}//loop

void PlayNotes( void )
{
    static uint8_t
        index = 0;    
    uint32_t
        timeNow = micros();

    //read switches every 2mS (2000uS)
    if( (timeNow - ButtonInfo[index].timeRead) >= 2000ul )
    {
        //time for a read; save the micros count now for next read
        ButtonInfo[index].timeRead = timeNow;
        //read the switch
        uint8_t nowRead = digitalRead( ButtonInfo[index].pin );
        //if not the same as last time...
        if( nowRead != ButtonInfo[index].lastRead )
        {
            //save new value as last
            ButtonInfo[index].lastRead = nowRead;
            //if low, button has been pressed
            if( nowRead == LOW )
            {
                //see switch speaker is available
                switch( spkrMask )
                {
                    //if speaker mask is 0b11 or 0x01, then both or only #1 is available
                    //in either case, use speaker 1
                    case    0x03:
                    case    0x01:
                        //show that speaker 1 is now occupied by clearing bit 0 in the mask
                        spkrMask &= 0b00000010;
                        //and assign tone1 to this button
                        ButtonInfo[index].pTone = &tone1;
                        //this is the mask we use to reinstate the bit when the note is finished
                        //here we'll re-set bit 0
                        ButtonInfo[index].usedMask = 0b00000001;
                        
                    break;

                    case    0x02:
                        //spkr 1 in use; select spkr 2
                        //same logic as case 3/1 but now spkr 2 is the one we use
                        spkrMask &= 0b00000001; //clear bit 1
                        ButtonInfo[index].pTone = &tone2;   //assign tone2
                        ButtonInfo[index].usedMask = 0b00000010;    //re-set mask is for bit 1
                        
                    break;

                    default:
                        //invalid or no spkr available
                        //no tone is given and no bits are set when button released
                        ButtonInfo[index].pTone = NULL;
                        ButtonInfo[index].usedMask = 0b00000000;
                        
                    break;
                    
                }//switch

                //if a tone pin was assigned...
                if( ButtonInfo[index].pTone )
                    //play the note for this button at the tone output assigned above
                    ButtonInfo[index].pTone->play( grNotes[index] );
                
            }//if
            else
            {
                //here is key was just released
                //if we're playing a note...
                if( ButtonInfo[index].pTone )
                    //stop it now
                    ButtonInfo[index].pTone->stop();                

                //re-set the speaker mask bit (if used; if none was available, 0x00 is ORd in and nothing changes)
                spkrMask |= ButtonInfo[index].usedMask;
                //clear the re-set mask for this button
                ButtonInfo[index].usedMask = 0;
                
            }//else
            
        }//if
        
    }//if

    //each pass we look at one button.
    //bump button index each pass
    index++;
    if( index == 2 )
        index = 0;
    
}//PlayNotes

This code worked exactly right on the first try. There are truly not enough people in the world like you. Thank you so much!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.