Line data Source code
1 : #pragma once
2 :
3 : #include <chrono>
4 :
5 : #include "audio/effects/core/effect.h"
6 : #include "audio/engine/i_audio_engine.h"
7 : #include "gui/commands/command_base.h"
8 :
9 : namespace Amplitron {
10 :
11 : /**
12 : * @brief Command that records a single parameter value change on an effect.
13 : *
14 : * Supports coalescing: rapid changes to the same parameter within 500 ms are
15 : * merged into one undo step. Uses shared_ptr<Effect> directly so it is robust
16 : * against effect chain reordering.
17 : */
18 6 : class ParameterChangeCommand : public Command {
19 : public:
20 : /**
21 : * @brief Construct a ParameterChangeCommand.
22 : * @param engine Reference to the AudioEngine.
23 : * @param effect Shared pointer to the target effect.
24 : * @param param_index Index of the parameter within the effect's param list.
25 : * @param old_value Value before the change (used by undo).
26 : * @param new_value Value after the change (used by execute).
27 : */
28 52 : ParameterChangeCommand(IAudioEngine& engine, std::shared_ptr<Effect> effect, int param_index,
29 : float old_value, float new_value)
30 65 : : engine_(engine),
31 39 : effect_(std::move(effect)),
32 39 : param_index_(param_index),
33 30 : old_value_(old_value),
34 63 : new_value_(new_value) {}
35 :
36 : /** @brief Set the parameter to new_value_. */
37 12 : bool execute() override {
38 12 : auto& params = effect_->params();
39 12 : if (param_index_ >= 0 && param_index_ < static_cast<int>(params.size())) {
40 6 : params[param_index_].value = new_value_;
41 6 : int node_id = -1;
42 30 : for (const auto& node : engine_.graph().get_nodes()) {
43 24 : if (node.pedal == effect_) {
44 0 : node_id = node.id;
45 0 : break;
46 : }
47 : }
48 6 : if (node_id == -1) {
49 6 : auto& fx = engine_.effects();
50 24 : for (int i = 0; i < static_cast<int>(fx.size()); ++i) {
51 18 : if (fx[i] == effect_) {
52 0 : node_id = i;
53 0 : break;
54 : }
55 6 : }
56 2 : }
57 6 : if (node_id >= 0) engine_.push_param_change(node_id, param_index_, new_value_);
58 2 : }
59 12 : return true;
60 : }
61 :
62 : /** @brief Restore the parameter to old_value_. */
63 12 : void undo() override {
64 12 : auto& params = effect_->params();
65 12 : if (param_index_ >= 0 && param_index_ < static_cast<int>(params.size())) {
66 6 : params[param_index_].value = old_value_;
67 6 : int node_id = -1;
68 30 : for (const auto& node : engine_.graph().get_nodes()) {
69 24 : if (node.pedal == effect_) {
70 0 : node_id = node.id;
71 0 : break;
72 : }
73 : }
74 6 : if (node_id == -1) {
75 6 : auto& fx = engine_.effects();
76 24 : for (int i = 0; i < static_cast<int>(fx.size()); ++i) {
77 18 : if (fx[i] == effect_) {
78 0 : node_id = i;
79 0 : break;
80 : }
81 6 : }
82 2 : }
83 6 : if (node_id >= 0) engine_.push_param_change(node_id, param_index_, old_value_);
84 2 : }
85 12 : }
86 :
87 : /** @brief Return "Change Parameter". */
88 0 : const char* description() const override { return "Change Parameter"; }
89 :
90 : /**
91 : * @brief Attempt to coalesce @p other into this command.
92 : *
93 : * Merges if @p other is a ParameterChangeCommand targeting the same
94 : * effect and parameter index within 500 ms. On success, this command's
95 : * new_value_ and timestamp are updated; old_value_ is preserved.
96 : *
97 : * @return true if @p other was absorbed.
98 : */
99 21 : bool merge_with(const Command& other) override {
100 21 : auto* pc = dynamic_cast<const ParameterChangeCommand*>(&other);
101 21 : if (!pc) return false;
102 18 : if (pc->effect_.get() != effect_.get() || pc->param_index_ != param_index_) return false;
103 :
104 3 : auto dt =
105 9 : std::chrono::duration_cast<std::chrono::milliseconds>(pc->timestamp_ - timestamp_);
106 9 : if (dt.count() > 500) return false;
107 :
108 6 : new_value_ = pc->new_value_;
109 6 : timestamp_ = pc->timestamp_;
110 6 : return true;
111 7 : }
112 :
113 : /** @brief Accessor for the target effect. */
114 3 : std::shared_ptr<Effect> effect() const { return effect_; }
115 :
116 : /** @brief Index of the parameter being changed. */
117 5 : int param_index() const { return param_index_; }
118 :
119 : /** @brief Value before the change. */
120 : float old_value() const { return old_value_; }
121 :
122 : /** @brief Value after the change. */
123 : float new_value() const { return new_value_; }
124 :
125 : private:
126 : IAudioEngine& engine_;
127 : std::shared_ptr<Effect> effect_;
128 : int param_index_;
129 : float old_value_;
130 : float new_value_;
131 : };
132 :
133 : } // namespace Amplitron
|