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