Files
op-pedal/src/MidiLearnMode.hpp
T
shawn 5fd5946ff6 P4: MIDI control surface mapping — faders/buttons over USB MIDI
C++ backend (fully implemented):
- MidiMapper: CC processing, mapping table, JSON persistence, learn mode
- MidiLearnMode: 3-step learn workflow state machine
- MixerEngine::processMidiEvent() wired into AudioHost MIDI pipeline
- MixerApi + PiPedalSocket: all WS handlers (getMidiMappings,
  setMidiLearnMode, setMidiLearnTarget, commitMidiLearn, etc.)

React frontend (new):
- MidiMappingPanel: dialog with learn mode toggle, CC capture polling,
  commit workflow, current mappings list with delete, manual add
- MixerPage: MIDI button in toolbar, learn mode state management
- ChannelStrip + MasterBus: learn mode callbacks on fader/mute/solo touch
2026-06-20 16:14:12 -04:00

67 lines
2.1 KiB
C++

// Copyright (c) 2026 Ourpad Network
// See LICENSE file in the project root for full license text.
#pragma once
#include <cstdint>
#include <mutex>
#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