// MELODY PLAYBACK WILL BE HANDLED HERE #include extern Player player; // Define a structure to track active solenoids struct ActiveRelay { uint8_t relayIndex; // Physical relay index (0-15) uint8_t bellIndex; // Bell index for duration lookup uint64_t activationTime; // Activation start time uint16_t duration; // Duration for which it should remain active }; // Duration per BELL (not per relay output) uint16_t bellDurations[16] = {90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90}; // Level 1: Bell to Physical Output mapping (bell index -> relay index) // bellOutputs[0] = which relay controls Bell #0, etc. uint16_t bellOutputs[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 0-based indexing // Vector to track active solenoids std::vector activeRelays; // Locks for Writing the Counters portMUX_TYPE mySpinlock = portMUX_INITIALIZER_UNLOCKED; void loop_playback(std::vector &melody_steps); void bellEngine(void *parameter); void relayControlTask(void *param); void itsHammerTime(uint16_t note); void turnOffRelays(uint64_t now); // Main Bell Engine. Activates Relays on the exact timing required. void bellEngine(void *parameter) { // SETUP TASK for (;;) { // Playback until stopped (Completes AT LEAST 1 full loop) loop_playback(melody_steps); /* UBaseType_t highWaterMark = uxTaskGetStackHighWaterMark(NULL); Serial.print("Stack high water mark: "); Serial.println(highWaterMark); */ } } // Task to deactivate relays dynamically after set timers void relayControlTask(void *param) { while (true) { uint64_t now = millis(); // Iterate through active relays and deactivate those whose duration has elapsed for (auto it = activeRelays.begin(); it != activeRelays.end();) { if (now - it->activationTime >= it->duration) { relays.digitalWrite(it->relayIndex, HIGH); // Deactivate the relay it = activeRelays.erase(it); // Remove from the active list } else { ++it; // Move to the next relay } } vTaskDelay(pdMS_TO_TICKS(10)); // Check every 10ms } } // Function to wait for tempo, then loop to the next beat. void loop_playback(std::vector &melody_steps) { while(player.isPlaying && !player.isPaused){ LOG_DEBUG("(BellEngine) Single Loop Starting."); // iterate through the beats and call the bell mechanism on each beat for (uint16_t note : melody_steps) { if (player.hardStop) return; itsHammerTime(note); int tempo = player.speed; vTaskDelay(pdMS_TO_TICKS(tempo)); } player.segmentCmpltTime = millis(); LOG_DEBUG("(BellEngine) Single Full Loop Complete"); } } // Function to activate relays for a specific note void itsHammerTime(uint16_t note) { uint64_t now = millis(); // First, determine which bells should ring based on the note pattern for (uint8_t noteIndex = 0; noteIndex < 16; noteIndex++) { if (note & (1 << noteIndex)) { // This note should be played // Level 2: Map note to bell using noteAssignments uint8_t bellIndex = player.noteAssignments[noteIndex]; // Skip if no bell assigned to this note (0 means no assignment) if (bellIndex == 0) continue; // Convert to 0-based indexing (noteAssignments uses 1-based) bellIndex = bellIndex - 1; // Level 1: Map bell to physical relay output uint8_t relayIndex = bellOutputs[bellIndex]; // Activate the relay relays.digitalWrite(relayIndex, LOW); // Add to the activeRelays list with bell-specific duration activeRelays.push_back({ relayIndex, bellIndex, now, bellDurations[bellIndex] }); // Write ring to counter (count per bell, not per relay) portENTER_CRITICAL(&mySpinlock); strikeCounters[bellIndex]++; // Count strikes per bell bellLoad[bellIndex]++; // Load per bell coolingActive = true; portEXIT_CRITICAL(&mySpinlock); } } } // Helper function to update bell-to-output mapping via JSON void updateBellOutputs(JsonVariant doc) { for (uint8_t i = 0; i < 16; i++) { String key = String("b") + (i + 1); if (doc.containsKey(key)) { bellOutputs[i] = doc[key].as()-1; // -1 to convert to 0 based indexing LOG_DEBUG("Bell %d Output set to Relay #%d", i + 1, bellOutputs[i]+1); } else { LOG_DEBUG("Bell %d not found in JSON payload. Keeping previous Output: Relay #%d", i + 1, bellOutputs[i]+1); } } LOG_INFO("Updated Relay Outputs.") StaticJsonDocument<128> response; response["status"] = "OK"; response["type"] = "set_relay_outputs"; char jsonOut[128]; // Create Char Buffer serializeJson(response, jsonOut); // Serialize to Buffer replyOnWebSocket(activeClient, jsonOut); // Reply on WebSocket } // Sets Incoming Relay Durations to RAM and then call funtion to save them to file. void updateRelayTimings(JsonVariant doc) { // Iterate through the relays in the JSON payload for (uint8_t i = 0; i < 16; i++) { String key = String("b") + (i + 1); // Generate "b1", "b2", ... if (doc.containsKey(key)) { bellDurations[i] = doc[key].as(); LOG_DEBUG("Relay %d duration set to %d ms", i + 1, bellDurations[i]); } else { LOG_DEBUG("Relay %d not found in JSON payload. Keeping previous duration: %d ms", i + 1, bellDurations[i]); } } saveRelayTimings(); LOG_INFO("Updated Relay Timings.") StaticJsonDocument<128> response; response["status"] = "OK"; response["type"] = "set_relay_timings"; char jsonOut[128]; // Create Char Buffer serializeJson(response, jsonOut); // Serialize to Buffer replyOnWebSocket(activeClient, jsonOut); // Reply on WebSocket }