LCOV - code coverage report
Current view: top level - src/midi - midi_manager.h (source / functions) Coverage Total Hit
Test: merged.info Lines: 93.3 % 15 14
Test Date: 2026-06-03 09:13:19 Functions: 85.7 % 7 6

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <string>
       4              : #include <vector>
       5              : #include <cstdint>
       6              : 
       7              : #ifndef AMPLITRON_NO_MIDI
       8              : #include "audio/utils/spsc_queue.h"
       9              : #endif
      10              : 
      11              : namespace Amplitron {
      12              : 
      13              : class AudioEngine;
      14              : 
      15              : // Raw MIDI event pushed from the RtMidi callback thread.
      16              : // Must be trivially copyable for SPSCQueue.
      17        48128 : struct MidiEvent {
      18              :     uint8_t status;  // e.g. 0xB0 = CC on channel 0
      19              :     uint8_t data1;   // CC number (0-127)
      20              :     uint8_t data2;   // CC value (0-127)
      21        24064 :     uint8_t pad = 0; // Pad to 4 bytes
      22              : };
      23              : 
      24              : enum class MidiMappingMode : uint8_t {
      25              :     Continuous, // CC 0-127 maps linearly to param [min..max]
      26              :     Toggle,     // CC >= 64 -> on, CC < 64 -> off
      27              : };
      28              : 
      29              : enum class MidiTargetType : uint8_t {
      30              :     EffectParam,  // Maps to a specific effect parameter
      31              :     EffectBypass, // Maps to effect enabled/disabled
      32              :     InputGain,    // Maps to master input gain
      33              :     OutputGain,   // Maps to master output gain
      34              : };
      35              : 
      36          595 : struct MidiMapping {
      37           92 :     int cc_number = 0;    // 0-127
      38           92 :     int midi_channel = -1; // 0-15, or -1 for "any channel"
      39           92 :     MidiTargetType target_type = MidiTargetType::EffectParam;
      40           92 :     MidiMappingMode mode = MidiMappingMode::Continuous;
      41              : 
      42              :     std::string effect_name;        // For EffectParam/EffectBypass targets
      43              :     std::string param_name;         // For EffectParam targets only
      44           92 :     mutable bool last_state = false; // Tracks pedal state for Toggle mode
      45              : };
      46              : 
      47              : #ifdef AMPLITRON_NO_MIDI
      48              : 
      49              : // Stub implementation for non-desktop platforms (web, mobile)
      50              : class MidiManager {
      51              :     friend class TestAccessor;
      52              : public:
      53              :     MidiManager() = default;
      54              :     ~MidiManager() = default;
      55              : 
      56              :     bool initialize() { return false; }
      57              :     void shutdown() {}
      58              : 
      59              :     std::vector<std::string> get_available_ports() const { return {}; }
      60              :     bool open_port(int) { return false; }
      61              :     void close_port() {}
      62              :     int current_port() const { return -1; }
      63              :     std::string current_port_name() const { return ""; }
      64              :     bool is_port_open() const { return false; }
      65              : 
      66              :     void add_mapping(const MidiMapping&) {}
      67              :     void remove_mapping(int) {}
      68              :     void remove_mapping_for_param(const std::string&, const std::string&) {}
      69              :     void clear_mappings() {}
      70              :     const std::vector<MidiMapping>& mappings() const {
      71              :         static std::vector<MidiMapping> empty;
      72              :         return empty;
      73              :     }
      74              : 
      75              :     void install_default_mappings() {}
      76              : 
      77              :     void start_learn(MidiTargetType, const std::string&, const std::string&) {}
      78              :     void cancel_learn() {}
      79              :     bool is_learning() const { return false; }
      80              :     std::string learn_status() const { return ""; }
      81              :     const std::string& learn_effect_name() const { static std::string empty; return empty; }
      82              :     const std::string& learn_param_name() const { static std::string empty; return empty; }
      83              : 
      84              :     void poll(AudioEngine&) {}
      85              :     void save_config() const {}
      86              :     void load_config() {}
      87              : 
      88              :     void inject_event(const MidiEvent&) {}
      89              : };
      90              : 
      91              : #else
      92              : 
      93              : /**
      94              :  * @brief MIDI input manager with CC-to-parameter mapping and MIDI learn.
      95              :  *
      96              :  * Runs a lock-free SPSC queue between RtMidi's callback thread and the
      97              :  * GUI thread. The GUI thread calls poll() each frame to drain events and
      98              :  * route CC values through the existing engine.push_param_change() path.
      99              :  */
     100              : class MidiManager {
     101              :     friend class TestAccessor;
     102              : public:
     103              :     MidiManager();
     104              :     ~MidiManager();
     105              : 
     106              :     /** @brief Open the first available MIDI input port. @return true on success. */
     107              :     bool initialize();
     108              : 
     109              :     /** @brief Close the MIDI port and release resources. */
     110              :     void shutdown();
     111              : 
     112              :     // --- Port management ---
     113              : 
     114              :     /** @brief List available MIDI input port names. */
     115              :     std::vector<std::string> get_available_ports() const;
     116              : 
     117              :     /** @brief Open a specific MIDI input port by index. @return true on success. */
     118              :     bool open_port(int port_index);
     119              : 
     120              :     /** @brief Close the currently open port. */
     121              :     void close_port();
     122              : 
     123              :     /** @brief Return the index of the currently open port, or -1 if none. */
     124            0 :     int current_port() const { return current_port_; }
     125              : 
     126              :     /** @brief Return the name of the currently open port, or empty string. */
     127            8 :     std::string current_port_name() const { return current_port_name_; }
     128              : 
     129              :     /** @brief Return true if a MIDI port is currently open. */
     130           16 :     bool is_port_open() const { return current_port_ >= 0; }
     131              : 
     132              :     // --- Mapping management ---
     133              : 
     134              :     void add_mapping(const MidiMapping& mapping);
     135              :     void remove_mapping(int index);
     136              :     void remove_mapping_for_param(const std::string& effect_name, const std::string& param_name);
     137              :     void clear_mappings();
     138          828 :     const std::vector<MidiMapping>& mappings() const { return mappings_; }
     139              : 
     140              :     /** @brief Install default CC mappings (CC7, CC11, CC64, CC74). */
     141              :     void install_default_mappings();
     142              : 
     143              :     // --- MIDI Learn ---
     144              : 
     145              :     /**
     146              :      * @brief Enter learn mode: the next CC event received will be bound to the given target.
     147              :      */
     148              :     void start_learn(MidiTargetType type, const std::string& effect_name, const std::string& param_name);
     149              : 
     150              :     /** @brief Cancel learn mode without creating a mapping. */
     151              :     void cancel_learn();
     152              : 
     153              :     /** @brief Return true if learn mode is active. */
     154         1773 :     bool is_learning() const { return learn_active_; }
     155              : 
     156              :     /** @brief Human-readable status for the learn indicator, or empty. */
     157              :     std::string learn_status() const;
     158              : 
     159           18 :     const std::string& learn_effect_name() const { return learn_effect_name_; }
     160           18 :     const std::string& learn_param_name() const { return learn_param_name_; }
     161              : 
     162              :     // --- Poll (called from GUI thread each frame) ---
     163              : 
     164              :     /**
     165              :      * @brief Drain the MIDI event queue and apply CC mappings.
     166              :      *
     167              :      * For each CC event:
     168              :      * - If learn mode is active, captures the CC and creates a mapping.
     169              :      * - Otherwise, resolves the mapping target and pushes the value to the engine.
     170              :      */
     171              :     void poll(AudioEngine& engine);
     172              : 
     173              :     // --- Persistence ---
     174              : 
     175              :     /** @brief Save mappings and port preference to midi_config.json. */
     176              :     void save_config() const;
     177              : 
     178              :     /** @brief Load mappings and port preference from midi_config.json. */
     179              :     void load_config();
     180              : 
     181              :     /**
     182              :      * @brief Push a MIDI event into the queue from test code.
     183              :      *
     184              :      * This is public so unit tests can inject events without hardware.
     185              :      */
     186              :     void inject_event(const MidiEvent& event);
     187              : 
     188              : private:
     189              :     static void midi_callback(double timestamp, std::vector<unsigned char>* message, void* user_data);
     190              : 
     191              :     static std::string get_config_path();
     192              : 
     193              :     void* midi_in_ = nullptr; // RtMidiIn* (opaque to avoid header dependency)
     194              :     int current_port_ = -1;
     195              :     std::string current_port_name_;
     196              : 
     197              :     SPSCQueue<MidiEvent, 256> midi_queue_;
     198              :     std::vector<MidiMapping> mappings_;
     199              : 
     200              :     // Learn state
     201              :     bool learn_active_ = false;
     202              :     MidiTargetType learn_target_type_ = MidiTargetType::EffectParam;
     203              :     std::string learn_effect_name_;
     204              :     std::string learn_param_name_;
     205              : 
     206              :     // Helpers
     207              :     void apply_mapping(const MidiMapping& mapping, int cc_value, AudioEngine& engine);
     208              :     std::string mappings_to_json() const;
     209              :     bool mappings_from_json(const std::string& json);
     210              : };
     211              : 
     212              : #endif // AMPLITRON_NO_MIDI
     213              : 
     214              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1