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
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user