5fd5946ff6
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
150 lines
4.9 KiB
C++
150 lines
4.9 KiB
C++
// Copyright (c) 2026 Ourpad Network
|
|
// See LICENSE file in the project root for full license text.
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <mutex>
|
|
#include <memory>
|
|
|
|
namespace pipedal {
|
|
|
|
class MixerEngine;
|
|
struct MidiEvent;
|
|
|
|
/// Types of mixer parameters that can be mapped from MIDI CC.
|
|
enum class MidiTargetType {
|
|
ChannelVolume, ///< Channel fader (-inf .. +12 dB)
|
|
ChannelPan, ///< Channel pan (-1 .. +1)
|
|
ChannelMute, ///< Channel mute toggle
|
|
ChannelSolo, ///< Channel solo toggle
|
|
BusVolume, ///< Bus fader (-inf .. +12 dB)
|
|
BusMute, ///< Bus mute toggle
|
|
MasterVolume, ///< Master bus volume
|
|
MasterMute, ///< Master bus mute
|
|
};
|
|
|
|
/// A single mapping entry: MIDI CC# + channel → mixer parameter.
|
|
struct MidiMappingEntry {
|
|
int midiChannel = -1; ///< MIDI channel (-1 = omni / any)
|
|
int ccNumber = 0; ///< MIDI CC number (0-127)
|
|
MidiTargetType targetType = MidiTargetType::ChannelVolume;
|
|
int64_t targetId = 0; ///< channel index for Channel*, bus ID for Bus*
|
|
|
|
/// Output range: CC=0 maps to minValue, CC=127 maps to maxValue.
|
|
float minValue = 0.0f;
|
|
float maxValue = 1.0f;
|
|
|
|
/// Convert between enum and string (for JSON serialization).
|
|
static MidiTargetType targetTypeFromString(const std::string& str);
|
|
static const char* targetTypeToString(MidiTargetType type);
|
|
};
|
|
|
|
/// MIDI CC → mixer parameter mapper.
|
|
///
|
|
/// Receives MIDI CC events (0xB0) from the real-time audio thread and applies
|
|
/// them to the MixerEngine via atomic parameter setters.
|
|
///
|
|
/// The mapping table is configured from the non-real-time thread; a mutex
|
|
/// protects the table while the RT path snapshots the current mapping set.
|
|
class MidiMapper {
|
|
public:
|
|
MidiMapper();
|
|
~MidiMapper();
|
|
|
|
/// Set the mixer engine to control. Must be set before processing events.
|
|
void setMixerEngine(MixerEngine* engine) { mixerEngine_ = engine; }
|
|
|
|
/// Process a MIDI event. Returns true if a mapping consumed the event.
|
|
/// RT-safe: uses atomic mixer setters directly.
|
|
bool processEvent(const MidiEvent& event);
|
|
|
|
// --- Mapping table management (non-RT thread) ---
|
|
|
|
/// Replace the entire mapping table.
|
|
void setMappings(const std::vector<MidiMappingEntry>& mappings);
|
|
|
|
/// Add a single mapping entry.
|
|
void addMapping(const MidiMappingEntry& entry);
|
|
|
|
/// Remove all mappings matching the given MIDI channel and CC number.
|
|
bool removeMapping(int midiChannel, int ccNumber);
|
|
|
|
/// Remove a specific mapping entry by index.
|
|
bool removeMappingByIndex(size_t index);
|
|
|
|
/// Clear all mappings.
|
|
void clearMappings();
|
|
|
|
/// Get a copy of the current mapping table.
|
|
std::vector<MidiMappingEntry> getMappings() const;
|
|
|
|
// --- Persistence ---
|
|
|
|
/// Serialize mappings to JSON string.
|
|
std::string getMappingsJson() const;
|
|
|
|
/// Deserialize mappings from JSON string.
|
|
void setMappingsFromJson(const std::string& json);
|
|
|
|
/// Default config file path.
|
|
static std::string defaultConfigPath();
|
|
|
|
/// Load mappings from default config file.
|
|
void loadFromFile();
|
|
|
|
/// Save mappings to default config file.
|
|
void saveToFile() const;
|
|
|
|
// --- Learn mode ---
|
|
|
|
/// Enable/disable MIDI learn mode.
|
|
/// When enabled, each incoming CC event is captured and cached
|
|
/// so the next call to commitLearnMapping() will create a mapping.
|
|
void setLearnMode(bool enabled);
|
|
|
|
/// True if learn mode is active.
|
|
bool learnMode() const { return learnMode_; }
|
|
|
|
/// Set the target parameter for the next learned mapping.
|
|
/// Call this when the user touches a UI control.
|
|
void setPendingLearnTarget(MidiTargetType type, int64_t id);
|
|
|
|
/// Get the last-learned MIDI event info.
|
|
/// Returns true if a CC event was captured since learn mode was enabled
|
|
/// or since the last clearLastLearnedEvent().
|
|
bool getLastLearnedEvent(int& outMidiChannel, int& outCcNumber) const;
|
|
|
|
/// Commit the current pending learn target + last CC into a mapping entry.
|
|
/// Returns the new entry, or nullopt if no CC was captured.
|
|
bool commitLearnMapping();
|
|
|
|
/// Clear cached last-learned CC event.
|
|
void clearLastLearnedEvent();
|
|
|
|
/// Get pending learn target info.
|
|
bool getPendingLearnTarget(MidiTargetType& outType, int64_t& outId) const;
|
|
|
|
private:
|
|
MixerEngine* mixerEngine_ = nullptr;
|
|
|
|
// Mapping table — mutable for snapshot-copy in RT path
|
|
std::vector<MidiMappingEntry> mappings_;
|
|
mutable std::mutex mappingsMutex_;
|
|
|
|
// Learn mode state
|
|
bool learnMode_ = false;
|
|
MidiTargetType pendingTargetType_ = MidiTargetType::ChannelVolume;
|
|
int64_t pendingTargetId_ = 0;
|
|
int lastLearnedMidiChannel_ = -1;
|
|
int lastLearnedCcNumber_ = -1;
|
|
mutable std::mutex learnMutex_;
|
|
|
|
// Apply a single mapping entry value to the mixer (RT-safe).
|
|
void applyValue(const MidiMappingEntry& entry, uint8_t ccValue);
|
|
};
|
|
|
|
} // namespace pipedal
|