Skip to content

Instantly share code, notes, and snippets.

@owennewo
Created September 4, 2020 15:13
Show Gist options
  • Save owennewo/150fc6f9385c43a8ab7a750b20da8952 to your computer and use it in GitHub Desktop.
Save owennewo/150fc6f9385c43a8ab7a750b20da8952 to your computer and use it in GitHub Desktop.
SimpleFOC Music Player
#include <Arduino.h>
#include <SimpleFOC.h>
/*
This works best on an esp32 but have tested on atmega328p too
Be careful to set voltage appropriately. Might want to turn down power_supply to 6v too.
*/
#ifdef ESP32
BLDCMotor motor = BLDCMotor(5, 18, 19, 7, 4);
#else
BLDCMotor motor = BLDCMotor(9, 10, 11, 7);
// BLDCMotor motor = BLDCMotor(3, 5, 6, 7);
#endif
float tone_voltage = 1.0; // as this openloop we need to give another voltage to achieve amplitude/vibration reliably
float tone_amplitude = 0.25 * PI; // controls volume (too high it'll be distorted)
float octave_offset = 0; // increasing/decreasing offset can sometimes sound louder/clearer
// calculates note duration in milliseconds
long noteDuration(int bpm, int tone_duration, boolean dotted) {
long durationMillis = (60 * 1000L / bpm) * (4.0/tone_duration);
if (dotted) {
durationMillis += durationMillis/2;
}
return durationMillis;
}
// calculates frequency of tone in Hz
float noteFrequency(char tone_note, int tone_octave, boolean tone_sharp) {
int twelth_index = 0;
switch(tone_note)
{
case 'c':
twelth_index = 1;
break;
case 'd':
twelth_index = 3;
break;
case 'e':
twelth_index = 5;
break;
case 'f':
twelth_index = 6;
break;
case 'g':
twelth_index = 8;
break;
case 'a':
twelth_index = 10;
break;
case 'b':
twelth_index = 12;
break;
case 'p':
default:
return 0; // a pause, no frequency
}
if (tone_sharp) twelth_index++;
int note_index = tone_octave * 12 + twelth_index;
int a4_index = 58;
// formula from https://pages.mtu.edu/~suits/NoteFreqCalcs.html
float freq = 440 * pow(1.059463094359, note_index - a4_index);
return freq;
}
// plays a tone of given frequency, duration and amplitude (volume)
void playTone(float freq, long duration_msec, float amplitude, float voltage)
{
// float period_usec = 1000000.0 / freq;
float period_msec = 1000.0 / freq;
// long start = _micros();
long start = millis();
long diff = 0;
long count = 0;
while (diff < duration_msec)
{
long now = millis();
diff = now - start;
float angle = sin((float) diff/period_msec) * amplitude;
motor.setPhaseVoltage(voltage, angle);
count ++;
}
motor.setPhaseVoltage(0, angle);
}
// plays a nokia rtttl ringtone - spec: http://merwin.bespin.org/t4a/specs/nokia_rtttl.txt
// example format of rttl is: rttl:"Simpsons:d=4,o=5,b=160:32p,c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g";
void playRTTTL(char* rtttl, float amplitude, float voltage, int octave_offset) {
// defaults
int octave = 6;
int bpm = 63;
int duration = 4;
// read three main sections which are separated by colons
char* name = strtok(rtttl, ":");
char* controls = strtok(NULL, ":");
char* tones = strtok(NULL, ":");
Serial.print(F("Playing: "));Serial.println(name);
// read and iterate through control tokens. There are a maximum of 3 (default bpm, duration, octave)
char *control_token = strtok(controls,",");
while (control_token)
{
// avoid nested use of strtok
char *save = NULL;
char* control_key = strtok_r(control_token,"=", &save);
int control_value = atoi(strtok_r(NULL,"=", &save));
switch (control_key[0]) {
case 'o':
octave = control_value;
break;
case 'd':
duration = control_value;
break;
case 'b':
bpm = control_value;
break;
}
control_token = strtok(NULL,",");
}
// read and iterate through the notes
char *tone_token = strtok(tones,",");
while (tone_token)
{
boolean tone_dotted = false;
boolean tone_sharp = false;
char tone_note = 0;
int tone_duration = 0;
int tone_octave = octave;
int size = strlen(tone_token);
for (int index = 0; index < size; index++) {
char current = tone_token[index];
if (current == '.') {
tone_dotted = true;
} else {
if (isDigit(current)) {
// either a duration or an octave depending on position
int value = atoi(&current);
if (tone_note == 0) {
tone_duration = 10 * tone_duration + value;
} else {
tone_octave = value;
}
} else if (current=='#') {
tone_sharp = true;
} else {
tone_note = current;
}
}
}
if (tone_duration == 0) tone_duration = duration;
float tone_freq = noteFrequency(tone_note, tone_octave + octave_offset, tone_sharp);
long tone_millis = noteDuration(bpm, tone_duration, tone_dotted);
Serial.print(F(" ")); Serial.print(tone_note); Serial.print(tone_octave + octave_offset);
// Lets play that note!
playTone(tone_freq, tone_millis, amplitude,voltage);
tone_token = strtok(NULL,",");
}
Serial.println();
Serial.println(F("Complete."));
delay(1000);
}
// This is reads instructions from serial in. It is not rtttl. Example format: '6cdec cdec efgp efgp +gagf-ec +gagf-ec c5g6cp c5g6-c' for Frere Jaque
void playNotesFromSerial() {
static int octave = 6;
static int duration = 400;
// a string to hold incoming data
static String received_chars;
while (Serial.available()) {
char inChar = (char)Serial.read();
if (isDigit(inChar)) {
octave = atoi(&inChar);
Serial.print(F("Octave is now: ")); Serial.println(octave);
} else if (isWhitespace(inChar)) {
// ignore
} else if (inChar == '-') {
Serial.println(F("Half Speed"));
duration *=2; // slow it down
} else if (inChar == '+') {
Serial.println(F("Double Speed"));
duration /=2; // speed it up
} else if (isAlpha(inChar)) {
char note = inChar;
float freq = noteFrequency(note, octave + octave_offset, false);
Serial.print(F("Playing: ")); Serial.print(note); Serial.println(octave);
playTone(freq, duration, tone_amplitude, tone_voltage);
} else {
Serial.println(F("bad character, try pasting with mouse not keyboard!"));
}
}
}
// these tunes are in rtttl (ringtone) format. Declared here for limited RAM
#define CROATIA "croatia:d=64,o=6,b=160:4f#,p,4f#,p,4f#.,p,8e,p,8e,p,8d,p,4d,p,2a5,p,8g5,p,8f#5,p,8g5,p,8a5,p,2b5,p,8a5,p,8g5,p,8f#5,p,8g5,p,2a5,p,4f#,p,4f#,p,4f#.,p,8e,p,8e,p,8d,p,4d,p,2a5,p,8g5,p,8f#5,p,8g5,p,8a5,p,2b5,p,8c#,p,8c#,p,4e,p,2d,p,4c#,p,4c#,p,4c#.,p,8b5,p,4c#,p,8c#,p,8d,p,4e.,p,8c#,p,8e,p,8e,p,8e,p,8e,p,4e,p,4d,p,4c#,p,4b5,p,2a5,p,4f#,p,4f#,p,4f#.,p,8e,p,8e,p,8d,p,4d,p,2a5,p,8g5,p,8f#5,p,8g5,p,8a5,2b5,p,8c#,p,p,8c#,p,4e,p,2d,2d,2p"
#define MARSEILLAI "marseillai:d=4,o=5,b=160:2p.,16d,8d.,16d,g,g,a,a,d.6,8b,8g,16p,16g,8b.,16g,e,2c6,8a.,16f#,2g,p,8g.,16a,b,b,b,8c.6,16b,b,a,p,8a.,16b,c6,c6,c6,8d.6,16c6,2b,p,8d.6,16d6,d6,8b.,16g,d6,8b.,16g,2d,8p.,16d,8d.,16f#,2a,c6,8a.,16f#,a,g,2f,e,8g.,16g,g,8f#.,16g,2a.,8p,8a,a#.,8a#,8a#,8a#,8c6,8d6,2a.,8a#,8a,g.,8g,8g,8a#,8a,8g,g,8f#,2p,16d6,2d.6,16d6,8b.,16g,2a.,8p.,16d6,2d.6,16d6,8b.,16g,2a,8p,d,2g,p,a,2b,2p,2c6,d6,e6,2a,8p,e6,2d.6,16b,8c.6,16a,2g."
#define SIMPSONS "Simpsons:d=4,o=5,b=160:32p,c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g"
void setup() {
Serial.begin(115200);
while(!Serial) {;}
delay(2000);
Serial.print(F("#########################\n Simple FOC Music Player \n#########################\n"));
#ifdef ESP32
Serial.println(F("\nYou have a 12 bit DAC. Good choice! Prepare for the Highest Fidelity BLDC has to offer!\n"));
octave_offset += 1;
#else
Serial.println(F("\nYou have a 8 bits DAC. Old school and retro! I won't be able to play high notes, sorry!\n"));
octave_offset -= 1;
#endif
motor.voltage_power_supply = 12;
motor.foc_modulation = FOCModulationType::SinePWM;
motor.init();
// This directive if/else is necessary because of limited space on atmega328p. otherwise 328p SRAM unstable
#ifdef ESP32
char croatia[] = CROATIA;
char marseillai[] = MARSEILLAI;
char simpsons[] = SIMPSONS;
playRTTTL(croatia, tone_amplitude, tone_voltage, octave_offset);
playRTTTL(marseillai, tone_amplitude, tone_voltage, octave_offset);
playRTTTL(simpsons, tone_amplitude, tone_voltage, octave_offset);
#else
playRTTTL(CROATIA, tone_amplitude, tone_voltage, octave_offset);
playRTTTL(MARSEILLAI, tone_amplitude, tone_voltage, octave_offset);
playRTTTL(SIMPSONS, tone_amplitude, tone_voltage, octave_offset);
#endif
Serial.print(F("\nYour turn!!\nType/paste: '6cdec cdec efgp efgp +gagf-ec +gagf-ec c5g6cp c5g6-c' for Frere Jaque (this is not rtttl format)\n"));
}
void loop() {
playNotesFromSerial();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment