#pragma once #include #include #include "../Config.h" class BPM : public InputSource, ConfigTaskMixin { public: BPM() : InputSource("BPM") {} void handleEvent(const InputEvent& evt) override { if (evt.intent == InputEvent::BeatDetect) { m_nextBpm = millis(); m_timings.insert(millis()); Log.trace("bpm: %d timings", m_timings.size()); if (m_timings.size() >= 5) { updateBPM(); } } ConfigTaskMixin::handleEvent(evt); } void loop() { InputSource::loop(); ConfigTaskMixin::loop(); } void handleConfigChange(const Configuration& cfg) override { double requestedBPM = cfg.get("bpm.idle", msToBPM(m_msPerBeat)); m_msPerBeat = 60000.0 / (double)requestedBPM; Log.notice("bpm: idle BPM set to %d (requested %d)", (int)msToBPM(m_msPerBeat), (int)requestedBPM); } InputEvent read() override { if (m_msPerBeat > 0) { uint16_t now = millis(); if (now >= m_nextBpm) { m_nextBpm += m_msPerBeat; return InputEvent{InputEvent::Beat, msToBPM(m_msPerBeat)}; } if (now >= m_nextLearn && m_nextLearn != 0) { m_timings.clear(); m_nextLearn = 0; } } return InputEvent{}; } private: uint16_t m_msPerBeat = 60000; uint16_t m_nextBpm = 0; uint16_t m_nextLearn = 0; Ringbuf m_timings; constexpr uint16_t msToBPM(float msPerBeat) const { if (msPerBeat == 0) { return 0; } return 60000.0 / msPerBeat; } void updateBPM() { uint16_t avgDelta = 0; for(uint8_t i = 0; i < m_timings.size() - 1; i++) { uint16_t delta = m_timings.peek(i+1) - m_timings.peek(i); Log.trace("bpm: Timing %d Delta %d", m_timings.peek(i), delta); avgDelta += delta; } m_msPerBeat = avgDelta / 4; m_nextLearn = m_msPerBeat * 5 + millis(); Log.notice("bpm: BPM is now %d", msToBPM(m_msPerBeat)); uint16_t trash; m_timings.take(trash); } };