You can consider this a demonstration for a framework.
You have a few things that are accociated to each other.
- button pin
- LED pin
- speaker pin
- note
Associated things fit best in a struct or class. Think e.g. of a phone book with a name, a phone number and possibly address and city.
Your struct could look like this
struct KEY
{
const uint8_t pinButton;
const uint8_t pinLed;
const uint8_t pinSpeaker;
const float tone;
const uint32_t duration;
};
And you can use an array of structs to make a sequence
KEY noteSequence[] =
{
{2, 8, 6, E4, 2000UL}, // button, LED, speaker, note, duration
{3, 9, 7, D4, 500UL},
};
You can extend this with other KEYs. Later you can loop through this array.
To know how many elements there are in any type of array, you can use the below macro (place it at the top of your code)
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
In setup() you can loop through the array to set pins to input or output
void setup()
{
Serial.begin(115200);
// loop through note sequence
for (uint8_t cnt = 0; cnt < NUMELEMENTS(noteSequence); cnt++)
{
// set button pins to input with pullup
pinMode(noteSequence[cnt].pinButton, INPUT_PULLUP);
// set LED pins as output
pinMode(noteSequence[cnt].pinLed, OUTPUT);
// and make sure it is off
digitalWrite(noteSequence[cnt].pinLed, LOW);
}
}
Note that this is not perfect because you initialise pins multiple times. But it is (for now) the easiest, else you need arrays for button pins and LED pins as well.
In loop() you need a few variables
void loop()
{
// index in array of noteSequence
static uint8_t noteToPlay;
// flag to indicate if a nore is playing
static bool isPlaying = false;
// remember time when note started playing
static uint32_t startTime;
...
...
}
static variables are like gloabal variables whose values will be remembered but they are only known inside (in this case) loop(). You can read up on C++ scope.
noteToPlay will be zero when you start
The first step is to indicate which key most be pressed by setting the LED.
// indicate key to be pressed
digitalWrite(noteSequence[noteToPlay].pinLed, HIGH);
Next you check if a note is already playing
// check if a tone is not already playing
if (isPlaying == false)
{
If not, you check if the key for that note is pressed.
// check if the associated key is pressed
if (digitalRead(noteSequence[noteToPlay].pinButton) == LOW)
{
And if it is you will play the note, remember the start time and set the flag that the note is already playing.
// check if the associated key is pressed
if (digitalRead(noteSequence[noteToPlay].pinButton) == LOW)
{
// play the tone
tone(noteSequence[noteToPlay].pinSpeaker, noteSequence[noteToPlay].tone);
// remember the start time
startTime = millis();
// indicate that a note is playing
isPlaying = true;
}
}
In the next step you check if a note is playing and if the duration has lapsed
// if a tone is playing and the duration has lapsed
if (isPlaying == true && millis() - startTime >= noteSequence[noteToPlay].duration)
{
If so, you switch the LED of and check if the key was released
// current LED off
digitalWrite(noteSequence[noteToPlay].pinLed, LOW);
// wait for key to be released
if (digitalRead(noteSequence[noteToPlay].pinButton) == HIGH)
{
And if so, you switch the tone off, indicate that a note is no longer playing and go to the next step in the sequence.
// tone off
noTone(noteSequence[noteToPlay].pinSpeaker);
// indicate no longer playing
isPlaying = false;
// next note in sequence
noteToPlay++;
// limit to number of notes
if (noteToPlay == NUMELEMENTS(noteSequence))
{
noteToPlay = 0;
}
}
}
Full code
/*
buttons 2, 3, 4 ,5
speakers 6, 7, 12, 13
LEDs 8, 9, 10, 11
*/
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))
const float E4 = 324.63;
const float D4 = 293.66;
const float C4 = 261.63;
const float B3 = 246.94;
struct KEY
{
const uint8_t pinButton;
const uint8_t pinLed;
const uint8_t pinSpeaker;
const float tone;
const uint32_t duration;
};
// clang-format off
KEY noteSequence[] =
{
{2, 8, 6, E4, 2000UL}, // button, LED, speaker, note, duration
{3, 9, 7, D4, 500UL},
};
// clang-format on
void setup()
{
Serial.begin(115200);
// loop through note sequence
for (uint8_t cnt = 0; cnt < NUMELEMENTS(noteSequence); cnt++)
{
// set button pins to input with pullup
pinMode(noteSequence[cnt].pinButton, INPUT_PULLUP);
// set LED pins as output
pinMode(noteSequence[cnt].pinLed, OUTPUT);
// and make sure it is off
digitalWrite(noteSequence[cnt].pinLed, LOW);
}
}
void loop()
{
// index in array of noteSequence
static uint8_t noteToPlay;
// flag to indicate if a nore is playing
static bool isPlaying = false;
// remember time when note started playing
static uint32_t startTime;
// indicate key to be pressed
digitalWrite(noteSequence[noteToPlay].pinLed, HIGH);
// check if a tone is not already playing
if (isPlaying == false)
{
// check if the associated key is pressed
if (digitalRead(noteSequence[noteToPlay].pinButton) == LOW)
{
// play the tone
tone(noteSequence[noteToPlay].pinSpeaker, noteSequence[noteToPlay].tone);
// remember the start time
startTime = millis();
// indicate that a note is playing
isPlaying = true;
}
}
// if a tone is playing and the duration has lapsed
if (isPlaying == true && millis() - startTime >= noteSequence[noteToPlay].duration)
{
// current LED off
digitalWrite(noteSequence[noteToPlay].pinLed, LOW);
// wait for key to be released
if (digitalRead(noteSequence[noteToPlay].pinButton) == HIGH)
{
// tone off
noTone(noteSequence[noteToPlay].pinSpeaker);
// indicate no longer playing
isPlaying = false;
// next note in sequence
noteToPlay++;
// limit to number of notes
if (noteToPlay == NUMELEMENTS(noteSequence))
{
noteToPlay = 0;
}
}
}
}
Notes:
- Compiles (Arduino Nano) but is not tested.
- This is meant as a framework that might not fully suite your needs.
If you have questions, ask.