// Bad Apple RESPECT!
// include necessary standard library
#include <string>
#include <vector>
#include <iostream>
using namespace std;
// include SD card library
#include "SD.h"
// include libraries for threads
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
// Define PIN
#define PIN_11_C 15
#define PIN_21_C 2
#define PIN_31_C 4
#define PIN_41_C 21
#define PIN_51_C 22
#define PIN_61_C 16 // RX2
#define PIN_11_A 13
#define PIN_12_A 12
#define PIN_13_A 14
#define PIN_14_A 27
#define PIN_15_A 26
#define PIN_16_A 25
#define PIN_17_A 33
#define PIN_18_A 32
// Define SD card connection
#define SD_MOSI_PIN 20
#define SD_MISO_PIN 19
#define SD_SCLK_PIN 18
#define SD_CS_PIN 5
// Define BUZZER connection
#define BUZZER_PIN 17 // TX2
// Define map data
#define WIDTH 8
#define HEIGHT 6
#define FPS 24
#define DELAY_MS 1
#define REVISION_MS 4.02
#define PIC_NUM 4391
// Define music data
#define BEAT_FREQUENCY 50
#define QUARTER_NOTE_DURATION 433.86 // ms // (bad apple BPM = 138)
#define SHUT_DURATION 200
#define SHEET_LENGTH 917
string tone_hz_arr_C0[61][2] = {
// {"(簡譜碼)(升降符)"}
{"00","0"},
{"..10","65.4064"},
{"..12","69.2957"},
{"..20","73.4162"},
{"..22","77.7817"},
{"..30","82.4069"},
{"..40","87.3071"},
{"..42","92.4986"},
{"..50","97.9989"},
{"..52","103.826"},
{"..60","110.000"},
{"..62","116.541"},
{"..70","123.471"},
{".10","130.813"},
{".12","138.591"},
{".20","146.832"},
{".22","155.563"},
{".30","164.814"},
{".40","174.614"},
{".42","184.997"},
{".50","195.998"},
{".52","207.65"},
{".60","220.000"},
{".62","233.082"},
{".70","246.942"},
{"10","261.626"},
{"12","277.183"},
{"20","293.665"},
{"22","311.127"},
{"30","329.628"},
{"40","349.228"},
{"42","369.994"},
{"50","391.995"},
{"52","415.305"},
{"60","440.000"},
{"62","466.164"},
{"70","493.883"},
{"1.0","523.251"},
{"1.2","554.365"},
{"2.0","587.330"},
{"2.2","622.254"},
{"3.0","659.255"},
{"4.0","698.456"},
{"4.2","739.989"},
{"5.0","783.991"},
{"5.2","830.609"},
{"6.0","880.000"},
{"6.2","932.328"},
{"7.0","987.767"},
{"1..0","1046.50"},
{"1..2","1108.73"},
{"2..0","1174.66"},
{"2..2","1244.51"},
{"3..0","1318.51"},
{"4..0","1396.91"},
{"4..2","1479.98"},
{"5..0","1567.98"},
{"5..2","1661.22"},
{"6..0","1760.00"},
{"6..2","1864.66"},
{"7..0","1975.53"}
};
string* key_to_C0[61]; // example: {{"10", "42"},...} for F♯大調
// pin array
const int allPin[] = {
PIN_11_C, PIN_21_C, PIN_31_C, PIN_41_C, PIN_51_C, PIN_61_C, PIN_11_A, PIN_12_A, PIN_13_A, PIN_14_A, PIN_15_A, PIN_16_A, PIN_17_A, PIN_18_A,
BUZZER_PIN, SD_MOSI_PIN, SD_MISO_PIN, SD_SCLK_PIN, SD_CS_PIN
};
const int pinLineHeadArr[] = {
PIN_11_C,
PIN_21_C,
PIN_31_C,
PIN_41_C,
PIN_51_C,
PIN_61_C,
};
const int pinColTopArr[] = {
PIN_11_A,
PIN_12_A,
PIN_13_A,
PIN_14_A,
PIN_15_A,
PIN_16_A,
PIN_17_A,
PIN_18_A,
};
// init settings
void set_pin_mode(){
// set pin mode
for (int p:allPin){
pinMode(p, OUTPUT);
}
}
void set_buzz_PWM(){
// set LED controller for PWM buzzer
ledcSetup(0, 5000, 8); // use LED controller 0, freq is 5000 Hz, 分辨率為8位
// set buzzer pin
ledcAttachPin(BUZZER_PIN, 0);
}
void load_SD_card(){
// load SD card
if (!SD.begin(SD_CS_PIN)) {
Serial.println("Card initialization failed!");
while (true);
}
}
void set_key_dict(string key){
/*
example key: "42" -> F♯大調
example key: "10" -> C大調
*/
// 10,12(21),20,22,30,40,42,50,52,60,62,70 共 12 鍵,而 p2=(p+1)1 for p = 1,2,4,5,6
if (key[1] == '1'){
key[0] = key[0]-1;
key[1] = '2';
}
string arr[12] = {"10","12","20","22","30","40","42","50","52","60","62","70"};
int push_num;
for (int i = 0; i < 12; i++){
if (arr[i] == key){
push_num = i;
break;
}
}
string temp[84] = {"...10","...12","...20","...22","...30","...40","...42","...50","...52","...60","...62","...70","..10","..12","..20","..22","..30","..40","..42","..50","..52","..60","..62","..70",".10",".12",".20",".22",".30",".40",".42",".50",".52",".60",".62",".70","10","12","20","22","30","40","42","50","52","60","62","70","1.0","1.2","2.0","2.2","3.0","4.0","4.2","5.0","5.2","6.0","6.2","7.0","1..0","1..2","2..0","2..2","3..0","4..0","4..2","5..0","5..2","6..0","6..2","7..0","1...0","1...2","2...0","2...2","3...0","4...0","4...2","5...0","5...2","6...0","6...2","7...0"};
for (int i = 0; i < 60; i++){
string* p = new string[2];
p[0] = temp[12 - push_num + i]; p[1] = temp[12 + i];
key_to_C0[i] = p;
}
string* p = new string[2];
p[0] = "00"; p[1] = "00";
key_to_C0[60] = p;
}
// audio functions
void playTone(double frequency, double duration) {
// set PWM frequency
ledcWriteTone(0, frequency);
delay(duration);
ledcWrite(0, 0);
}
string getLine(File textFile){
if (!textFile.available()){
Serial.println("no more lines in file!");
while (true);
}
string result;
char c_temp = textFile.read();
while (c_temp != '\n'){
result += c_temp;
c_temp = textFile.read(); // output an unknown char if read a '\n'
}
result.erase(result.size() - 1); // remove an unknown char at last
return result;
}
const vector<string> split(const string& str, const string& pattern) {
vector<string> result;
string::size_type begin, end;
end = str.find(pattern);
begin = 0;
while (end != string::npos) {
if (end - begin != 0) {
result.push_back(str.substr(begin, end-begin));
}
begin = end + pattern.size();
end = str.find(pattern, begin);
}
if (begin != str.length()) {
result.push_back(str.substr(begin));
}
return result;
}
string find_freq_from_C0(string s){
if (s[0] == 'x'){
return to_string(BEAT_FREQUENCY);
}
for (string* s_arr:tone_hz_arr_C0){
if (s_arr[0] == s){
return s_arr[1];
}
}
return "error";
}
string find_freq(string s){
if (s[0] == 'x'){
return to_string(BEAT_FREQUENCY);
}
if (s[s.size()-1] == '1'){
s[0] = s[0]-1;
s[s.size()-1] = '2';
}
for (string* s_arr: key_to_C0){
if (s_arr[0] == s){
return find_freq_from_C0(s_arr[1]);
}
}
return "error";
}
/*
【音符設定】 頻,長,連與否(1與0),降(1)升(2)無(0)
設定四分音符為一拍
四分音符的Do - 1,1
四分音符的Re - 2,1
八分音符的Do - 1,0.5
四分音符的高音Do(更高音以此類推) - 1.,1
四分音符的低音Do(更低音以此類推) - .1,1
四分音符的休止符 - 0,1
連音(標註於第一個音) - 1,1,1 例如:連音的四分音符Do-Re-Me為 1,1,1\n2,1,1\n3,1
四分音符的打擊 - x,1
四分音符的降So - 5,1,0,1
四分音符的升Fa - 4,1,0,2
*/
void play_music(void *audioFilePointer){
// cout << "start" << endl;
File audioFile = *((File *)audioFilePointer); // 取得傳入的參數
for (int i = 0; i < SHEET_LENGTH; i++){
string line = getLine(audioFile);
vector<string> v = split(line, ",");
float freq = stof(find_freq(v[0] + v[3]));
if (stoi(v[2])){
float duration = stof(v[1]) * QUARTER_NOTE_DURATION;
playTone(freq, duration);
}else{
float duration = stof(v[1]) * (QUARTER_NOTE_DURATION - SHUT_DURATION);
float shut_duration = stof(v[1]) * (SHUT_DURATION);
playTone(freq, duration);
delay(shut_duration);
}
}
// cout << "finished" << endl;
vTaskDelete(NULL); // 任務執行完畢後刪除自身
}
void setup() {
Serial.begin(115200);
set_pin_mode();
set_buzz_PWM();
load_SD_card();
set_key_dict("42");
// reading file from the card:
File screenFile = SD.open("/map.txt", FILE_READ);
File audioFile = SD.open("/sheet.txt", FILE_READ);
// check file is open
if (!screenFile) {
Serial.println("screenFile file was not opened");
return ;
}
if (!audioFile) {
Serial.println("file was not opened");
return ;
}
// use thread to play LED and music at the same time
// play LED
xTaskCreate(play_LED, "Play LED", 5000, &screenFile, 1, NULL);
// play music
play_music(&audioFile);
}
void loop() {
// null
}
// led functions
void play_LED(void *screenFilePointer){
File screenFile = *((File *)screenFilePointer); // 取得傳入的參數
for (int i = 0; i < PIC_NUM; i++){
char* matrix[HEIGHT];
for (int j = 0; j < HEIGHT; j++){
char* line_temp = new char[WIDTH];
for (int k = 0; k < WIDTH; k++){
line_temp[k] = screenFile.read();
}
screenFile.read(); // skip '\n'
screenFile.read(); // skip unknown char
matrix[j] = line_temp;
}
draw(matrix, FPS);
for (int j = 0; j < HEIGHT; j++){
delete[] matrix[j];
}
delay(REVISION_MS);
}
vTaskDelete(NULL); // 任務執行完畢後刪除自身
}
void closeAllLight(){
for (int p: pinLineHeadArr){
digitalWrite(p, HIGH);
}
for (int p: pinColTopArr){
digitalWrite(p, LOW);
}
}
void draw(char* matrix[HEIGHT], int fps){
float time_start = millis(); // get start time (ms)
float wait_ms = 1000.0 / fps;
while (millis() - time_start < wait_ms){
for (int i = 0; i < HEIGHT; i++){
drawLine(i, matrix[i]);
delay(DELAY_MS);
closeAllLight();
}
}
}
void drawLine(int lineNum, char* pos){
for (int i = 0; i < HEIGHT; i ++){
if (i != lineNum){
digitalWrite(pinLineHeadArr[i], HIGH); // high to close the line
}else{
digitalWrite(pinLineHeadArr[i], LOW);
}
}
for (int i = 0; i < WIDTH; i ++){
byte num = pos[i] - '0';
digitalWrite(pinColTopArr[i], num);
}
}