Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
A music library for Arduino or ESP boards. Bring back the 8-bit music adding visual feedback as notes or lyrics!
It works with Arduino or ESP boards, I used a Wemos D1 mini.
It plays melodies you can program with notes, rests and other commands - similar to the old BASIC PLAY() command - through a buzzer or small speaker. It can also provide visual feedback with the notes being played or lyrics (user programmable), in this video using 8 8x8 LED matrices.
The playback engine is fairly enhanced: among others, it supports 1, 2, 4, 8, 16, 32, 64, triplet, flat, sharp and dotted notes and rests, octave, tempo and transpose functions, legato, staccato and intermediate note lengths.
SetupCheck the schematics, it is provided for a Wemos D1 mini.
I finally had some time to build a completed player with a 3D printed enclosure. Here is a holiday themed video:
Put both files into the same folder. Check the inline documentation how to use the library.
/*
***************************************************************************
Arduino Melody Player by ericBcreator
loosely based on the old qbasic PLAY command
with lyrics and ESP boards support
***************************************************************************
version 0.95 - last update 20191229 by ericBcreator
***************************************************************************
Features:
***************************************************************************
Syntax:
playMelody(string);
commands:
CDEFGABn notes, n = note length: 1, 2, 4, 8, 16, 32, 64,
2, 4, 8, 16, 32 dotted (or 3, 6, 12, 24, 48),
4, 8, 16, 32 triplet 34, 38, 316, 332
defaults to Ln if n is omitted
Rn rest, n = note length. defaults to Ln if n is omitted
Tn tempo n = 32-255 (default is 120 BPM, 4/4 time base)
On octave n = 1-8 (default is 4)
Ln n = default note length (default is 4, quarter note)
MN music normal: 7/8 note length duration (default)
ML or music legato: full length duration
MS or music staccato: 3/4 note length duration
MU mute mode
< octave down (range 1-8)
> octave up (range 1-8)
[ transpose down a step (range -12 to 12)
] transpose up a step (range -12 to 12)
# sharp (place after note, D#n)
- flat (place after note, D-n)
. dotted note (note length will be increased with 50%)
$ reset the default settings. recommended at the start
of a new song
note: the Tn, On, Ln, M? and [ ] settings are static
so will remain in memory with the next call
to playtune. It is recommended to reset these
at the beginning of a new melody with the $
command
note: spaces are ignored, use them for readability
example the Big Ben tune:
playMelody("$T120 L4 O5 ECD<GR2 G>DEC R2");
or: melody = "$T120 L4 O5 ECD<GR2 G>DEC R2";
playMelody(melody);
lyric support:
The global String 'amp_lyrics' can be used to display
lyrics while playing melodies. The code will display words
and syllables synchronized to the order of the notes, so
word 1 for note 1, word 2 for note 2, etc.
Words can be broken into syllables by adding a - (hyphen).
Words have to be separated with space.
A double space will be treated as an empty word.
Lyrics are not played with rests.
example:
lyrics = "Jin-gle bells Jin-gle bells Jin-gle all the way";
playMelody("$T240 L4 O4 EEE2 EEE2 EGC.D8 E1");
***************************************************************************
Parts:
- Arduino / ESP board (I used a Wemos D1 mini)
- LED matrices
- small speaker
- button or rotary encoder
***************************************************************************
Todo list:
- cleanup
- check @EB-todo
- callback functions
- delay and check loop
- timing accuracy (check)
- add double tempo?
- amp_noteLengthToMS: use array, only calc after tempo change?
***************************************************************************
This code is free for personal use, not for commercial purposes.
Please leave this header intact.
contact: ericBcreator@gmail.com
***************************************************************************
*/
//
// includes
//
#include "amp_notes.h" // include the notes
#include <Adafruit_GFX.h> // Needed for the matrix https://github.com/adafruit/Adafruit-GFX-Library
#include <Max72xxPanel.h> // Matrix library https://github.com/markruys/arduino-Max72xxPanel
#include <RotaryEncoder.h> // Rotary encoder library https://github.com/mathertel/RotaryEncoder
#include <SPI.h>
//
// definitions
//
#define ESP // @EB-setup uncomment when using an ESP
//#define DEBUG // enable debug messages
//#define DEBUG_PLAY_CMD // print play commands
//#define DEBUG_SILENCE
#ifdef DEBUG
#define DEBUGPRINT(x) Serial.print(x)
#define DEBUGPRINTLN(x) Serial.println(x)
#else
#define DEBUGPRINT(x)
#define DEBUGPRINTLN(x)
#endif
#ifdef ESP // ESP pins
#define RE_PINA D1 // @EB-setup rotary encoder: pinA / clock pin number // VCC -> 5V GND -> GND
#define RE_PINB D2 // @EB-setup rotary encoder: pinB / data pin number
#define RE_SWITCH D3 // @EB-setup rotary encoder: switch / pin number
#define PIN_CS D4 // @EB-setup CS pin LED Matrix // VCC -> 5V GND -> GND DIN -> D7 (MOSI) CS -> D4 CLK -> D5 (SCK)
#define AMP_SPEAKER_PIN D8 // @EB-setup the pin of the buzzer of speaker
#else // arduino pins @EB-todo
#define RE_PINA 1 // @EB-setup rotary encoder: pinA / clock pin number // VCC -> 5V GND -> GND
#define RE_PINB 2 // @EB-setup rotary encoder: pinB / data pin number
#define RE_SWITCH 3 // @EB-setup rotary encoder: switch / pin number
#define PIN_CS 10 // @EB-setup CS pin LED Matrix // VCC -> 5V GND -> GND DIN -> 11 (MOSI) CS -> 10 CLK -> 13 (SCK)
#define AMP_SPEAKER_PIN 8 // @EB-setup the pin of the buzzer of speaker
#endif
//
// led matrix setup
//
int numberOfHorizontalDisplays = 8; // @EB-setup
int numberOfVerticalDisplays = 1; // @EB-setup
Max72xxPanel matrix = Max72xxPanel(PIN_CS, numberOfHorizontalDisplays, numberOfVerticalDisplays);
int ledBrightness = 3; // @EB-setup led brightness 0-15
int ledScrollDelayTime = 30; // @EB-setup delay time for scrolling messages
int ledSpacer = 1;
int ledWidth = 5 + ledSpacer; // The font width is 5 pixels
byte ledYOffset = 0;
//
// rotary encoder setup
//
RotaryEncoder RE_encoder(RE_PINA, RE_PINB); // initialize the rotary encoder
//
// set variables
//
byte amp_lyricsMode = 0; // @EB-setup 0: no lyrics, 1: notes, 2: lyrics (or notes if no lyrics are set), 3: lalala ;-)
byte amp_buttonPressed = 0;
String amp_lyrics = "";
String melody = "";
int melodyNum = 0;
bool musicOff = false;
byte loopMode = 0; // @EB-setup 0: loop all songs, 1: loop one song, 2: play one song, don't loop
//
// setup
//
void setup() {
#ifdef DEBUG
Serial.begin(115200);
Serial.println();
#endif
setupMatrix();
pinMode(RE_SWITCH, INPUT_PULLUP);
playMelody("$T120 L16 O4 CEG>C8");
displayMessage("AMP: Arduino Music Player");
amp_buttonPressed = 0;
amp_lyricsMode = 2;
melodyNum = 8;
loopMode = 0;
}
//
// main loop
//
void loop() {
if (!musicOff) {
DEBUGPRINTLN("Melody " + (String) melodyNum);
switch (melodyNum) {
case 0:
displayMessage("Note scale");
playToneScale(4);
break;
case 1:
displayMessage("Firestone");
playTuneFireStone();
break;
case 2:
displayMessage("Sweet but a psycho");
amp_lyrics = "Oh, she's sweet but a psy-cho ";
amp_lyrics += "A lit-tle bit psy-cho ";
amp_lyrics += "At night she's screa-min' ";
amp_lyrics += "I'm-ma-ma out my mi-nd ";
amp_lyrics += "Oh, she's hot but a psy-cho ";
amp_lyrics += "So left but she's right though ";
amp_lyrics += "At night she's screa-min' ";
amp_lyrics += "I'm-ma-ma out my mi-nd";
playMelody("$T133 L8 O4 [ G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED");
break;
case 3:
displayMessage("Shotgun");
amp_lyrics = "I'll be ri-ding ";
amp_lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one I'll be ri-ding ";
amp_lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one";
playMelody("$T116 L8 O4");
melody = "GAGD FRFRGAGD FRFRGAGD FRFRR2 R2";
playMelody(melody);
playMelody(melody);
break;
case 4:
displayMessage("Dragostea din tei");
playMelody("$T130 L8 O4 ]] C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4 C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4");
break;
case 5:
displayMessage("Star wars");
playTuneStarWars();
break;
case 6:
displayMessage("Big Ben");
playMelody("$T120 L4 O4 ML ECD<GR2 G>DECR2");
break;
case 7:
displayMessage("Give it up");
playMelody("$T128 L8 O5");
melody = "E-16-E-E-16E-E-16E-E-16E-E-E- E-4R4CCDD E-4GGF4R A-4A-GFE-FR4";
playMelody(melody);
playMelody(melody);
break;
case 8:
displayMessage("Jingle bells");
playMelody("$T240 L4 O4");
amp_lyrics = "Jin-gle bells Jin-gle bells Jin-gle all the way Oh what fun it is to ride in a one horse o-pen sleigh- ";
amp_lyrics += "Jin-gle bells Jin-gle bells Jin-gle all the way Oh what fun it is to ride in a one horse o-pen sleigh";
melody = "EEE2 EEE2 EGC.D8 E1 FFF.F8 FEEE8E8 EDDE D2G2";
melody += "EEE2 EEE2 EGC.D8 E1 FFF.F8 FEEE8E8 GGFD C1";
playMelody(melody);
break;
case 9:
displayMessage("Firework");
playMelody("$T124 L8 O4");
amp_lyrics = "'Cos ba-by you're a fi-re-work Come on, show them what you're worth Make them go-o Oh Oh Oh! As you shoot a-cross the sky-y-y! ";
amp_lyrics += "Ba-by you're a fi-re-work Come on, let your co-lours burst Make them go-o Oh Oh Oh! You're gon-na leave them go-ing Oh Oh Oh Oh!";
melody = "E>EDC#<B B2.A>C#2R EDC#<B B2.A>C#2R EDC#<B B4RB.R16RB4 R4 EE>EDC#<B B4RB.R16RB4";
melody += "R2>EDC#<B B2.A>C#2R EDC#<B B2.A>C#2R EDC#<B B4RB.R16RB4 R EEE>EDC#<B B4RB.R16RB4.A4";
playMelody(melody);
break;
case 10:
// displayMessage("Timing test 120 BPM");
// playTimingTest();
break;
default:
melodyNum = 0; // 1 gets added in loop so the note scale (melody 0) get skipped. set to -1 to not skip
}
}
amp_lyrics = "";
switch (amp_buttonPressed) {
case 1:
if (loopMode == 0)
melodyNum++;
delay(500);
amp_buttonPressed = 0;
break;
case 2:
musicOff = !musicOff;
displayMessage(musicOff == false ? "Music on" : "Music off");
DEBUGPRINTLN(musicOff == false ? "Music on" : "Music off");
amp_buttonPressed = 0;
break;
default:
delayAndCheckEnc(2000);
if (amp_buttonPressed)
break;
if (loopMode == 0)
melodyNum++;
}
if (loopMode == 2 || musicOff == true) {
DEBUGPRINTLN("Pause loop");
do {
displayMessage("AMP: Arduino Music Player");
if (!amp_buttonPressed)
delayAndCheckEnc(20000);
} while (!amp_buttonPressed);
musicOff = false;
amp_buttonPressed = 0;
}
}
///
/// melodies
///
void playSharpFlat() {
playMelody("$T120 L8 O4");
playMelody("cc#dd#eff#gg#aa#b>c4 r4");
playMelody("c<bb-aa-gg-fee-dd-c4");
}
void playToneScale(byte oneOctave) {
int startOctave = 1;
int endOctave = 8;
if (oneOctave) {
startOctave = oneOctave;
endOctave = oneOctave;
}
playMelody("$T120 L8 O" + (String) startOctave);
for (int i = startOctave; i <= endOctave; i++) {
playMelody("cc#dd#eff#gg#aa#b >");
if (amp_buttonPressed)
break;
}
if (oneOctave)
playMelody("c");
}
void playTimingTest() {
playMelody("$T120 L8 O4");
while (1) {
playMelody("GRCRCRCR");
if (amp_buttonPressed)
break;
}
}
void playTuneStarWars() {
String tune;
tune = "$T105 L8 O4 D38D38D38";
tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
tune += "E4.E>C<BAG G38A38B38A34E38F#4D34D38 E4.E>C<BAG>D.<A16A2D34D38";
tune += "E4.E>C<BAG G38A38B38A34E38F#4>D34D38 G.F16E-.D16C.<B-16A.G16>D2.<D38D38D38";
tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
tune += "G2>D2 C38<B38A38>G2D4 G38F38E-38B-2A4 GR<G38G38G38G4R4";
playMelody(tune);
}
void playTuneFireStone() {
playMelody("$T113 L8 O5 D16F#ED");
String tune = "D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E D.<B.B>D<B>E16F#8. ER16E.DR16D16F#ED D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E D.D.DDDE16F#8. D.D.D16A16D16F#ED16E16D";
for (int i = 0; i < 2; i++) {
playMelody(tune);
if (amp_buttonPressed)
break;
}
}
///
/// local functions
///
String fillSpace(int value, int valLength) {
String tmpStr = String(value);
while (tmpStr.length() < valLength)
tmpStr = " " + tmpStr;
return String(tmpStr);
}
void setupMatrix() {
matrix.setIntensity(ledBrightness);
for (int i = 0; i < numberOfHorizontalDisplays; i++)
matrix.setRotation(i, 1); // Set correct rotation
matrix.fillScreen(LOW);
matrix.write();
}
void displayMessage(String message) {
int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically
int x, letter, reSwitch = 0;
for (int i = 0 ; i < ledWidth * message.length() + matrix.width() - ledSpacer; i++) {
letter = i / ledWidth;
x = (matrix.width() - 1) - i % ledWidth;
while (x + ledWidth - ledSpacer >= 0 && letter >= 0) {
if (letter < message.length()) {
matrix.drawChar(x, y, message[letter], HIGH, LOW, 1);
}
letter--;
x -= ledWidth;
}
matrix.write();
delay(ledScrollDelayTime / 2);
reSwitch = readRotEncSwitch();
if (reSwitch > 0) {
matrix.fillScreen(LOW);
matrix.write();
amp_buttonPressed = reSwitch;
return;
}
}
}
bool delayAndCheckEnc(unsigned int delayTime) {
unsigned long startTime = millis();
unsigned long checkTime = millis();
int reSwitch = 0;
while ((millis() - startTime) < delayTime) {
if (millis() - checkTime > 50) {
checkTime = millis();
reSwitch = readRotEncSwitch();
if (reSwitch > 0) {
amp_buttonPressed = reSwitch;
return true;
}
} else
delay(1);
}
return false;
}
int readRotEncSwitch() { // returns 1 when pressed or 2 when pressed for 1 second
static int reLastSwitchState = 0;
int reSwitchState = digitalRead(RE_SWITCH);
if (reSwitchState == LOW && reLastSwitchState == HIGH) {
unsigned long startTime = millis();
reLastSwitchState = reSwitchState;
while (digitalRead(RE_SWITCH) == LOW) { // wait until the button is released or 1 second has passed
if (millis() - startTime > 1000)
return 2;
}
return 1;
}
reLastSwitchState = reSwitchState;
return false;
}
//////////////////////////////////////////////////////////////////////
/// amp functions
//////////////////////////////////////////////////////////////////////
void playMelody (String melody) {
static byte tempo = 120; // default to 120 BPM
static byte octave = 4; // default octave
static unsigned int defaultNoteLength = 4; // default to quarter note
static unsigned int noteLengthType = 0; // 0: normal 7/8, 1: legato 1/1, 2: staccato 3/4, 9: mute (default 0)
static int transpose = 0; // default to 0
byte notes[] = { 9, 11, 0, 2, 4, 5, 7 };
byte curFunc = 0;
byte curChar = 0;
unsigned int curVal = 0;
bool skip = false;
unsigned int curNoteLength = 0;
int sharpFlat = 0;
bool dotted = false;
int octaveOffset = 0;
int curNote = 0;
double curNoteLengthMS = 0;
String curNoteName;
#ifdef ESP32 // ESP32
ledcAttachPin(AMP_SPEAKER_PIN, 0);
#endif
if (amp_lyricsMode > 0) { // @EB-todo
amp_displayLyrics("$"); // initialize lyrics function
}
for (unsigned int i = 0; i < melody.length(); i++) {
if (amp_buttonPressed > 0)
break;
curNoteLength = 0;
curVal = 0;
skip = true;
octaveOffset = 0;
curChar = melody[i];
if (curChar >= 97 && curChar <= 122) { // convert lowercase to uppercase
curChar -= 32;
}
switch (curChar) {
case 32: // skip spaces
break;
case '$': // restore default settings
tempo = 120;
octave = 4;
defaultNoteLength = 4;
noteLengthType = 0;
transpose = 0;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("Reset settings");
#endif
break;
case '<': // < sign, octave lower
octave--;
if (octave < 1) octave = 1;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("Octave set to " + (String) octave);
#endif
break;
case '>': // > sign, octave higher
octave++;
if (octave > 8) octave = 8;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("Octave set to " + (String) octave);
#endif
break;
case '[': // [ transpose down
transpose--;
if (transpose < -12) transpose = -12;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("Transpose down " + (String) transpose);
#endif
break;
case ']': // ] transpose up
transpose++;
if (transpose > 12) transpose = 12;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("Transpose up " + (String) transpose);
#endif
break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'R': case '#': case '-':
// A B C D E F G notes and R rest and # sharp and - flat
skip = false;
if (i + 1 < melody.length()) {
if (amp_isNum(melody[i + 1])) {
skip = true;
}
if (melody[i + 1] == '#') { // sharp
sharpFlat = 1;
skip = true;
} else if (melody[i + 1] == '-') { // flat
sharpFlat = -1;
skip = true;
} else if (melody[i + 1] == '.') { // dotted
dotted = true;
i++;
}
}
if (curChar != '#' && curChar != '-') {
curFunc = curChar;
}
break;
case 'M': // music length type
if (i + 1 < melody.length()) {
switch (melody[i + 1]) {
case 'N': case 'n':
noteLengthType = 0; // normal 7/8
i++;
break;
case 'L': case 'l':
noteLengthType = 1; // legato 1/1
i++;
break;
case 'S': case 's':
noteLengthType = 2; // staccato 3/4
i++;
break;
case 'U': case 'u':
noteLengthType = 9; // mute
i++;
break;
}
}
case 'L': // L default note/rest length
case 'O': // O octave
case 'T': // T tempo
curFunc = curChar;
break;
default:
if (amp_isNum(curChar)) {
curVal = curChar - 48;
for (int j = 0; j <= 2; j++) { // look ahead to get the next 2 numbers or dot
if (i + 1 < melody.length()) {
if (amp_isNum(melody[i + 1])) {
curVal = curVal * 10 + melody[i + 1] - 48;
i++;
} else if (melody[i + 1] == '.') {
dotted = true;
break;
} else {
break;
}
}
}
curNoteLength= curVal;
skip = false;
}
}
if (curFunc > 0 && !skip) {
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT("Command " + (String) curFunc + " value " + fillSpace(curVal, 3));
#endif
if (curFunc >= 65 and curFunc <= 71 || curFunc == 82) {
if (!curNoteLength) {
curNoteLength = defaultNoteLength;
}
if (dotted) {
curNoteLength = curNoteLength * 1.5;
}
curNoteLengthMS = amp_noteLengthToMS(curNoteLength, tempo);
if (curFunc == 82) {
curNote = 0;
curNoteName = "";
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" Pause length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
#endif
} else {
curNote = notes[curFunc - 65];
curNote = curNote + transpose + sharpFlat;
while (curNote < 0) {
curNote += 12;
if (octave > 1) octaveOffset -= 1;
}
while (curNote > 11) {
curNote -= 12;
if (octave < 8) octaveOffset += 1;
}
curNoteName = amp_noteName(curNote, sharpFlat);
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" Transpose " + (String) transpose + " Octave " + (String) (octave + octaveOffset) + " Note " + curNoteName);
#endif
curNote = ((octave + octaveOffset) * 12) + curNote;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" Notenumber " + fillSpace(curNote, 3) + " Frequency " + fillSpace(pt_notes[curNote], 4) + " length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
#endif
curNoteName += (String) (octave + octaveOffset); // @EB-todo
}
switch (noteLengthType) {
case 0: // normal 7/8
amp_playNote(curNote, (curNoteLengthMS / 8 * 7), curNoteName);
amp_playMelodyLyricsOff();
amp_playNote(0, (curNoteLengthMS / 8 * 1), "");
break;
case 1: // legato 1/1
amp_playNote(curNote, curNoteLengthMS, curNoteName);
amp_playMelodyLyricsOff();
break;
case 2: // staccato
amp_playNote(curNote, (curNoteLengthMS / 4 * 3), curNoteName);
amp_playMelodyLyricsOff();
amp_playNote(0, (curNoteLengthMS / 4 * 1), "");
break;
case 9: // mute
amp_playNote(0, curNoteLengthMS, curNoteName);
amp_playMelodyLyricsOff();
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" MUTE");
#endif
break;
}
dotted = false;
curNoteLength = 0;
sharpFlat = 0;
} else {
switch (curFunc) {
case 'L':
switch (curVal) {
case 1: case 2: case 3: case 4: case 6: case 8: case 12: case 16: case 24: case 32: case 48: case 64:
case 34: case 38: case 316: case 332:
defaultNoteLength = curVal;
break;
}
break;
case 'O':
octave = constrain(curVal, 1, 8);
break;
case 'T':
tempo = constrain(curVal, 32, 255);
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN(" Tempo " + (String) tempo);
#endif
break;
}
}
curFunc = 0;
curNoteName = "";
}
if (!skip) {
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("");
#endif
}
}
amp_playMelodyEnd();
}
bool amp_isNum(byte value) {
if (value >= 48 && value <= 57) // 0 to 9
return true;
else
return false;
}
String amp_noteName(byte note, int sharpFlat) {
switch (note) {
case 0: return "C "; break;
case 1: return (sharpFlat == -1 ? "D-" : "C#"); break;
case 2: return "D "; break;
case 3: return (sharpFlat == -1 ? "E-" : "D#"); break;
case 4: return "E "; break;
case 5: return "F "; break;
case 6: return (sharpFlat == -1 ? "G-" : "F#"); break;
case 7: return "G "; break;
case 8: return (sharpFlat == -1 ? "A-" : "G#"); break;
case 9: return "A "; break;
case 10: return (sharpFlat == -1 ? "B-" : "A#"); break;
case 11: return "B "; break;
}
}
double amp_noteLengthToMS(unsigned int curNoteLength, byte tempo) {
double timeBase = 60000 / tempo; // @EB-todo default 4/4
switch (curNoteLength) {
case 1: return (timeBase * 4); // whole note
case 2: return (timeBase * 2); // half
case 4: return (timeBase); // quarter
case 8: return (timeBase / 2); // 8th
case 16: return (timeBase / 4); // 16th
case 32: return (timeBase / 8); // 32nd
case 64: return (timeBase / 16); // 64th
case 3: return (timeBase * 3); // dotted half
case 6: return (timeBase * 3 / 2); // dotted 4th
case 12: return (timeBase * 3 / 4); // dotted 8th
case 24: return (timeBase * 3 / 8); // dotted 16th
case 48: return (timeBase * 3 / 16); // dotted 32th
case 34: return (timeBase / 1.5); // triplet quarter
case 38: return (timeBase / 3); // triplet 8th
case 316: return (timeBase / 6); // triplet 16th
case 332: return (timeBase / 12); // triplet 32th
}
}
void amp_playNote(byte note, int duration, String curNoteName) {
if (amp_lyricsMode > 0) { // @EB-todo
amp_displayLyrics(curNoteName);
}
#ifndef DEBUG_SILENCE
if (note >= 0 && note <= 107) {
if (pt_notes[note]) {
#ifdef ESP32
ledcWriteTone(0, pt_notes[note]);
#else
tone(AMP_SPEAKER_PIN, pt_notes[note]);
#endif
} else {
#ifdef ESP32
ledcWriteTone(0, 0);
ledcWrite(0, LOW);
#else
noTone(AMP_SPEAKER_PIN);
#endif
}
}
#endif
// delay(duration);
delayAndCheckEnc(duration); // @EB-todo
}
void amp_playMelodyLyricsOff() {
if (amp_lyricsMode > 0) { // @EB-todo
matrix.fillScreen(LOW);
matrix.write();
}
}
void amp_playMelodyEnd() {
amp_playNote(0, 0, "");
pinMode(AMP_SPEAKER_PIN, INPUT); // make sure the buzzer is silent ;-)
}
//////////////////////////////////////////////////////////////////////
/// display lyrics function
//////////////////////////////////////////////////////////////////////
void amp_displayLyrics(String noteName) {
static unsigned int wordPos = 0;
String lyricWord = "";
unsigned int lyricsLength = amp_lyrics.length();
if (noteName.length() == 0) { // skip pauses
return;
}
if (noteName == "$") { // init
wordPos = 0;
return;
}
if (amp_lyricsMode == 3) {
lyricWord = "la";
} else if (amp_lyricsMode == 1 || amp_lyricsMode == 2 && lyricsLength == 0) {
lyricWord = noteName;
} else {
if (wordPos >= lyricsLength)
wordPos = 0;
while (wordPos < lyricsLength) {
if (amp_lyrics[wordPos] == 32) { // break at spaces
wordPos++;
break;
}
lyricWord += amp_lyrics[wordPos];
if (amp_lyrics[wordPos] == '-') { // break at syllables after adding '-'
wordPos++;
break;
}
wordPos++;
}
}
DEBUGPRINT(" lyric: " + lyricWord);
int wordLength = lyricWord.length();
// if (lyricWord[wordLength - 1] == 32)
// wordLength--;
int x = (matrix.width() - (wordLength * (ledWidth + ledSpacer) - 1)) / 2;
int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically
for (int i = 0; i < wordLength; i++) {
matrix.drawChar(x, y, lyricWord[i], HIGH, LOW, 1);
x = x + ledWidth + ledSpacer;
}
matrix.write();
}
//////////////////////////////////////////////////////////////////////
/*
***************************************************************************
Arduino Melody Player by ericBcreator
loosely based on the old qbasic PLAY command
also works on ESP boards
***************************************************************************
version 0.9 - last update 20190610 by ericBcreator
***************************************************************************
Syntax:
playMelody(string);
commands:
CDEFGABn notes, n = note length: 1, 2, 4, 8, 16, 32, 64,
2, 4, 8, 16, 32 dotted (or 3, 6, 12, 24, 48),
4, 8, 16, 32 triplet 34, 38, 316, 332
defaults to Ln if n is omitted
Rn rest, n = note length. defaults to Ln if n is omitted
Tn tempo n = 32-255 (default is 120 BPM, 4/4 time base)
On octave n = 1-8 (default is 4)
Ln n = default note length (default is 4, quarter note)
MN music normal: 7/8 note length duration (default)
ML or music legato: full length duration
MS or music staccato: 3/4 note length duration
MU mute mode
< octave down (range 1-8)
> octave up (range 1-8)
[ transpose down a step (range -12 to 12)
] transpose up a step (range -12 to 12)
# sharp (place after note, D#n)
- flat (place after note, D-n)
. dotted note (note length will be increased with 50%)
$ reset the default settings. recommended at the start
of a song when playing
note: the Tn, On, Ln, M? and [ ] settingsare static
so will remain in memory with the next call
to playtune. It is recommended to reset these
at the beginning of a new melody with the $
command
example the Big Ben tune:
playMelody("$T120 L4 O5 ECD<GR2 G>DEC R2");
or: String tune = "$T120 L4 O5 ECD<GR2 G>DEC R2";
playMelody(tune);
***************************************************************************
This code is free for personal use, not for commercial purposes.
Please leave this header intact.
contact: ericBcreator@gmail.com
***************************************************************************
*/
//
// includes
//
#include "amp_notes.h" // include the notes
#include <Adafruit_GFX.h> // Needed for the matrix https://github.com/adafruit/Adafruit-GFX-Library
#include <Max72xxPanel.h> // Matrix library https://github.com/markruys/arduino-Max72xxPanel
#include <RotaryEncoder.h> // Rotary encoder library https://github.com/mathertel/RotaryEncoder
#include <SPI.h>
//
// definitions
//
#define ESP // @EB-setup uncomment when using an ESP
//#define DEBUG // @EB-setup enable debug messages
//#define DEBUG_PLAY_CMD // @EB-setup print play commands
//#define DEBUG_SILENCE
#ifdef DEBUG
#define DEBUGPRINT(x) Serial.print(x)
#define DEBUGPRINTLN(x) Serial.println(x)
#else
#define DEBUGPRINT(x)
#define DEBUGPRINTLN(x)
#endif
#ifdef ESP // ESP pins
#define RE_PINA D1 // @EB-setup rotary encoder: pinA / clock pin number // VCC -> 5V GND -> GND
#define RE_PINB D2 // @EB-setup rotary encoder: pinB / data pin number
#define RE_SWITCH D3 // @EB-setup rotary encoder: switch / pin number
#define PIN_CS D4 // @EB-setup CS pin LED Matrix // VCC -> 5V GND -> GND DIN -> D7 (MOSI) CS -> D4 CLK -> D5 (SCK)
#define AMP_SPEAKER_PIN D8 // @EB-setup the pin of the buzzer of speaker
#else // arduino pins @EB-todo
#define RE_PINA 1 // @EB-setup rotary encoder: pinA / clock pin number // VCC -> 5V GND -> GND
#define RE_PINB 2 // @EB-setup rotary encoder: pinB / data pin number
#define RE_SWITCH 3 // @EB-setup rotary encoder: switch / pin number
#define PIN_CS 10 // @EB-setup CS pin LED Matrix // VCC -> 5V GND -> GND DIN -> 11 (MOSI) CS -> 10 CLK -> 13 (SCK)
#define AMP_SPEAKER_PIN 8 // @EB-setup the pin of the buzzer of speaker
#endif
//
// led matrix setup
//
int numberOfHorizontalDisplays = 8; // @EB-setup
int numberOfVerticalDisplays = 1; // @EB-setup
Max72xxPanel matrix = Max72xxPanel(PIN_CS, numberOfHorizontalDisplays, numberOfVerticalDisplays);
int ledBrightness = 3; // @EB-setup led brightness 0-15
int ledScrollDelayTime = 30; // @EB-setup delay time for scrolling messages
int ledSpacer = 1;
int ledWidth = 5 + ledSpacer; // The font width is 5 pixels
byte ledYOffset = 0;
//
// rotary encoder setup
//
RotaryEncoder RE_encoder(RE_PINA, RE_PINB); // initialize the rotary encoder
//
// set variables
//
byte amp_lyrics = 0; // @EB-setup 0: no lyrics, 1: notes, 2: lyrics (or notes if no lyrics are set), 3: lalala
byte amp_buttonPressed = 0;
byte melodyNum = 0;
byte numOfMelodies = 7;
bool musicOff = false;
String lyrics;
String melody;
//
// setup
//
void setup() {
#ifdef DEBUG
Serial.begin(115200);
Serial.println();
#endif
setupMatrix();
pinMode(RE_SWITCH, INPUT_PULLUP);
playMelody("$T120 L16 O4 CEG>C8");
displayMessage("AMP: Arduino Music Player");
amp_lyrics = 2;
melodyNum = 0;
}
//
// main loop
//
void loop() {
if (!musicOff) {
DEBUGPRINTLN("Melody " + (String) melodyNum);
switch (melodyNum) {
case 0:
displayMessage("Note scale");
playToneScale(4);
break;
case 1:
displayMessage("Firestone");
playTuneFireStone();
break;
case 2:
displayMessage("Sweet but a psycho");
lyrics = "$Oh, she's sweet but a psy-cho ";
lyrics += "A lit-tle bit psy-cho ";
lyrics += "At night she's screa-min' ";
lyrics += "I'mma ma ma out my mi-nd ";
lyrics += "Oh, she's hot but a psy-cho ";
lyrics += "So left but she's right though ";
lyrics += "At night she's screa-min' ";
lyrics += "I'mma ma ma out my mi-nd";
playMelody("$T133 L8 O4 G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED");
break;
case 3:
displayMessage("Shotgun");
lyrics = "$I'll be ri-ding ";
lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one I'll be ri-ding ";
lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one";
melody = "GAGD FRFRGAGD FRFRGAGD FRFRR2 R2";
playMelody("$T116 L8 O4");
playMelody(melody);
playMelody(melody);
break;
case 4:
displayMessage("Dragostea din tei");
playMelody("$T130 L8 O4 C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4 C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4");
break;
case 5:
displayMessage("Star wars");
playTuneStarWars();
break;
case 6:
displayMessage("Big Ben");
playMelody("$T120 L4 O4 ML ECD<GR2 G>DECR2");
break;
case 7:
displayMessage("Timing test 120 BPM");
playTimingTest();
break;
}
}
lyrics = "";
switch (amp_buttonPressed) {
case 1:
melodyNum++;
if (melodyNum> numOfMelodies)
melodyNum = 0;
delay(500);
amp_buttonPressed = 0;
break;
case 2:
musicOff = !musicOff;
DEBUGPRINTLN(musicOff == false ? "Music on" : "Music off");
amp_buttonPressed = 0;
break;
default:
for (int i = 0; i < 5; i++) {
delayAndCheckEnc(1000);
if (amp_buttonPressed)
break;
}
melodyNum++;
melodyNum = melodyNum % numOfMelodies;
}
}
///
/// local functions
///
void playToneScale(byte oneOctave) {
int startOctave = 1;
int endOctave = 8;
if (oneOctave) {
startOctave = oneOctave;
endOctave = oneOctave;
}
playMelody("T120 L8 O" + (String) startOctave);
for (int i = startOctave; i <= endOctave; i++) {
playMelody("cc#dd#eff#gg#aa#b >");
if (amp_buttonPressed)
break;
}
if (oneOctave)
playMelody("c");
}
void playTimingTest() {
playMelody("$T120 L8 O4");
while (1) {
playMelody("GRCRCRCR");
if (amp_buttonPressed)
break;
}
}
void playTuneStarWars() {
String tune;
tune = "$T105 L8 O4 D38D38D38";
tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
tune += "E4.E>C<BAG G38A38B38A34E38F#4D34D38 E4.E>C<BAG>D.<A16A2D34D38";
tune += "E4.E>C<BAG G38A38B38A34E38F#4>D34D38 G.F16E-.D16C.<B-16A.G16>D2.<D38D38D38";
tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
tune += "G2>D2 C38<B38A38>G2D4 G38F38E-38B-2A4 GR<G38G38G38G4R4";
playMelody(tune);
}
void playTuneFireStone() {
playMelody("$T113 L8 O5 D16F#ED");
String tune = "T113 L8 O5 D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E D.<B.B>D<B>E16F#8. ER16E.DR16D16F#ED D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E D.D.DDDE16F#8. D.D.D16A16D16F#ED16E16D";
for (int i = 0; i < 2; i++) {
playMelody(tune);
if (amp_buttonPressed)
break;
}
}
///////////
String fillSpace(int value, int valLength) {
String tmpStr = String(value);
while (tmpStr.length() < valLength)
tmpStr = " " + tmpStr;
return String(tmpStr);
}
void setupMatrix() {
matrix.setIntensity(ledBrightness);
for (int i = 0; i < numberOfHorizontalDisplays; i++)
matrix.setRotation(i, 1); // Set correct rotation
matrix.fillScreen(LOW);
matrix.write();
}
void displayMessage(String message) {
for (int i = 0 ; i < ledWidth * message.length() + matrix.width() - ledSpacer; i++) {
int letter = i / ledWidth;
int x = (matrix.width() - 1) - i % ledWidth;
int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically
while (x + ledWidth - ledSpacer >= 0 && letter >= 0) {
if (letter < message.length()) {
matrix.drawChar(x, y, message[letter], HIGH, LOW, 1);
}
letter--;
x -= ledWidth;
}
matrix.write();
delay(ledScrollDelayTime / 2);
if (readRotEncSwitch()) {
matrix.fillScreen(LOW);
matrix.write();
return;
}
}
}
void displayLyrics(String noteName) {
static unsigned int wordPos = 0;
String lyricWord = "";
unsigned int lyricsLength = lyrics.length();
if (noteName.length() == 0) { // skip pauses
return;
}
if (amp_lyrics == 3) {
lyricWord = "la";
} else if (amp_lyrics == 1 || amp_lyrics == 2 && lyricsLength == 0) {
lyricWord = noteName;
} else {
if (wordPos >= lyricsLength)
wordPos = 0;
if (lyrics[wordPos] == '$')
wordPos = 1;
else
wordPos++;
while (lyrics[wordPos] != 32 && wordPos < lyricsLength) {
lyricWord += lyrics[wordPos];
if (lyrics[wordPos] == '-') // break at syllables
break;
wordPos++;
}
}
DEBUGPRINT(" lyric: " + lyricWord);
int wordLength = lyricWord.length();
// if (lyricWord[wordLength - 1] == 32)
// wordLength--;
int x = (matrix.width() - (wordLength * (ledWidth + ledSpacer) - 1)) / 2;
int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically
for (int i = 0; i < wordLength; i++) {
matrix.drawChar(x, y, lyricWord[i], HIGH, LOW, 1);
x = x + ledWidth + ledSpacer;
}
matrix.write();
}
bool delayAndCheckEnc(unsigned int delayTime) {
unsigned long startTime = millis();
unsigned long checkTime = millis();
int reSwitch = 0;
while ((millis() - startTime) < delayTime) {
if (millis() - checkTime > 50) {
checkTime = millis();
reSwitch = readRotEncSwitch();
if (reSwitch > 0) {
amp_buttonPressed = reSwitch;
return true;
}
} else
delay(1);
}
return false;
}
int readRotEncSwitch() { // returns 1 when pressed or 2 when pressed for 1 second
static int reLastSwitchState = 0;
int reSwitchState = digitalRead(RE_SWITCH);
if (reSwitchState == LOW && reLastSwitchState == HIGH) {
unsigned long startTime = millis();
reLastSwitchState = reSwitchState;
while (digitalRead(RE_SWITCH) == LOW) { // wait until the button is released or 1 second has passed
if (millis() - startTime > 1000)
return 2;
}
return 1;
}
reLastSwitchState = reSwitchState;
return false;
}
//////////////////////////////////////////////////////////////////////
#ifdef DEBUG_NO_BUZZER
void playBuzzer(int numOfBuzz) {
for (int i = 0; i < numOfBuzz; i++) {
delayAndCheckEnc(100);
}
}
#else
void playBuzzer(int numOfBuzz) {
#ifdef ESP32 // ESP32
ledcAttachPin(AMP_SPEAKER_PIN, 0);
#endif
if (numOfBuzz < 11) {
for (int i = 0; i < numOfBuzz; i++) {
amp_playNote(pt_notes[83], 50, "");
amp_playNote(0, 50, "");
}
} else {
switch (numOfBuzz - 11) { // @EB-todo
case 1: playMelody("T120 L4 O5 ML ECD<GR2 G>DEC R2"); // bigben
}
}
pinMode(AMP_SPEAKER_PIN, INPUT); // make sure the buzzer is silent ;-)
}
#endif
//////////////////////////////////////////////////////////////////////
/// melody player functions
//////////////////////////////////////////////////////////////////////
void playMelody (String melody) {
static byte tempo = 120; // default to 120 BPM
static byte octave = 4; // default octave
static unsigned int defaultNoteLength = 4; // default to quarter note
static unsigned int noteLengthType = 0; // 0: normal 7/8, 1: legato 1/1, 2: staccato 3/4, 9: mute (default 0)
static int transpose = 0; // default to 0
byte notes[] = { 0, 2, 4, 5, 7, 9, 11 };
byte curFunc = 0;
byte curChar = 0;
unsigned int curVal = 0;
bool skip = false;
unsigned int curNoteLength = 0;
byte sharpFlat = 0;
bool dotted = false;
byte curNote = 0;
double curNoteLengthMS = 0;
String curNoteName;
#ifdef ESP32 // ESP32
ledcAttachPin(AMP_SPEAKER_PIN, 0);
#endif
for (unsigned int i = 0; i < melody.length(); i++) {
if (amp_buttonPressed > 0)
break;
curNoteLength = 0;
curVal = 0;
skip = true;
curChar = melody[i];
if (curChar >= 97 && curChar <= 122) { // convert lowercase to uppercase
curChar -= 32;
}
switch (curChar) {
case 32: // skip spaces
break;
case '$': // restore default settings
tempo = 120;
octave = 4;
defaultNoteLength = 4;
noteLengthType = 0;
transpose = 0;
break;
case '<': // < sign, octave lower
octave--;
if (octave < 1) octave = 1;
break;
case '>': // > sign, octave higher
octave++;
if (octave > 8) octave = 8;
break;
case '[': // [ transpose down
transpose--;
if (transpose < -12) transpose = -12;
break;
case ']': // ] transpose up
transpose++;
if (transpose > 12) transpose = 12;
break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'R': case '#': case '-':
// A B C D E F G notes and R rest and # sharp and - flat
skip = false;
if (i + 1 < melody.length()) {
if (amp_isNum(melody[i + 1])) {
skip = true;
}
if (melody[i + 1] == '#') { // sharp
sharpFlat = 1;
skip = true;
} else if (melody[i + 1] == '-') { // flat
sharpFlat = -1;
skip = true;
} else if (melody[i + 1] == '.') { // dotted
dotted = true;
i++;
}
}
if (curChar != '#' && curChar != '-') {
curFunc = curChar;
}
break;
case 'M': // music length type
if (i + 1 < melody.length()) {
switch (melody[i + 1]) {
case 'N': case 'n':
noteLengthType = 0; // normal 7/8
i++;
break;
case 'L': case 'l':
noteLengthType = 1; // legato 1/1
i++;
break;
case 'S': case 's':
noteLengthType = 2; // staccato 3/4
i++;
break;
case 'U': case 'u':
noteLengthType = 9; // mute
i++;
break;
}
}
case 'L': // L default note/rest length
case 'O': // O octave
case 'T': // T tempo
curFunc = curChar;
break;
default:
if (amp_isNum(curChar)) {
curVal = curChar - 48;
for (int j = 0; j <= 2; j++) { // look ahead to get the next 2 numbers or dot
if (i + 1 < melody.length()) {
if (amp_isNum(melody[i + 1])) {
curVal = curVal * 10 + melody[i + 1] - 48;
i++;
} else if (melody[i + 1] == '.') {
dotted = true;
break;
} else {
break;
}
}
}
curNoteLength= curVal;
skip = false;
}
}
if (curFunc > 0 && !skip) {
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT("Command " + (String) curFunc + " value " + fillSpace(curVal, 3));
#endif
if (curFunc >= 65 and curFunc <= 71 || curFunc == 82) {
if (!curNoteLength) {
curNoteLength = defaultNoteLength;
}
if (dotted) {
curNoteLength = curNoteLength * 1.5;
}
curNoteLengthMS = amp_noteLengthToMS(curNoteLength, tempo);
if (curFunc == 82) {
curNote = 0;
curNoteName = "";
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" Pause length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
#endif
} else {
if (curFunc <= 66) {
curNote = notes[curFunc - 60];
} else {
curNote = notes[curFunc - 67];
}
curNote = curNote + transpose + sharpFlat;
curNoteName = amp_noteName(curNote);
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" Octave " + (String) octave + " Note " + curNoteName);
#endif
curNote = (octave * 12) + curNote;
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" Notenumber " + fillSpace(curNote, 3) + " Frequency " + fillSpace(pt_notes[curNote], 4) + " length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
#endif
}
switch (noteLengthType) {
case 0: // normal 7/8
amp_playNote(curNote, (curNoteLengthMS / 8 * 7), curNoteName);
amp_playNote(0, (curNoteLengthMS / 8 * 1), "");
break;
case 1: // legato 1/1
amp_playNote(curNote, curNoteLengthMS, curNoteName);
break;
case 2: // staccato
amp_playNote(curNote, (curNoteLengthMS / 4 * 3), curNoteName);
amp_playNote(0, (curNoteLengthMS / 4 * 1), "");
break;
case 9: // mute
amp_playNote(0, curNoteLengthMS, curNoteName);
#ifdef DEBUG_PLAY_CMD
DEBUGPRINT(" MUTE");
#endif
break;
}
dotted = false;
curNoteLength = 0;
sharpFlat = 0;
} else {
switch (curFunc) {
case 'L':
switch (curVal) {
case 1: case 2: case 3: case 4: case 6: case 8: case 12: case 16: case 24: case 32: case 48: case 64:
case 34: case 38: case 316: case 332:
defaultNoteLength = curVal;
break;
}
break;
case 'O': octave = constrain(curVal, 1, 8); break;
case 'T':
tempo = constrain(curVal, 32, 255);
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN(" Tempo " + (String) tempo);
#endif
break;
}
}
if (amp_lyrics > 0) {
matrix.fillScreen(LOW);
matrix.write();
}
curFunc = 0;
curNoteName = "";
}
if (!skip) {
#ifdef DEBUG_PLAY_CMD
DEBUGPRINTLN("");
#endif
}
}
playTuneEnd();
}
bool amp_isNum(byte value) {
if (value >= 48 && value <= 57) // 0 to 9
return true;
else
return false;
}
String amp_noteName(byte note) {
switch (note) {
case 0: return "C "; break;
case 1: return "C#"; break;
case 2: return "D "; break;
case 3: return "D#"; break;
case 4: return "E "; break;
case 5: return "F "; break;
case 6: return "F#"; break;
case 7: return "G "; break;
case 8: return "G#"; break;
case 9: return "A "; break;
case 10: return "A#"; break;
case 11: return "B "; break;
}
}
double amp_noteLengthToMS(unsigned int curNoteLength, byte tempo) {
double timeBase = 60000 / tempo; // @EB-todo default 4/4
switch (curNoteLength) {
case 1: return (timeBase * 4); // whole note
case 2: return (timeBase * 2); // half
case 4: return (timeBase); // quarter
case 8: return (timeBase / 2); // 8th
case 16: return (timeBase / 4); // 16th
case 32: return (timeBase / 8); // 32nd
case 64: return (timeBase / 16); // 64th
case 3: return (timeBase * 3); // dotted half
case 6: return (timeBase * 3 / 2); // dotted 4th
case 12: return (timeBase * 3 / 4); // dotted 8th
case 24: return (timeBase * 3 / 8); // dotted 16th
case 48: return (timeBase * 3 / 16); // dotted 32th
case 34: return (timeBase / 1.5); // triplet quarter
case 38: return (timeBase / 3); // triplet 8th
case 316: return (timeBase / 6); // triplet 16th
case 332: return (timeBase / 12); // triplet 32th
}
}
void amp_playNote(byte note, int duration, String curNoteName) {
if (amp_lyrics > 0) { // @EB-todo
displayLyrics(curNoteName);
}
#ifndef DEBUG_SILENCE
if (note >= 0 && note <= 107) {
if (pt_notes[note]) {
#ifdef ESP32
ledcWriteTone(0, pt_notes[note]);
#else
tone(AMP_SPEAKER_PIN, pt_notes[note]);
#endif
} else {
#ifdef ESP32
ledcWriteTone(0, 0);
ledcWrite(0, LOW);
#else
noTone(AMP_SPEAKER_PIN);
#endif
}
}
#endif
// delay(duration);
delayAndCheckEnc(duration); // @EB-todo
}
void playTune_lyricsOff() {
if (amp_lyrics > 0) { // @EB-todo
matrix.fillScreen(LOW);
matrix.write();
}
}
void playTuneEnd() {
amp_playNote(0, 0, "");
pinMode(AMP_SPEAKER_PIN, INPUT); // make sure the buzzer is silent ;-)
}
/*************************************************
* Note frequency array
*************************************************/
unsigned int pt_notes[] = {
0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0, // 0- 11 undefined
31, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, // 12- 23 C1 C#1 D1 D#1 E1 F1 F#1 G1 G#1 A1 A#1 B1
65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, // 24- 35 C2 to B2
131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, // 36- 47 C3 to B3
262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, // 48- 59 C4 to B4 (middle C)
523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, // 60- 71 C5 to B5
1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, // 72- 83 C6 to B6
2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, // 84- 95 C7 to B7
4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902 // 96-107 C8 to B8
};
Comments