// Copyright (c) 2026 Ourpad Network // See LICENSE file in the project root for full license text. #pragma once #include #include #include "MidiMapper.hpp" namespace pipedal { /// MIDI Learn mode state machine. /// /// Tracks the three-step learn workflow: /// 1. User enables learn mode and touches a UI control (setPendingTarget) /// 2. User moves a hardware fader — the CC event is captured (captureEvent) /// 3. User confirms — a new MidiMappingEntry is created (commitMapping) /// class MidiLearnMode { public: MidiLearnMode(); ~MidiLearnMode(); /// Enable or disable learn mode. void setEnabled(bool enabled); bool isEnabled() const { return enabled_; } /// Set the mixer parameter that should receive the next learned mapping. /// Call this when the user touches a UI control while in learn mode. void setPendingTarget(MidiTargetType type, int64_t id); /// Get the current pending target. bool getPendingTarget(MidiTargetType& outType, int64_t& outId) const; /// Capture a MIDI CC event while in learn mode. /// Call this from the RT audio thread when processEvent sees a CC. void captureEvent(int midiChannel, int ccNumber); /// Check if a CC event has been captured since learn mode was entered /// or since the last clear(). bool hasCapturedEvent() const; /// Get the last captured CC event info. /// Returns true if an event was captured. bool getCapturedEvent(int& outMidiChannel, int& outCcNumber) const; /// Build a MidiMappingEntry from pending target + captured event. /// Clears the captured event after building (avoids stale recomit). MidiMappingEntry buildMapping() const; /// Clear captured event without committing. void clearCapturedEvent(); /// Reset all learn state. void reset(); private: bool enabled_ = false; MidiTargetType pendingTargetType_ = MidiTargetType::ChannelVolume; int64_t pendingTargetId_ = 0; int capturedMidiChannel_ = -1; int capturedCcNumber_ = -1; mutable std::mutex mutex_; }; } // namespace pipedal