#pragma once extern std::vector melody_steps; void sendToApp(String jsonMessage); void PublishMqtt(const char * data); bool addMelody(JsonVariant doc); class Player { public: uint16_t id; // The (internal) ID of the selected melody. Not specificly used anywhere atm. Might be used later. std::string name = "melody1"; // Name of the Melody saved. Will be used to read the file: /name.bin std::string uid = "x"; // The UID of the melody, from Firestore std::string url = "-"; // The URL of the binary, from Firestore uint16_t noteAssignments[16] = {0}; // The list of Note Assignments for the melody uint16_t speed = 500; // Time to wait per beat. (In Miliseconds) uint32_t segment_duration = 15000; // Duration of the playback per segment (per "loop") uint32_t pause_duration = 0; // Duration of the pauses between segments (loops) uint32_t total_duration = 0; // The Total Run Duration of the program. Including all loops uint64_t segmentCmpltTime = 0; // The time of completion of the last Segment uint64_t segmentStartTime = 0; // The time-point the current segment started Playing uint64_t startTime = 0; // The time-point the Melody started Playing uint64_t pauseTime = 0; // The time-point the melody paused bool continuous_loop = false; // Indicates if the Loop should Run 1-Take or Continuous with Intervals bool infinite_play = false; // Infinite Loop Indicator (If True the melody will loop forever or until stoped, with pauses of "interval" in between loops) bool isPlaying = false; // Indicates if the Melody is actually Playing right now. bool isPaused = false; // If playing, indicates if the Melody is Paused bool hardStop = false; // Flags a hardstop, immediately. void play() { isPlaying = true; hardStop = false; startTime = segmentStartTime = millis(); LOG_DEBUG("Plbck: PLAY"); } void forceStop() { hardStop = true; isPlaying = false; LOG_DEBUG("Plbck: FORCE STOP"); } void stop() { hardStop = false; isPlaying = false; LOG_DEBUG("Plbck: STOP"); StaticJsonDocument<128> doc; doc["status"] = "NOTIFY"; doc["type"] = "playback"; doc["payload"] = "stop"; String output; serializeJson(doc, output); sendToApp(output); } void pause() { isPaused = true; LOG_DEBUG("Plbck: PAUSE"); } void unpause() { isPaused = false; segmentStartTime = millis(); LOG_DEBUG("Plbck: RESUME"); } // Handles Incoming Commands to PLAY or STOP void command(JsonVariant data, AsyncWebSocketClient *client = nullptr){ setMelodyAttributes(data); loadMelodyInRAM(melody_steps); String action = data["action"]; LOG_DEBUG("Incoming Command: %s", action); // Play or Stop Logic if (action == "play") { play(); } else if (action == "stop") { forceStop(); } else { return; } // Prepare JSON response StaticJsonDocument<128> response; response["status"] = "OK"; response["type"] = action; // "play" or "stop" response["payload"] = nullptr; // Use null in JSON // Serialize JSON to char buffer char jsonOut[256]; serializeJson(response, jsonOut); // Send via MQTT PublishMqtt(jsonOut); // Optionally send via WebSocket if (client) { client->text(jsonOut); } } // Sets incoming Attributes for the Melody, into the class' variables. void setMelodyAttributes(JsonVariant doc){ if (doc.containsKey("name")) { name = doc["name"].as(); } if (doc.containsKey("uid")) { uid = doc["uid"].as(); } if (doc.containsKey("url")) { url = doc["url"].as(); } if (doc.containsKey("speed")) { speed = doc["speed"].as(); } if (doc.containsKey("note_assignments")) { JsonArray noteArray = doc["note_assignments"]; size_t arraySize = min(noteArray.size(), (size_t)16); for (size_t i = 0; i < arraySize; i++) { noteAssignments[i] = noteArray[i]; } } if (doc.containsKey("segment_duration")) { segment_duration = doc["segment_duration"].as(); } if (doc.containsKey("pause_duration")) { pause_duration = doc["pause_duration"].as(); } if (doc.containsKey("total_duration")) { total_duration = doc["total_duration"].as(); } if (doc.containsKey("continuous_loop")) { continuous_loop = doc["continuous_loop"].as(); } if (continuous_loop && total_duration == 0){ infinite_play = true; } if (!continuous_loop) { total_duration = segment_duration; } // Print Just for Debugging Purposes LOG_DEBUG("Set Melody Vars / Name: %s, UID: %s", name.c_str(), uid.c_str() ); // Print Just for Debugging Purposes LOG_DEBUG("URL: %s", url.c_str() ); // Print Just for Debugging Purposes LOG_DEBUG("Speed: %d, Per Segment Duration: %lu, Pause Duration: %lu, Total Duration: %d, Continuous: %s, Infinite: %s", speed, segment_duration, pause_duration, total_duration, continuous_loop ? "true" : "false", infinite_play ? "true" : "false" ); } // Loads the Selected melody from a .bin file on SD into RAM void loadMelodyInRAM(std::vector &melody_steps) { String filePath = "/melodies/" + String(uid.c_str()); File bin_file = SD.open(filePath.c_str(), FILE_READ); if (!bin_file) { LOG_ERROR("Failed to open file: %s", filePath.c_str()); LOG_ERROR("Check Servers for the File..."); StaticJsonDocument<128> doc; doc["download_url"] = url; doc["melodys_uid"] = uid; if (!addMelody(doc)){ LOG_ERROR("Failed to Download File. Check Internet Connection"); return; } else { bin_file = SD.open(filePath.c_str(), FILE_READ); } } size_t fileSize = bin_file.size(); if (fileSize % 2 != 0) { LOG_ERROR("Invalid file size: %u (not a multiple of 2)", fileSize); bin_file.close(); return; } melody_steps.resize(fileSize / 2); for (size_t i = 0; i < melody_steps.size(); i++) { uint8_t high = bin_file.read(); uint8_t low = bin_file.read(); melody_steps[i] = (high << 8) | low; } LOG_INFO("Melody Load Successful"); bin_file.close(); } };