LCOV - code coverage report
Current view: top level - src/gui/state - snapshot_manager.h (source / functions) Coverage Total Hit
Test: merged.info Lines: 100.0 % 62 62
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 9 9

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <array>
       4              : #include <optional>
       5              : #include <vector>
       6              : 
       7              : #include "audio/effects/core/effect.h"
       8              : #include "audio/engine/i_audio_engine.h"
       9              : #include "common.h"
      10              : #include "gui/commands/command.h"
      11              : 
      12              : namespace Amplitron {
      13              : 
      14              : /**
      15              :  * @brief In-memory snapshot manager for A/B/C/D board state switching.
      16              :  *
      17              :  * Stores up to NUM_SLOTS complete board configurations (effect chain + gains)
      18              :  * in memory for instant recall during performance, without file I/O. Each slot
      19              :  * captures the same state as a preset — effect instances, enabled/mix flags,
      20              :  * and all parameter values — using the LoadPresetCommand::EffectSnapshot pattern
      21              :  * so that recall integrates cleanly with the undo/redo system.
      22              :  */
      23           73 : class SnapshotManager {
      24              :    public:
      25              :     static constexpr int NUM_SLOTS = 4;
      26              : 
      27              :     /** @brief Display labels for each slot. */
      28              :     static constexpr const char* SLOT_LABELS[NUM_SLOTS] = {"A", "B", "C", "D"};
      29              : 
      30              :     /**
      31              :      * @brief Complete board state captured at a point in time.
      32              :      *
      33              :      * Mirrors the data captured by LoadPresetCommand so that RecallSnapshotCommand
      34              :      * can restore it using the same atomic engine swap.
      35              :      */
      36          134 :     struct BoardSnapshot {
      37              :         std::vector<LoadPresetCommand::EffectSnapshot> effects;
      38           49 :         float input_gain = 0.7f;
      39           49 :         float output_gain = 0.8f;
      40              :     };
      41              : 
      42              :     // ------------------------------------------------------------------
      43              :     // Slot management
      44              :     // ------------------------------------------------------------------
      45              : 
      46              :     /** @brief Capture the current engine state into the given slot (0–3). */
      47          119 :     void save_slot(int slot, IAudioEngine& engine) {
      48          119 :         if (slot < 0 || slot >= NUM_SLOTS) return;
      49          114 :         slots_[slot] = capture(engine);
      50           40 :     }
      51              : 
      52              :     /** @brief True if the slot contains a saved snapshot. */
      53           88 :     bool has_slot(int slot) const {
      54           67 :         if (slot < 0 || slot >= NUM_SLOTS) return false;
      55           83 :         return slots_[slot].has_value();
      56           30 :     }
      57              : 
      58              :     /**
      59              :      * @brief Apply the stored snapshot directly to the engine (no undo/redo).
      60              :      *
      61              :      * For use in tests and headless scenarios. GUI code should call
      62              :      * GuiSnapshots::recall_slot() instead to get undo/redo support.
      63              :      */
      64           93 :     void recall_slot_direct(int slot, IAudioEngine& engine) {
      65           93 :         if (slot < 0 || slot >= NUM_SLOTS) return;
      66           90 :         if (!slots_[slot].has_value()) return;
      67           87 :         apply(*slots_[slot], engine);
      68           87 :         active_slot_ = slot;
      69           32 :     }
      70              : 
      71              :     /** @brief Return a pointer to the stored snapshot, or nullptr if the slot is empty. */
      72           60 :     const BoardSnapshot* get_slot(int slot) const {
      73           60 :         if (slot < 0 || slot >= NUM_SLOTS) return nullptr;
      74           63 :         return slots_[slot].has_value() ? &(*slots_[slot]) : nullptr;
      75           21 :     }
      76              : 
      77              :     /** @brief Clear the stored snapshot from a slot. */
      78           16 :     void clear_slot(int slot) {
      79           16 :         if (slot < 0 || slot >= NUM_SLOTS) return;
      80           12 :         slots_[slot].reset();
      81           12 :         if (active_slot_ == slot) active_slot_ = -1;
      82            6 :     }
      83              : 
      84              :     // ------------------------------------------------------------------
      85              :     // Active slot tracking
      86              :     // ------------------------------------------------------------------
      87              : 
      88              :     /** @brief Index of the most recently recalled slot (-1 = none active). */
      89           94 :     int active_slot() const { return active_slot_; }
      90              : 
      91              :     /** @brief Mark a slot as active (called after save or recall). */
      92           35 :     void set_active_slot(int slot) { active_slot_ = slot; }
      93              : 
      94              :     // ------------------------------------------------------------------
      95              :     // Static helpers (shared with RecallSnapshotCommand)
      96              :     // ------------------------------------------------------------------
      97              : 
      98              :     /** @brief Capture the current engine state as a BoardSnapshot. */
      99          147 :     static BoardSnapshot capture(IAudioEngine& engine) {
     100          147 :         BoardSnapshot snap;
     101          147 :         snap.input_gain = engine.get_input_gain();
     102          147 :         snap.output_gain = engine.get_output_gain();
     103              : 
     104          336 :         for (auto& fx : engine.effects()) {
     105          189 :             LoadPresetCommand::EffectSnapshot es;
     106          189 :             es.effect = fx;
     107          189 :             es.enabled = fx->is_enabled();
     108          189 :             es.mix = fx->get_mix();
     109          816 :             for (auto& p : fx->params()) {
     110          627 :                 es.param_values.push_back(p.value);
     111              :             }
     112          189 :             snap.effects.push_back(std::move(es));
     113          189 :         }
     114          147 :         return snap;
     115           49 :     }
     116              : 
     117              :     /**
     118              :      * @brief Apply a BoardSnapshot to the engine.
     119              :      *
     120              :      * Restores all effect enabled/mix/param states and performs an atomic
     121              :      * effect chain swap via AudioEngine::restore_effects_state so the audio
     122              :      * thread never sees a partial state.
     123              :      */
     124           87 :     static void apply(const BoardSnapshot& snap, IAudioEngine& engine) {
     125           87 :         std::vector<std::shared_ptr<Effect>> new_effects;
     126           87 :         new_effects.reserve(snap.effects.size());
     127              : 
     128          204 :         for (const auto& es : snap.effects) {
     129          117 :             es.effect->set_enabled(es.enabled);
     130          117 :             es.effect->set_mix(es.mix);
     131          117 :             auto& params = es.effect->params();
     132          757 :             for (int i = 0; i < static_cast<int>(params.size()) &&
     133          384 :                             i < static_cast<int>(es.param_values.size());
     134          128 :                  ++i) {
     135          384 :                 params[i].value = es.param_values[i];
     136          128 :             }
     137          117 :             new_effects.push_back(es.effect);
     138              :         }
     139              : 
     140           87 :         engine.restore_effects_state(std::move(new_effects));
     141           87 :         engine.set_input_gain(snap.input_gain);
     142           87 :         engine.set_output_gain(snap.output_gain);
     143           87 :     }
     144              : 
     145              :    private:
     146              :     std::array<std::optional<BoardSnapshot>, NUM_SLOTS> slots_;
     147           35 :     int active_slot_ = -1;
     148              : };
     149              : 
     150              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1