diff --git a/gui/dialogs/createchasedialog.cpp b/gui/dialogs/createchasedialog.cpp index 3184274..b45c851 100644 --- a/gui/dialogs/createchasedialog.cpp +++ b/gui/dialogs/createchasedialog.cpp @@ -109,12 +109,13 @@ void CreateChaseDialog::onCreateChaseButtonClicked() { new_chase->SetName(folder.GetAvailableName("Chase")); folder.Add(new_chase); - theatre::Sequence &sequence = new_chase->GetSequence(); + std::vector sequence; Gtk::TreeModel::Children children = _newChaseListModel->children(); for (const Gtk::TreeRow &row : children) { theatre::Controllable *object = row[_newChaseListColumns._controllable]; - sequence.Add(*object, 0); + sequence.emplace_back(*object, 0); } + new_chase->SetSequence(sequence); lock.unlock(); diff --git a/gui/windows/chasepropertieswindow.cpp b/gui/windows/chasepropertieswindow.cpp index 4201037..fd7bb98 100644 --- a/gui/windows/chasepropertieswindow.cpp +++ b/gui/windows/chasepropertieswindow.cpp @@ -199,8 +199,10 @@ void ChasePropertiesWindow::onToTimeSequenceClicked() { theatre::TimeSequence &tSequence = *time_sequence_ptr; tSequence.SetRepeatCount(0); size_t index = 0; - for (theatre::Input &input : _chase->GetSequence().List()) { - tSequence.AddStep(*input.GetControllable(), input.InputIndex()); + for (const theatre::Input &input : _chase->GetSequence()) { + theatre::Controllable &controllable = + Instance::Management().GetNonConst(*input.GetControllable()); + tSequence.AddStep(controllable, input.InputIndex()); theatre::TimeSequence::Step &step = tSequence.GetStep(index); if (_chase->GetTrigger().Type() == theatre::TriggerType::Delay) step.transition = _chase->GetTransition(); diff --git a/gui/windows/designwizard.cpp b/gui/windows/designwizard.cpp index 1e3e2ec..e18c6e5 100644 --- a/gui/windows/designwizard.cpp +++ b/gui/windows/designwizard.cpp @@ -20,6 +20,7 @@ #include "theatre/folder.h" #include "theatre/folderoperations.h" #include "theatre/management.h" +#include "theatre/presetcollection.h" #include "theatre/theatre.h" #include "theatre/timesequence.h" diff --git a/gui/windows/scenewindow.cpp b/gui/windows/scenewindow.cpp index ec950c9..137537a 100644 --- a/gui/windows/scenewindow.cpp +++ b/gui/windows/scenewindow.cpp @@ -329,10 +329,10 @@ void SceneWindow::fillControllablesList() { _latestSelectedControllable = nullptr; } } - for (size_t output_index = 0; output_index != _selectedScene->NOutputs(); - ++output_index) { + for (size_t output_index = 0; + output_index != _selectedScene->NConnections(); ++output_index) { std::pair output = - _selectedScene->Output(output_index); + _selectedScene->GetConnection(output_index); Gtk::TreeModel::iterator iter = _controllablesListModel->append(); Gtk::TreeModel::Row &row = *iter; row[_controllablesListColumns._text] = output.first->Name(); diff --git a/gui/windows/timesequencepropertieswindow.cpp b/gui/windows/timesequencepropertieswindow.cpp index ed54a9a..e2b78ad 100644 --- a/gui/windows/timesequencepropertieswindow.cpp +++ b/gui/windows/timesequencepropertieswindow.cpp @@ -169,7 +169,7 @@ void TimeSequencePropertiesWindow::fillStepsList() { for (size_t i = 0; i != _timeSequence->Size(); ++i) { Gtk::TreeModel::iterator iter = _stepsStore->append(); Gtk::TreeModel::Row &row = *iter; - theatre::Input &input = _timeSequence->Sequence().List()[i]; + const theatre::Input &input = _timeSequence->Sequence()[i]; row[_stepsListColumns._title] = input.GetControllable()->InputName(input.InputIndex()); row[_stepsListColumns._trigger] = diff --git a/system/reader.cpp b/system/reader.cpp index 5d17e63..e59b44e 100644 --- a/system/reader.cpp +++ b/system/reader.cpp @@ -14,6 +14,7 @@ #include "theatre/fixturetype.h" #include "theatre/folder.h" #include "theatre/management.h" +#include "theatre/presetcollection.h" #include "theatre/presetvalue.h" #include "theatre/theatre.h" #include "theatre/timesequence.h" @@ -320,17 +321,18 @@ void ParsePresetCollection(const Object &node, Management &management) { } } -void ParseSequence(const Object &node, Sequence &sequence, - Management &management) { +std::vector ParseSequence(const Object &node, Management &management) { const Array &inputs = ToArr(node["inputs"]); + std::vector sequence; for (Node &item_node : inputs) { const Object &item = ToObj(item_node); size_t input = OptionalSize(item, "input-index", 0); size_t folderId = OptionalSize(item, "folder", 0); Controllable &c = dynamic_cast( management.Folders()[folderId]->GetChild(ToStr(item["name"]))); - sequence.Add(c, input); + sequence.emplace_back(c, input); } + return sequence; } void ParseTrigger(const Object &node, Trigger &trigger) { @@ -353,7 +355,7 @@ void ParseChase(const Object &node, Management &management) { Chase &chase = *chase_ptr; ParseTrigger(ToObj(node["trigger"]), chase.GetTrigger()); chase.GetTransition() = ParseTransition(ToObj(node["transition"])); - ParseSequence(ToObj(node["sequence"]), chase.GetSequence(), management); + chase.SetSequence(ParseSequence(ToObj(node["sequence"]), management)); } void ParseTimeSequence(const Object &node, Management &management) { @@ -363,7 +365,7 @@ void ParseTimeSequence(const Object &node, Management &management) { TimeSequence &time_sequence = *time_sequence_ptr; time_sequence.SetSustain(ToBool(node["sustain"])); time_sequence.SetRepeatCount(ToNum(node["repeat-count"]).AsSize()); - ParseSequence(ToObj(node["sequence"]), time_sequence.Sequence(), management); + time_sequence.SetSequence(ParseSequence(ToObj(node["sequence"]), management)); const Array &steps = ToArr(node["steps"]); for (Node &item : steps) { const Object &step_obj = ToObj(item); @@ -371,7 +373,7 @@ void ParseTimeSequence(const Object &node, Management &management) { ParseTrigger(ToObj(step_obj["trigger"]), step.trigger); step.transition = ParseTransition(ToObj(step_obj["transition"])); } - if (time_sequence.Steps().size() != time_sequence.Sequence().Size()) + if (time_sequence.Steps().size() != time_sequence.Sequence().size()) throw std::runtime_error( "nr of steps in time sequence doesn't match sequence size"); } diff --git a/system/writer.cpp b/system/writer.cpp index 0cc4d65..c1bf4e6 100644 --- a/system/writer.cpp +++ b/system/writer.cpp @@ -15,6 +15,7 @@ #include "theatre/fixturetype.h" #include "theatre/folder.h" #include "theatre/management.h" +#include "theatre/presetcollection.h" #include "theatre/presetvalue.h" #include "theatre/theatre.h" #include "theatre/timesequence.h" @@ -289,7 +290,7 @@ void writePresetValue(WriteState &state, const PresetValue &presetValue) { } void writePresetCollection(WriteState &state, - const class PresetCollection &presetCollection) { + const PresetCollection &presetCollection) { const std::vector> &values = presetCollection.PresetValues(); for (const std::unique_ptr &pv : values) @@ -339,10 +340,10 @@ void writeTransition(WriteState &state, const Transition &transition, state.writer.EndObject(); } -void writeSequence(WriteState &state, const Sequence &sequence) { +void writeSequence(WriteState &state, const std::vector &sequence) { state.writer.StartObject("sequence"); state.writer.StartArray("inputs"); - for (const Input &input : sequence.List()) { + for (const Input &input : sequence) { state.writer.StartObject(); if (input.InputIndex()) state.writer.Number("input-index", input.InputIndex()); @@ -356,7 +357,7 @@ void writeSequence(WriteState &state, const Sequence &sequence) { } void writeChase(WriteState &state, const Chase &chase) { - const std::vector &list = chase.GetSequence().List(); + const std::vector &list = chase.GetSequence(); for (const Input &input : list) writeControllable(state, *input.GetControllable()); @@ -370,7 +371,7 @@ void writeChase(WriteState &state, const Chase &chase) { } void writeTimeSequence(WriteState &state, const TimeSequence &timeSequence) { - const std::vector &list = timeSequence.Sequence().List(); + const std::vector &list = timeSequence.Sequence(); for (const Input &input : list) writeControllable(state, *input.GetControllable()); @@ -500,8 +501,8 @@ void writeSceneItem(WriteState &state, const SceneItem &item) { } void writeScene(WriteState &state, const Scene &scene) { - for (size_t i = 0; i != scene.NOutputs(); ++i) { - writeControllable(state, *scene.Output(i).first); + for (size_t i = 0; i != scene.NConnections(); ++i) { + writeControllable(state, *scene.GetConnection(i).first); } state.writer.StartObject(); diff --git a/tests/system/tfileformat.cpp b/tests/system/tfileformat.cpp index c760c0f..f4e2a89 100644 --- a/tests/system/tfileformat.cpp +++ b/tests/system/tfileformat.cpp @@ -82,8 +82,8 @@ void FillManagement(Management &management) { ObservingPtr chase = management.AddChasePtr(); chase->SetName("A chase"); subFolder.Add(chase); - chase->GetSequence().Add(*a, 0); - chase->GetSequence().Add(*b, 0); + std::vector sequence{{*a, 0}, {*b, 0}}; + chase->SetSequence(std::move(sequence)); management.AddSourceValue(*chase, 0); ObservingPtr timeSequence = management.AddTimeSequencePtr(); @@ -186,7 +186,7 @@ void CheckEqual(const Management &a, const Management &b) { BOOST_CHECK_EQUAL(a_fixture_control->Name(), "Control for RGBW fixture"); BOOST_CHECK_EQUAL(a_fixture_control->NInputs(), 3); // rgb filter will make it 3 - BOOST_CHECK_EQUAL(a_fixture_control->NOutputs(), 0); + BOOST_CHECK_EQUAL(a_fixture_control->NConnections(), 0); BOOST_CHECK_EQUAL( a.GetFixtureControl(a_fixture).Get(), &a.GetObjectFromPath( @@ -219,7 +219,7 @@ void CheckEqual(const Management &a, const Management &b) { "The root folder/A subfolder/A preset collection")); BOOST_CHECK_EQUAL(readCollection.Name(), "A preset collection"); BOOST_CHECK_EQUAL(readCollection.NInputs(), 1); - BOOST_CHECK_EQUAL(readCollection.NOutputs(), 2); + BOOST_CHECK_EQUAL(readCollection.NConnections(), 2); BOOST_CHECK_EQUAL(readCollection.PresetValues()[0]->Value().UInt(), ControlValue::MaxUInt() / 2); BOOST_CHECK_EQUAL(&readCollection.PresetValues()[0]->GetControllable(), @@ -239,10 +239,10 @@ void CheckEqual(const Management &a, const Management &b) { const Chase &readChase = static_cast( a.GetObjectFromPath("The root folder/A subfolder/A chase")); - BOOST_CHECK_EQUAL(readChase.GetSequence().Size(), 2); - BOOST_CHECK_EQUAL(readChase.GetSequence().List()[0].GetControllable(), + BOOST_CHECK_EQUAL(readChase.GetSequence().size(), 2); + BOOST_CHECK_EQUAL(readChase.GetSequence()[0].GetControllable(), &readCollection); - BOOST_CHECK_EQUAL(readChase.GetSequence().List()[0].InputIndex(), 0); + BOOST_CHECK_EQUAL(readChase.GetSequence()[0].InputIndex(), 0); const AudioLevelEffect *readEffect = dynamic_cast( &a.GetObjectFromPath("The root folder/Effect folder/An audio effect")); @@ -261,7 +261,8 @@ void CheckEqual(const Management &a, const Management &b) { const Controllable &controllable_b = *b.Controllables()[controllable_index]; BOOST_CHECK_EQUAL(controllable_a.FullPath(), controllable_b.FullPath()); BOOST_CHECK_EQUAL(controllable_a.NInputs(), controllable_b.NInputs()); - BOOST_CHECK_EQUAL(controllable_a.NOutputs(), controllable_b.NOutputs()); + BOOST_CHECK_EQUAL(controllable_a.NConnections(), + controllable_b.NConnections()); if (const Scene *scene_a = dynamic_cast(&controllable_a); scene_a) { const Scene *scene_b = dynamic_cast(&controllable_b); diff --git a/tests/theatre/tchase.cpp b/tests/theatre/tchase.cpp index 447af01..cdf15cb 100644 --- a/tests/theatre/tchase.cpp +++ b/tests/theatre/tchase.cpp @@ -38,10 +38,11 @@ BOOST_AUTO_TEST_CASE(remove_indirect) { pcB->SetFromCurrentSituation(management); ObservingPtr chase = management.AddChasePtr(); chase->SetName("chase"); - Sequence &sequence = chase->GetSequence(); root.Add(chase); - sequence.Add(*pcA, 0); - sequence.Add(*pcB, 0); + std::vector sequence; + sequence.emplace_back(*pcA, 0); + sequence.emplace_back(*pcB, 0); + chase->SetSequence(sequence); BOOST_CHECK_EQUAL(management.Controllables().size(), 4); // 1 preset, 2 collections, 1 chase management.RemoveControllable(*pcA); diff --git a/tests/theatre/tfixturecontrol.cpp b/tests/theatre/tfixturecontrol.cpp index e71b0fe..d054498 100644 --- a/tests/theatre/tfixturecontrol.cpp +++ b/tests/theatre/tfixturecontrol.cpp @@ -38,7 +38,7 @@ BOOST_AUTO_TEST_CASE(SetValue) { BOOST_CHECK_EQUAL(fixture.Functions().front()->MainChannel().Channel(), 100); BOOST_CHECK(!fixture.Functions().front()->FineChannel()); control->InputValue(0) = ControlValue::Zero(); - control->MixInput(0, ControlValue::Max()); + control->MixInput(0, ControlValue::Max(), 0); std::vector values(512, 0); Timing timing(0.0, 0, 0, 0, 0); control->Mix(timing, true); diff --git a/tests/theatre/tfolder.cpp b/tests/theatre/tfolder.cpp index 5f64227..39cd409 100644 --- a/tests/theatre/tfolder.cpp +++ b/tests/theatre/tfolder.cpp @@ -158,7 +158,8 @@ BOOST_AUTO_TEST_CASE(RemoveFolder) { ObservingPtr c = management.AddChasePtr(); c->SetName("c"); folder.Add(c); - c->GetSequence().Add(*control, 0); + std::vector sequence{{*control, 0}}; + c->SetSequence(std::move(sequence)); ts1->AddStep(*c, 0); diff --git a/tests/theatre/tpresetcollection.cpp b/tests/theatre/tpresetcollection.cpp index 0c0b9ec..5dc537d 100644 --- a/tests/theatre/tpresetcollection.cpp +++ b/tests/theatre/tpresetcollection.cpp @@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE(SetValue) { fixtureControl.InputValue(0) = ControlValue::Zero(); presetCollection.InputValue(0) = ControlValue::Zero(); - presetCollection.MixInput(0, ControlValue::Max()); + presetCollection.MixInput(0, ControlValue::Max(), 0); std::vector values(512, 0); Timing timing(0.0, 0, 0, 0, 0); diff --git a/tests/theatre/ttheatre.cpp b/tests/theatre/ttheatre.cpp index 9a91bc7..6a8ef16 100644 --- a/tests/theatre/ttheatre.cpp +++ b/tests/theatre/ttheatre.cpp @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(add_fixture) { BOOST_CHECK(control.InputColor(0) == Color(255, 0, 0)); BOOST_CHECK(control.InputColor(1) == Color(0, 255, 0)); BOOST_CHECK(control.InputColor(2) == Color(0, 0, 255)); - BOOST_CHECK_EQUAL(control.NOutputs(), 0); + BOOST_CHECK_EQUAL(control.NConnections(), 0); BOOST_CHECK(control.InputType(0) != control.InputType(1)); BOOST_CHECK(control.InputType(1) != control.InputType(2)); } diff --git a/tests/theatre/ttransition.cpp b/tests/theatre/ttransition.cpp index 742c82d..132fdfd 100644 --- a/tests/theatre/ttransition.cpp +++ b/tests/theatre/ttransition.cpp @@ -7,6 +7,7 @@ namespace glight { +using theatre::Connection; using theatre::ControlValue; using theatre::Timing; using theatre::Transition; @@ -58,34 +59,46 @@ BOOST_AUTO_TEST_CASE(fade_mix) { const Transition t(500.0, TransitionType::Fade); VariableEffect result_a; - t.Mix(result_a, 0, result_a, 1, 0.0, ControlValue::Max(), timing); + Connection a0{&result_a, 0, {0, 0}}; + Connection a1{&result_a, 1, {0, 0}}; + t.Mix(a0, a1, 0.0, ControlValue::Max(), timing, true); BOOST_CHECK_EQUAL(result_a.InputValue(0).ToUChar(), 255); BOOST_CHECK_EQUAL(result_a.InputValue(1).ToUChar(), 0); VariableEffect result_b; - t.Mix(result_b, 0, result_b, 1, 500.0, ControlValue::Max() / 2, timing); + Connection b0{&result_b, 0, {0, 0}}; + Connection b1{&result_b, 1, {0, 0}}; + t.Mix(b0, b1, 500.0, ControlValue::Max() / 2, timing, true); BOOST_CHECK_EQUAL(result_b.InputValue(0).ToUChar(), 0); BOOST_CHECK_EQUAL(result_b.InputValue(1).ToUChar(), 127); VariableEffect result_c; - t.Mix(result_c, 0, result_c, 1, 125.0, ControlValue::Max(), timing); + Connection c0{&result_c, 0, {0, 0}}; + Connection c1{&result_c, 1, {0, 0}}; + t.Mix(c0, c1, 125.0, ControlValue::Max(), timing, true); BOOST_CHECK_EQUAL(result_c.InputValue(0).ToUChar(), 192); BOOST_CHECK_EQUAL(result_c.InputValue(1).ToUChar(), 63); // Test for time values outside the transition range VariableEffect result_d; - t.Mix(result_d, 0, result_d, 1, -100.0, ControlValue::Max(), timing); + Connection d0{&result_d, 0, {0, 0}}; + Connection d1{&result_d, 1, {0, 0}}; + t.Mix(d0, d1, -100.0, ControlValue::Max(), timing, true); BOOST_CHECK_EQUAL(result_d.InputValue(0).ToUChar(), 255); BOOST_CHECK_EQUAL(result_d.InputValue(1).ToUChar(), 0); VariableEffect result_e; - t.Mix(result_e, 0, result_e, 1, 600.0, ControlValue::Max(), timing); + Connection e0{&result_e, 0, {0, 0}}; + Connection e1{&result_e, 1, {0, 0}}; + t.Mix(e0, e1, 600.0, ControlValue::Max(), timing, true); BOOST_CHECK_EQUAL(result_e.InputValue(0).ToUChar(), 0); BOOST_CHECK_EQUAL(result_e.InputValue(1).ToUChar(), 255); // Test for too high control values VariableEffect result_f; - t.Mix(result_f, 0, result_f, 1, 500.0, ControlValue::Max() * 5u / 4, timing); + Connection f0{&result_f, 0, {0, 0}}; + Connection f1{&result_f, 1, {0, 0}}; + t.Mix(f0, f1, 500.0, ControlValue::Max() * 5u / 4, timing, true); BOOST_CHECK_EQUAL(result_f.InputValue(0).ToUChar(), 0); BOOST_CHECK_EQUAL(result_f.InputValue(1).ToUChar(), 255); } diff --git a/theatre/chase.h b/theatre/chase.h index 63da456..82496c1 100644 --- a/theatre/chase.h +++ b/theatre/chase.h @@ -1,149 +1,164 @@ #ifndef THEATRE_CHASE_H_ #define THEATRE_CHASE_H_ +#include +#include +#include + #include "controllable.h" -#include "sequence.h" +#include "controlvalue.h" +#include "input.h" #include "timing.h" #include "transition.h" #include "trigger.h" namespace glight::theatre { -/** - @author Andre Offringa -*/ class Chase final : public Controllable { public: - Chase() : _phaseOffset(0.0) {} + Chase() = default; size_t NInputs() const override { return 1; } - ControlValue &InputValue(size_t) override { return _inputValue; } + ControlValue &InputValue(size_t) override { return input_value_; } virtual FunctionType InputType(size_t) const override { return FunctionType::Master; } - size_t NOutputs() const override { return _sequence.List().size(); } + size_t NConnections() const override { return sequence_.size(); } - std::pair Output(size_t index) const override { - const Input &input = _sequence.List()[index]; - return std::pair(input.GetControllable(), - input.InputIndex()); + std::pair GetConnection( + size_t index) const override { + const Input &to_input = sequence_[index]; + return std::pair(to_input.GetControllable(), + to_input.InputIndex()); } - virtual void Mix(const Timing &timing, bool primary) override { + void Mix(const Timing &timing, bool primary) override { // Slowly drive the phase offset back to zero. - if (_phaseOffset != 0.0) { - if (_phaseOffset > 8.0) - _phaseOffset -= 8.0; - else if (_phaseOffset < -8.0) - _phaseOffset += 8.0; + if (phase_offset_ != 0.0) { + if (phase_offset_ > 8.0) + phase_offset_ -= 8.0; + else if (phase_offset_ < -8.0) + phase_offset_ += 8.0; else - _phaseOffset = 0.0; + phase_offset_ = 0.0; } - switch (_trigger.Type()) { + switch (trigger_.Type()) { case TriggerType::Delay: - mixDelayChase(timing); + MixDelayChase(timing, primary); break; case TriggerType::Sync: - mixSyncedChase(timing); + MixSyncedChase(timing, primary); break; case TriggerType::Beat: - mixBeatChase(timing); + MixBeatChase(timing, primary); break; } } - const Transition &GetTransition() const { return _transition; } - Transition &GetTransition() { return _transition; } + const Transition &GetTransition() const { return transition_; } + Transition &GetTransition() { return transition_; } - const Trigger &GetTrigger() const { return _trigger; } - Trigger &GetTrigger() { return _trigger; } + const Trigger &GetTrigger() const { return trigger_; } + Trigger &GetTrigger() { return trigger_; } - const Sequence &GetSequence() const { return _sequence; } - Sequence &GetSequence() { return _sequence; } + const std::vector &GetSequence() const { return sequence_; } + template + void SetSequence(InputVector &&sequence) { + sequence_ = std::forward(sequence); + connection_values_.resize(sequence_.size()); + } void ShiftDelayTrigger(double triggerTime, double transitionTime, double currentTime) { - double currentDuration = _trigger.DelayInMs() + _transition.LengthInMs(); - double currentPhase = std::fmod(currentTime + _phaseOffset, - currentDuration * _sequence.Size()); + double currentDuration = trigger_.DelayInMs() + transition_.LengthInMs(); + double currentPhase = std::fmod(currentTime + phase_offset_, + currentDuration * sequence_.size()); double stepPhase = std::fmod(currentPhase, currentDuration); unsigned step = - (unsigned)fmod(currentPhase / currentDuration, _sequence.Size()); + (unsigned)fmod(currentPhase / currentDuration, sequence_.size()); double newStepDuration = (triggerTime + transitionTime); - double newDuration = newStepDuration * _sequence.Size(); - if (stepPhase < _trigger.DelayInMs()) { + double newDuration = newStepDuration * sequence_.size(); + if (stepPhase < trigger_.DelayInMs()) { // No transition is ongoing // Find an offset such that // (time + _phaseOffset) % duration = step*duration + stepPhase*old/new // phaseOffset = (step*stepDuration + stepPhase*old/new - time) % duration - _phaseOffset = std::fmod( + phase_offset_ = std::fmod( step * newStepDuration + - stepPhase * triggerTime / _trigger.DelayInMs() - currentTime, + stepPhase * triggerTime / trigger_.DelayInMs() - currentTime, newDuration); } else { // Transition ongoing: shift to the relative position inside the // transition Find an offset such that (time + _phaseOffset) % duration = // step*duration + stepPhase*old/new + trigger phaseOffset = // (step*stepDuration + transPhase*old/new + trigger - time) % duration - _phaseOffset = + phase_offset_ = std::fmod(step * newStepDuration + - (stepPhase - _trigger.DelayInMs()) * transitionTime / - _transition.LengthInMs() + + (stepPhase - trigger_.DelayInMs()) * transitionTime / + transition_.LengthInMs() + triggerTime - currentTime, newDuration); } - _trigger.SetDelayInMs(triggerTime); - _transition.SetLengthInMs(transitionTime); + trigger_.SetDelayInMs(triggerTime); + transition_.SetLengthInMs(transitionTime); } - void ResetPhaseOffset() { _phaseOffset = 0.0; } + void ResetPhaseOffset() { phase_offset_ = 0.0; } private: - void mixBeatChase(const Timing &timing) { + void MixBeatChase(const Timing &timing, bool primary) { double timeInMs = timing.BeatValue(); - unsigned step = - (unsigned)fmod(timeInMs / _trigger.DelayInBeats(), _sequence.Size()); - _sequence.List()[step].GetControllable()->MixInput( - _sequence.List()[step].InputIndex(), _inputValue); + unsigned step = (unsigned)std::fmod(timeInMs / trigger_.DelayInBeats(), + sequence_.size()); + sequence_[step].GetControllable()->MixInput( + sequence_[step].InputIndex(), input_value_, + connection_values_[step][primary]); } - void mixSyncedChase(const Timing &timing) { + void MixSyncedChase(const Timing &timing, bool primary) { unsigned step = - (timing.TimestepNumber() / _trigger.DelayInSyncs()) % _sequence.Size(); - _sequence.List()[step].GetControllable()->MixInput( - _sequence.List()[step].InputIndex(), _inputValue); + (timing.TimestepNumber() / trigger_.DelayInSyncs()) % sequence_.size(); + sequence_[step].GetControllable()->MixInput( + sequence_[step].InputIndex(), input_value_, + connection_values_[step][primary]); } - void mixDelayChase(const Timing &timing) { - double timeInMs = timing.TimeInMS() + _phaseOffset; - double totalDuration = _trigger.DelayInMs() + _transition.LengthInMs(); + void MixDelayChase(const Timing &timing, bool primary) { + double timeInMs = timing.TimeInMS() + phase_offset_; + double totalDuration = trigger_.DelayInMs() + transition_.LengthInMs(); double phase = std::fmod(timeInMs, totalDuration); - unsigned step = (unsigned)fmod(timeInMs / totalDuration, _sequence.Size()); - if (phase < _trigger.DelayInMs()) { + unsigned step = + (unsigned)std::fmod(timeInMs / totalDuration, sequence_.size()); + if (phase < trigger_.DelayInMs()) { // We are not in a transition, just mix the corresponding controllable - _sequence.List()[step].GetControllable()->MixInput( - _sequence.List()[step].InputIndex(), _inputValue); + sequence_[step].GetControllable()->MixInput( + sequence_[step].InputIndex(), input_value_, + connection_values_[step][primary]); } else { // We are in a transition - const double transition_time = phase - _trigger.DelayInMs(); - Controllable &first = *_sequence.List()[step].GetControllable(); - Controllable &second = - *_sequence.List()[(step + 1) % _sequence.Size()].GetControllable(); - _transition.Mix( - first, _sequence.List()[step].InputIndex(), second, - _sequence.List()[(step + 1) % _sequence.Size()].InputIndex(), - transition_time, _inputValue, timing); + const double transition_time = phase - trigger_.DelayInMs(); + Connection first = {.to_controllable = sequence_[step].GetControllable(), + .to_input_index = sequence_[step].InputIndex(), + .values = connection_values_[step]}; + const size_t next_step = (step + 1) % sequence_.size(); + Connection second = { + .to_controllable = sequence_[next_step].GetControllable(), + .to_input_index = sequence_[next_step].InputIndex(), + .values = connection_values_[next_step]}; + transition_.Mix(first, second, transition_time, input_value_, timing, + primary); } } - ControlValue _inputValue; - Sequence _sequence; - Trigger _trigger; - Transition _transition; - double _phaseOffset; + ControlValue input_value_; + std::vector sequence_; + Trigger trigger_; + Transition transition_; + double phase_offset_ = 0.0; + std::vector> connection_values_; }; } // namespace glight::theatre diff --git a/theatre/controllable.h b/theatre/controllable.h index cec7e82..aae6bf8 100644 --- a/theatre/controllable.h +++ b/theatre/controllable.h @@ -1,7 +1,10 @@ #ifndef THEATRE_CONTROL_H_ #define THEATRE_CONTROL_H_ +#include #include +#include +#include #include "color.h" #include "controlvalue.h" @@ -10,8 +13,15 @@ namespace glight::theatre { +class Controllable; class Timing; +struct Connection { + Controllable *to_controllable; + size_t to_input_index; + std::array values; +}; + /** * A Controllable has a number of inputs and optionally some outputs * that this controllable controls. @@ -19,12 +29,11 @@ class Timing; */ class Controllable : public FolderObject { public: - Controllable() : _visitLevel(0) {} + Controllable() = default; - Controllable(const Controllable &source) - : FolderObject(source), _visitLevel(0) {} + Controllable(const Controllable &source) : FolderObject(source) {} - Controllable(const std::string &name) : FolderObject(name), _visitLevel(0) {} + Controllable(const std::string &name) : FolderObject(name) {} virtual size_t NInputs() const = 0; @@ -32,24 +41,33 @@ class Controllable : public FolderObject { virtual FunctionType InputType(size_t index) const = 0; - virtual size_t NOutputs() const = 0; + /** + * Number of output connections this controllable has from one of its outputs + * to the input of another controllable. + */ + virtual size_t NConnections() const = 0; - virtual std::pair Output( + /** + * Get information about a connection. A connection starts from the + * output of this controllable and connects to the input of another + * controllable. + */ + virtual std::pair GetConnection( size_t index) const = 0; - std::pair Output(size_t index) { + std::pair GetConnection(size_t index) { const std::pair output = - const_cast(this)->Output(index); + const_cast(this)->GetConnection(index); return std::make_pair(const_cast(output.first), output.second); } - virtual std::vector InputColors([[maybe_unused]] size_t index) const { + virtual std::vector InputColors(size_t index) const { // Return the colours that it connects to std::vector colors; - colors.reserve(NOutputs()); - for (size_t o = 0; o != NOutputs(); ++o) { - const auto output = Output(o); + colors.reserve(NConnections()); + for (size_t o = 0; o != NConnections(); ++o) { + const auto output = GetConnection(o); const std::vector c = output.first->InputColors(output.second); colors.insert(colors.end(), c.begin(), c.end()); } @@ -73,15 +91,16 @@ class Controllable : public FolderObject { /** * Sets the value at the controllable's input. */ - void MixInput(size_t index, const ControlValue &value) { - const unsigned mixVal = ControlValue::Mix(InputValue(index).UInt(), - value.UInt(), MixStyle::Default); - InputValue(index) = ControlValue(mixVal); + void MixInput(size_t index, ControlValue new_value, + ControlValue previous_value) { + const FunctionType input_type = InputType(index); + InputValue(index) = theatre::MixInput(InputValue(index), new_value, + previous_value, input_type); } bool HasOutputConnection(const Controllable &controllable) const { - for (size_t i = 0; i != NOutputs(); ++i) - if (Output(i).first == &controllable) return true; + for (size_t i = 0; i != NConnections(); ++i) + if (GetConnection(i).first == &controllable) return true; return false; } @@ -90,9 +109,9 @@ class Controllable : public FolderObject { void SetVisitLevel(char visitLevel) { _visitLevel = visitLevel; } + protected: private: - ControlValue _inputValue; - char _visitLevel; + char _visitLevel = 0; }; } // namespace glight::theatre diff --git a/theatre/controlvalue.h b/theatre/controlvalue.h index 5edebc3..bc84646 100644 --- a/theatre/controlvalue.h +++ b/theatre/controlvalue.h @@ -1,6 +1,7 @@ #ifndef THEATRE_CONTROLVALUE_H_ #define THEATRE_CONTROLVALUE_H_ +#include "functiontype.h" #include "mixstyle.h" #include @@ -15,12 +16,20 @@ namespace glight::theatre { */ class ControlValue { public: - constexpr ControlValue() noexcept : _value(0) {} - constexpr explicit ControlValue(unsigned value) noexcept : _value(value) {} + constexpr ControlValue() noexcept : value_(0) {} + constexpr ControlValue(uint32_t value) noexcept : value_(value) {} - constexpr explicit operator bool() const noexcept { return _value != 0; } + constexpr ControlValue(const ControlValue& source) noexcept = default; + constexpr ControlValue& operator=(const ControlValue& rhs) noexcept = default; + ControlValue& operator+=(ControlValue value) noexcept { + value_ += value.UInt(); + return *this; + } - constexpr unsigned int UInt() const noexcept { return _value; } + constexpr explicit operator bool() const noexcept { return value_ != 0; } + constexpr explicit operator uint32_t() const noexcept { return value_; } + + constexpr uint32_t UInt() const noexcept { return value_; } constexpr static ControlValue Zero() noexcept { return ControlValue(0); } constexpr static ControlValue Max() noexcept { @@ -28,21 +37,21 @@ class ControlValue { } constexpr static ControlValue FromRatio(double ratio) noexcept { return ControlValue( - static_cast(std::clamp(ratio, 0.0, 1.0) * MaxUInt())); + static_cast(std::clamp(ratio, 0.0, 1.0) * MaxUInt())); } - constexpr static ControlValue FromUChar(unsigned char value) noexcept { - return ControlValue(static_cast(value) * MaxUInt() / 255); + constexpr static ControlValue FromUChar(uint8_t value) noexcept { + return ControlValue(static_cast(value) * MaxUInt() / 255); } - constexpr static unsigned MaxUInt() noexcept { return (1 << 24) - 1; } + constexpr static uint32_t MaxUInt() noexcept { return (1 << 24) - 1; } - constexpr static unsigned Invert(unsigned value) noexcept { + constexpr static uint32_t Invert(uint32_t value) noexcept { return MaxUInt() - value; } - constexpr static unsigned CharToValue(unsigned char value) noexcept { - return (static_cast(value) * MaxUInt()) / 255; + constexpr static uint32_t CharToValue(uint8_t value) noexcept { + return (static_cast(value) * MaxUInt()) / 255; } - static unsigned Mix(unsigned firstValue, unsigned secondValue, + static uint32_t Mix(uint32_t firstValue, uint32_t secondValue, MixStyle mixStyle) noexcept { switch (mixStyle) { default: @@ -68,16 +77,16 @@ class ControlValue { } } - constexpr static unsigned MultiplyValues(unsigned first, - unsigned second) noexcept { + constexpr static uint32_t MultiplyValues(uint32_t first, + uint32_t second) noexcept { if (first >= MaxUInt() && second >= MaxUInt()) return MaxUInt(); first >>= 9; second >>= 9; return (first * second) >> 6; } - constexpr static unsigned Fraction(unsigned numerator, - unsigned denominator) noexcept { + constexpr static uint32_t Fraction(uint32_t numerator, + uint32_t denominator) noexcept { if (denominator == 0) { return numerator == 0 ? 0 : MaxUInt(); } else { @@ -85,7 +94,7 @@ class ControlValue { (static_cast(numerator) << 24u); // to 48 bits const uint64_t d = denominator; // remain 24 bits return std::min(MaxUInt(), - static_cast(n / d)); // from 48 bit to 24 bit + static_cast(n / d)); // from 48 bit to 24 bit } } @@ -97,22 +106,18 @@ class ControlValue { return primaryStyle; } constexpr double Ratio() const noexcept { - return (double)_value / (double)((1 << 24) - 1); + return (double)value_ / (double)((1 << 24) - 1); } constexpr double RoundedPercentage() const noexcept { - return std::round(1000.0 * (double)_value / (double)((1 << 24) - 1)) * 0.1; + return std::round(1000.0 * (double)value_ / (double)((1 << 24) - 1)) * 0.1; } - constexpr unsigned char ToUChar() const noexcept { - return std::min(_value, ControlValue::MaxUInt()) >> 16; - } - void Set(unsigned int uintValue) noexcept { _value = uintValue; } - ControlValue& operator+=(ControlValue value) noexcept { - _value += value.UInt(); - return *this; + constexpr uint8_t ToUChar() const noexcept { + return std::min(value_, ControlValue::MaxUInt()) >> 16; } + void Set(uint32_t uintValue) noexcept { value_ = uintValue; } private: - unsigned int _value; + uint32_t value_; }; inline constexpr bool operator==(const ControlValue& lhs, @@ -136,7 +141,7 @@ inline constexpr ControlValue operator*(const ControlValue& lhs, } inline constexpr ControlValue operator*(const ControlValue& lhs, - unsigned factor) noexcept { + uint32_t factor) noexcept { return ControlValue(lhs.UInt() * factor); } @@ -149,7 +154,7 @@ inline constexpr ControlValue operator*(const ControlValue& lhs, } inline constexpr ControlValue operator/(const ControlValue& lhs, - unsigned factor) noexcept { + uint32_t factor) noexcept { return ControlValue(lhs.UInt() / factor); } @@ -182,13 +187,26 @@ inline ControlValue Max(const ControlValue& first, const ControlValue& second, return ControlValue(std::max(first.UInt(), Max(second, third...).UInt())); } -inline ControlValue Mix(const ControlValue& firstValue, - const ControlValue& secondValue, +inline ControlValue Mix(ControlValue firstValue, ControlValue secondValue, MixStyle mixStyle) noexcept { return ControlValue( ControlValue::Mix(firstValue.UInt(), secondValue.UInt(), mixStyle)); } +inline ControlValue MixInput(ControlValue input, ControlValue mix, + ControlValue previous_mix, + FunctionType function_type) noexcept { + const MixStyle style = GetMixStyle(function_type); + if (style == MixStyle::LastTakesPrecedence) { + if (mix != previous_mix) + return mix; + else + return input; + } else { + return ControlValue(ControlValue::Mix(input.UInt(), mix.UInt(), style)); + } +} + inline std::string ToString(const ControlValue& value) { std::ostringstream str; str << value.UInt() << " (" << std::round(value.Ratio() * 100.0) << "%)"; diff --git a/theatre/design/autodesign.cpp b/theatre/design/autodesign.cpp index b34130b..a795d0d 100644 --- a/theatre/design/autodesign.cpp +++ b/theatre/design/autodesign.cpp @@ -29,7 +29,7 @@ Chase &AutoDesign::MakeRunningLight(const DesignInfo &design, chase->SetName(GetValidName(design, "Runchase")); design.destination->Add(chase); design.management->AddSourceValue(*chase, 0); - Sequence &seq = chase->GetSequence(); + std::vector seq; size_t frames; if (runType == RunType::InwardRun || runType == RunType::OutwardRun) frames = (colors.size() + 1) / 2; @@ -96,13 +96,14 @@ Chase &AutoDesign::MakeRunningLight(const DesignInfo &design, } } } - seq.Add(*pc, 0); + seq.emplace_back(*pc, 0); design.management->AddSourceValue(*pc, 0); } if (runType == RunType::BackAndForthRun) { for (size_t i = 2; i < colors.size(); ++i) - seq.Add(*seq.List()[colors.size() - i].GetControllable(), 0); + seq.emplace_back(*seq[colors.size() - i].GetControllable(), 0); } + chase->SetSequence(std::move(seq)); return *chase; } @@ -116,7 +117,7 @@ Chase &AutoDesign::MakeColorVariation( Chase &chase = *chase_ptr; chase.SetName(GetValidName(design, "Colorvar")); management.AddSourceValue(chase, 0); - Sequence &seq = chase.GetSequence(); + std::vector seq; std::random_device rd; std::mt19937 rnd(rd()); std::normal_distribution distribution(0.0, variation); @@ -148,9 +149,10 @@ Chase &AutoDesign::MakeColorVariation( design.deduction); } } - seq.Add(pc, 0); + seq.emplace_back(pc, 0); management.AddSourceValue(pc, 0); } + chase.SetSequence(std::move(seq)); return chase; } @@ -164,7 +166,7 @@ Chase &AutoDesign::MakeColorShift(const DesignInfo &design, chase.SetName(GetValidName(design, "Colourshift")); destination.Add(chase_ptr); management.AddSourceValue(chase, 0); - Sequence &seq = chase.GetSequence(); + std::vector seq; size_t frames = colors.size(); std::vector> pos(frames); std::random_device rd; @@ -220,13 +222,14 @@ Chase &AutoDesign::MakeColorShift(const DesignInfo &design, AddPresetValue(management, *(*design.controllables)[cIndex], pc, colors[colourIndex], design.deduction); } - seq.Add(pc, 0); + seq.emplace_back(pc, 0); management.AddSourceValue(pc, 0); } if (shiftType == ShiftType::BackAndForthShift) { for (size_t i = 2; i < frames; ++i) - seq.Add(*seq.List()[frames - i].GetControllable(), 0); + seq.emplace_back(*seq[frames - i].GetControllable(), 0); } + chase.SetSequence(std::move(seq)); return chase; } @@ -314,7 +317,7 @@ Chase &AutoDesign::MakeIncreasingChase( chase.SetName(GetValidName(design, "Increasing chase")); destination.Add(std::move(chase_ptr)); management.AddSourceValue(chase, 0); - Sequence &seq = chase.GetSequence(); + std::vector seq; size_t nFix = design.controllables->size(); for (size_t frameIndex = 0; frameIndex != nFix * 2; ++frameIndex) { @@ -358,9 +361,10 @@ Chase &AutoDesign::MakeIncreasingChase( AddPresetValue(management, *(*design.controllables)[i], pc, colors[i], design.deduction); } - seq.Add(pc, 0); + seq.emplace_back(pc, 0); management.AddSourceValue(pc, 0); } + chase.SetSequence(std::move(seq)); return chase; } diff --git a/theatre/design/rotation.cpp b/theatre/design/rotation.cpp index 5deb6e0..55650c6 100644 --- a/theatre/design/rotation.cpp +++ b/theatre/design/rotation.cpp @@ -7,6 +7,7 @@ #include "theatre/folder.h" #include "theatre/management.h" +#include "theatre/presetcollection.h" #include "theatre/timesequence.h" namespace glight::theatre { diff --git a/theatre/effect.h b/theatre/effect.h index 4949ce6..885e93a 100644 --- a/theatre/effect.h +++ b/theatre/effect.h @@ -1,14 +1,14 @@ #ifndef THEATRE_EFFECT_H_ #define THEATRE_EFFECT_H_ +#include "controllable.h" #include "effecttype.h" #include "folderobject.h" -#include "../theatre/controllable.h" - #include #include +#include #include #include @@ -19,8 +19,8 @@ class Effect : public Controllable { Effect(size_t n_inputs) : input_values_(n_inputs, ControlValue()) {} virtual ~Effect() { - while (!outputs_.empty()) { - RemoveConnection(outputs_.size() - 1); + while (!connections_.empty()) { + RemoveConnection(connections_.size() - 1); } } @@ -29,7 +29,8 @@ class Effect : public Controllable { static std::unique_ptr Make(EffectType type); void AddConnection(Controllable &controllable, size_t input) { - outputs_.emplace_back(&controllable, input); + connections_.emplace_back(&controllable, input); + connection_values_.push_back({ControlValue::Zero(), ControlValue::Zero()}); on_delete_connections_.emplace_back( controllable.SignalDelete().connect([&controllable, input, this]() { RemoveConnection(controllable, input); @@ -37,24 +38,26 @@ class Effect : public Controllable { } void RemoveConnection(Controllable &controllable, size_t input) { - std::vector>::iterator item = std::find( - outputs_.begin(), outputs_.end(), std::make_pair(&controllable, input)); - if (item == outputs_.end()) + std::vector>::iterator item = + std::find(connections_.begin(), connections_.end(), + std::make_pair(&controllable, input)); + if (item == connections_.end()) throw std::runtime_error( "RemoveConnection() called for unconnected controllable"); // convert to index to also remove corresponding connection - size_t index = item - outputs_.begin(); + size_t index = item - connections_.begin(); RemoveConnection(index); } void RemoveConnection(size_t index) { - outputs_.erase(outputs_.begin() + index); + connections_.erase(connections_.begin() + index); + connection_values_.erase(connection_values_.begin() + index); on_delete_connections_[index].disconnect(); on_delete_connections_.erase(on_delete_connections_.begin() + index); } const std::vector> &Connections() const { - return outputs_; + return connections_; } std::unique_ptr Copy() const; @@ -69,11 +72,11 @@ class Effect : public Controllable { return FunctionType::Master; } - size_t NOutputs() const final override { return outputs_.size(); } + size_t NConnections() const final override { return connections_.size(); } - std::pair Output( + std::pair GetConnection( size_t index) const final override { - return outputs_[index]; + return connections_[index]; } void Mix(const Timing &timing, bool primary) final override { @@ -89,16 +92,26 @@ class Effect : public Controllable { * inputs are where the values are stored, this implies that this * function sets the inputs of the connected objects. */ - void setAllOutputs(const ControlValue &value) const { - for (const std::pair &connection : Connections()) - connection.first->MixInput(connection.second, value); + void setAllOutputs(ControlValue value, bool primary) const { + for (size_t i = 0; i != connections_.size(); ++i) { + MixConnection(i, value, primary); + } + } + + void MixConnection(size_t connection_index, ControlValue value, + bool primary) const { + const std::pair &connection = + connections_[connection_index]; + connection.first->MixInput(connection.second, value, + connection_values_[connection_index][primary]); } private: friend class EffectControl; std::vector input_values_; - std::vector> outputs_; + std::vector> connections_; + std::vector> connection_values_; std::vector on_delete_connections_; }; diff --git a/theatre/effects/audioleveleffect.h b/theatre/effects/audioleveleffect.h index 2a37822..eabc360 100644 --- a/theatre/effects/audioleveleffect.h +++ b/theatre/effects/audioleveleffect.h @@ -29,7 +29,7 @@ class AudioLevelEffect final : public Effect { unsigned audioLevel = (unsigned(timing.AudioLevel()) << 8); double timePassed = timing.TimeInMS() - _lastTime[primary]; _lastTime[primary] = timing.TimeInMS(); - unsigned decay = + const unsigned decay = unsigned(std::min(timePassed * _decaySpeed, (1 << 24) - 1)); if (_lastValue[primary] < decay) _lastValue[primary] = 0; @@ -40,9 +40,7 @@ class AudioLevelEffect final : public Effect { unsigned v = ControlValue::Mix(_lastValue[primary], values[0].UInt(), MixStyle::Multiply); ControlValue audioLevelCV(v); - for (const std::pair &connection : Connections()) { - connection.first->MixInput(connection.second, audioLevelCV); - } + setAllOutputs(audioLevelCV, primary); } private: diff --git a/theatre/effects/colorcontroleffect.h b/theatre/effects/colorcontroleffect.h index a58326d..b5fdaee 100644 --- a/theatre/effects/colorcontroleffect.h +++ b/theatre/effects/colorcontroleffect.h @@ -43,49 +43,52 @@ class ColorControlEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &timing, bool primary) override { - for (const std::pair &connection : Connections()) { + for (size_t connection_index = 0; connection_index != NConnections(); + ++connection_index) { + const std::pair &connection = + GetConnection(connection_index); switch (connection.first->InputType(connection.second)) { case FunctionType::Red: { const ControlValue v = values[0] * values[1]; - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::Green: { const ControlValue v = values[0] * values[2]; - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::Blue: { const ControlValue v = values[0] * values[3]; - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::White: { const ControlValue v = values[0] * DeduceWhite(values[1], values[2], values[3]); - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::Amber: { const ControlValue v = values[0] * DeduceAmber(values[1], values[2], values[3]); - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::UV: { const ControlValue v = values[0] * DeduceUv(values[1], values[2], values[3]); - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::Lime: { const ControlValue v = values[0] * DeduceLime(values[1], values[2], values[3]); - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::ColdWhite: { const ControlValue v = values[0] * DeduceColdWhite(values[1], values[2], values[3]); - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; case FunctionType::WarmWhite: { const ControlValue v = values[0] * DeduceWarmWhite(values[1], values[2], values[3]); - connection.first->MixInput(connection.second, v); + MixConnection(connection_index, v, primary); } break; default: break; diff --git a/theatre/effects/colortemperatureeffect.h b/theatre/effects/colortemperatureeffect.h index fa144f3..a146d09 100644 --- a/theatre/effects/colortemperatureeffect.h +++ b/theatre/effects/colortemperatureeffect.h @@ -34,32 +34,38 @@ class ColorTemperatureEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &, - bool) override { + bool primary) override { const unsigned range = std::min(40000u, max_temperature_ - min_temperature_); const unsigned scaled_value = values[0].UInt() >> 14; // make 10 bit const unsigned temperature = min_temperature_ + ((range * scaled_value) >> 10); const theatre::Color rgb = system::TemperatureToRgb(temperature); - for (const std::pair &connection : Connections()) { + for (size_t connection_index = 0; connection_index != NConnections(); + ++connection_index) { + const std::pair &connection = + GetConnection(connection_index); switch (connection.first->InputType(connection.second)) { case FunctionType::Red: - connection.first->MixInput( - connection.second, - ControlValue(static_cast(rgb.Red()) << 16) * values[1]); + MixConnection( + connection_index, + ControlValue(static_cast(rgb.Red()) << 16) * values[1], + primary); break; case FunctionType::Green: - connection.first->MixInput( - connection.second, - ControlValue(static_cast(rgb.Green()) << 16) * values[1]); + MixConnection( + connection_index, + ControlValue(static_cast(rgb.Green()) << 16) * values[1], + primary); break; case FunctionType::Blue: - connection.first->MixInput( - connection.second, - ControlValue(static_cast(rgb.Blue()) << 16) * values[1]); + MixConnection( + connection_index, + ControlValue(static_cast(rgb.Blue()) << 16) * values[1], + primary); break; case FunctionType::White: - connection.first->MixInput(connection.second, values[1]); + MixConnection(connection_index, values[1], primary); break; case FunctionType::Amber: // TODO diff --git a/theatre/effects/constantvalueeffect.h b/theatre/effects/constantvalueeffect.h index ae3dc01..6d970ff 100644 --- a/theatre/effects/constantvalueeffect.h +++ b/theatre/effects/constantvalueeffect.h @@ -22,7 +22,7 @@ class ConstantValueEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &timing, bool primary) override { - setAllOutputs(ControlValue(_value)); + setAllOutputs(ControlValue(_value), primary); } private: diff --git a/theatre/effects/curveeffect.h b/theatre/effects/curveeffect.h index 67af44a..a2a26e3 100644 --- a/theatre/effects/curveeffect.h +++ b/theatre/effects/curveeffect.h @@ -32,7 +32,7 @@ class CurveEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &timing, bool primary) override { - unsigned value = values[0].UInt(); + uint32_t value = values[0].UInt(); switch (_function) { case Linear: break; @@ -67,7 +67,7 @@ class CurveEffect final : public Effect { std::sqrt(double(ControlValue::MaxUInt())); } break; } - setAllOutputs(ControlValue(value)); + setAllOutputs(ControlValue(value), primary); } private: diff --git a/theatre/effects/delayeffect.h b/theatre/effects/delayeffect.h index 91817db..007aef5 100644 --- a/theatre/effects/delayeffect.h +++ b/theatre/effects/delayeffect.h @@ -57,10 +57,7 @@ class DelayEffect final : public Effect { _bufferReadPos[primary] = (_bufferReadPos[primary] + 1) % buffer.size(); } } - for (const std::pair &connection : Connections()) { - connection.first->MixInput(connection.second, - buffer[_bufferReadPos[primary]].second); - } + setAllOutputs(buffer[_bufferReadPos[primary]].second, primary); } private: diff --git a/theatre/effects/dispensereffect.h b/theatre/effects/dispensereffect.h index e4fd84d..ed2d9c5 100644 --- a/theatre/effects/dispensereffect.h +++ b/theatre/effects/dispensereffect.h @@ -14,7 +14,7 @@ class DispenserEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &timing, bool primary) override { - setAllOutputs(values[0]); + setAllOutputs(values[0], primary); } private: diff --git a/theatre/effects/fadeeffect.h b/theatre/effects/fadeeffect.h index 6971679..a43a156 100644 --- a/theatre/effects/fadeeffect.h +++ b/theatre/effects/fadeeffect.h @@ -77,7 +77,7 @@ class FadeEffect final : public Effect { } } if (_fadingValue[primary] != 0) { - setAllOutputs(ControlValue(_fadingValue[primary])); + setAllOutputs(ControlValue(_fadingValue[primary]), primary); } } diff --git a/theatre/effects/flickereffect.h b/theatre/effects/flickereffect.h index 4db01ba..6866ddb 100644 --- a/theatre/effects/flickereffect.h +++ b/theatre/effects/flickereffect.h @@ -50,11 +50,10 @@ class FlickerEffect final : public Effect { if (_independentOutputs) { for (size_t i = 0; i != Connections().size(); ++i) { - Connections()[i].first->MixInput(Connections()[i].second, - values[0] * ControlValue(value[i])); + MixConnection(i, values[0] * ControlValue(value[i]), primary); } } else { - setAllOutputs(values[0] * ControlValue(value[0])); + setAllOutputs(values[0] * ControlValue(value[0]), primary); } } } diff --git a/theatre/effects/fluorescentstarteffect.h b/theatre/effects/fluorescentstarteffect.h index 7925ed3..008b513 100644 --- a/theatre/effects/fluorescentstarteffect.h +++ b/theatre/effects/fluorescentstarteffect.h @@ -75,10 +75,9 @@ class FluorescentStartEffect final : public Effect { else value = _glowValue; if (_independentOutputs) { - Connections()[i].first->MixInput(Connections()[i].second, - values[0] * ControlValue(value)); + MixConnection(i, values[0] * ControlValue(value), primary); } else { - setAllOutputs(values[0] * ControlValue(value)); + setAllOutputs(values[0] * ControlValue(value), primary); } } } else { diff --git a/theatre/effects/functiongeneratoreffect.h b/theatre/effects/functiongeneratoreffect.h index 833658f..8c486ea 100644 --- a/theatre/effects/functiongeneratoreffect.h +++ b/theatre/effects/functiongeneratoreffect.h @@ -85,7 +85,7 @@ class FunctionGeneratorEffect final : public Effect { output = std::clamp(output * amplitude_.Ratio() + offset_.Ratio(), 0.0, 1.0) * input; - setAllOutputs(ControlValue(output)); + setAllOutputs(ControlValue(output), primary); } private: diff --git a/theatre/effects/hue_saturation_lightness_effect.cpp b/theatre/effects/hue_saturation_lightness_effect.cpp index 3360b72..b2aac97 100644 --- a/theatre/effects/hue_saturation_lightness_effect.cpp +++ b/theatre/effects/hue_saturation_lightness_effect.cpp @@ -96,52 +96,55 @@ std::array HueSaturationLightnessEffect::Convert( void HueSaturationLightnessEffect::MixImplementation(const ControlValue *values, const Timing & /*timing*/, - bool /*primary*/) { + bool primary) { // TODO cache std::array rgb = Convert(values[0], values[1], values[2]); - for (const std::pair &connection : Connections()) { + for (size_t connection_index = 0; connection_index != NConnections(); + ++connection_index) { + const std::pair &connection = + GetConnection(connection_index); switch (connection.first->InputType(connection.second)) { case FunctionType::Red: - connection.first->MixInput(connection.second, rgb[0]); + MixConnection(connection_index, rgb[0], primary); break; case FunctionType::Green: - connection.first->MixInput(connection.second, rgb[1]); + MixConnection(connection_index, rgb[1], primary); break; case FunctionType::Blue: - connection.first->MixInput(connection.second, rgb[2]); + MixConnection(connection_index, rgb[2], primary); break; case FunctionType::White: - connection.first->MixInput(connection.second, - DeduceWhite(rgb[0], rgb[1], rgb[2])); + MixConnection(connection_index, DeduceWhite(rgb[0], rgb[1], rgb[2]), + primary); break; case FunctionType::Amber: - connection.first->MixInput(connection.second, - DeduceAmber(rgb[0], rgb[1], rgb[2])); + MixConnection(connection_index, DeduceAmber(rgb[0], rgb[1], rgb[2]), + primary); break; case FunctionType::UV: - connection.first->MixInput(connection.second, - DeduceUv(rgb[0], rgb[1], rgb[2])); + MixConnection(connection_index, DeduceUv(rgb[0], rgb[1], rgb[2]), + primary); break; case FunctionType::Lime: - connection.first->MixInput(connection.second, - DeduceLime(rgb[0], rgb[1], rgb[2])); + MixConnection(connection_index, DeduceLime(rgb[0], rgb[1], rgb[2]), + primary); break; case FunctionType::ColdWhite: - connection.first->MixInput(connection.second, - DeduceColdWhite(rgb[0], rgb[1], rgb[2])); + MixConnection(connection_index, DeduceColdWhite(rgb[0], rgb[1], rgb[2]), + primary); break; case FunctionType::WarmWhite: - connection.first->MixInput(connection.second, - DeduceWarmWhite(rgb[0], rgb[1], rgb[2])); + MixConnection(connection_index, DeduceWarmWhite(rgb[0], rgb[1], rgb[2]), + primary); break; case FunctionType::Hue: - connection.first->MixInput(connection.second, values[0]); + MixConnection(connection_index, values[0], primary); break; case FunctionType::Saturation: - connection.first->MixInput(connection.second, values[1]); + MixConnection(connection_index, values[1], primary); break; case FunctionType::Lightness: - connection.first->MixInput(connection.second, values[2]); + MixConnection(connection_index, values[2], primary); break; default: break; diff --git a/theatre/effects/inverteffect.h b/theatre/effects/inverteffect.h index 49c65d2..9cc049c 100644 --- a/theatre/effects/inverteffect.h +++ b/theatre/effects/inverteffect.h @@ -25,8 +25,7 @@ class InvertEffect final : public Effect { ControlValue inverted = Invert(values[0]); if (inverted.UInt() < _offThreshold) inverted = ControlValue(0); ControlValue value = theatre::Mix(values[1], inverted, MixStyle::Multiply); - for (const std::pair &connection : Connections()) - connection.first->MixInput(connection.second, value); + setAllOutputs(value, primary); } virtual FunctionType InputType(size_t inputIndex) const override { diff --git a/theatre/effects/musicactivationeffect.h b/theatre/effects/musicactivationeffect.h index 158ce07..bc351fb 100644 --- a/theatre/effects/musicactivationeffect.h +++ b/theatre/effects/musicactivationeffect.h @@ -34,8 +34,7 @@ class MusicActivationEffect final : public Effect { } const double timePassed = timing.TimeInMS() - _lastBeatTime[primary]; if (timePassed < _offDelay) { - for (const std::pair &connection : Connections()) - connection.first->MixInput(connection.second, values[0]); + setAllOutputs(values[0], primary); } } diff --git a/theatre/effects/pulseeffect.h b/theatre/effects/pulseeffect.h index 05c85b9..6d1fed8 100644 --- a/theatre/effects/pulseeffect.h +++ b/theatre/effects/pulseeffect.h @@ -49,7 +49,7 @@ class PulseEffect final : public Effect { if (pos < transition_in_.LengthInMs()) { // Fade in const ControlValue value = transition_in_.InValue(pos, timing); - setAllOutputs(values[0] * value); + setAllOutputs(values[0] * value, primary); handled = true; } else { pos -= transition_in_.LengthInMs(); @@ -58,7 +58,7 @@ class PulseEffect final : public Effect { if (sustain_ != 0 && !handled) { if (pos < sustain_) { - setAllOutputs(ControlValue(values[0].UInt())); + setAllOutputs(ControlValue(values[0].UInt()), primary); handled = true; } else pos -= sustain_; @@ -68,7 +68,7 @@ class PulseEffect final : public Effect { if (pos < transition_out_.LengthInMs()) { // Fade out const ControlValue value = transition_out_.OutValue(pos, timing); - setAllOutputs(values[0] * value); + setAllOutputs(values[0] * value, primary); } } } diff --git a/theatre/effects/randomselecteffect.h b/theatre/effects/randomselecteffect.h index 3ebb6df..5ff64a3 100644 --- a/theatre/effects/randomselecteffect.h +++ b/theatre/effects/randomselecteffect.h @@ -57,11 +57,13 @@ class RandomSelectEffect final : public Effect { } if (active_transition_[primary]) { MixDirect(transition_connections, - values[0] * transition_.OutValue(transition_time, timing)); + values[0] * transition_.OutValue(transition_time, timing), + primary); MixDirect(activeConnections, - values[0] * transition_.InValue(transition_time, timing)); + values[0] * transition_.InValue(transition_time, timing), + primary); } else { - MixDirect(activeConnections, values[0]); + MixDirect(activeConnections, values[0], primary); } } else { _active[primary] = false; @@ -82,13 +84,11 @@ class RandomSelectEffect final : public Effect { } void MixDirect(const std::vector &connections, - const ControlValue value) { + const ControlValue value, bool primary) { size_t n_active = std::min(_count, Connections().size()); for (size_t i = 0; i != n_active; ++i) { if (connections[i] < Connections().size()) { - const std::pair &connection = - Connections()[connections[i]]; - connection.first->MixInput(connection.second, value); + MixConnection(connections[i], value, primary); } } } diff --git a/theatre/effects/rgbmastereffect.h b/theatre/effects/rgbmastereffect.h index cc94c7d..ea743f5 100644 --- a/theatre/effects/rgbmastereffect.h +++ b/theatre/effects/rgbmastereffect.h @@ -42,18 +42,21 @@ class RgbMasterEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &timing, bool primary) override { - for (const std::pair &connection : Connections()) { + for (size_t connection_index = 0; connection_index != NConnections(); + ++connection_index) { + const std::pair &connection = + GetConnection(connection_index); const size_t input_index = connection.second; const ControlValue master = values[3]; switch (connection.first->InputType(input_index)) { case FunctionType::Red: - connection.first->MixInput(input_index, values[0] * master); + MixConnection(connection_index, values[0] * master, primary); break; case FunctionType::Green: - connection.first->MixInput(input_index, values[1] * master); + MixConnection(connection_index, values[1] * master, primary); break; case FunctionType::Blue: - connection.first->MixInput(input_index, values[2] * master); + MixConnection(connection_index, values[2] * master, primary); break; default: break; diff --git a/theatre/effects/thresholdeffect.h b/theatre/effects/thresholdeffect.h index 4b0e486..9aa0504 100644 --- a/theatre/effects/thresholdeffect.h +++ b/theatre/effects/thresholdeffect.h @@ -71,9 +71,7 @@ class ThresholdEffect final : public Effect { thresholded.Set(ControlValue::Max().UInt() - v * 65536); } } - for (const std::pair &connection : Connections()) { - connection.first->MixInput(connection.second, thresholded); - } + setAllOutputs(thresholded, primary); } private: diff --git a/theatre/effects/timereffect.h b/theatre/effects/timereffect.h index dc9ba04..672514a 100644 --- a/theatre/effects/timereffect.h +++ b/theatre/effects/timereffect.h @@ -54,13 +54,13 @@ class TimerEffect final : public Effect { if (timing.TimeInMS() - start < transition_in_.LengthInMs()) { const ControlValue multiplier = transition_in_.InValue(timing.TimeInMS() - start, timing); - setAllOutputs(input * multiplier); + setAllOutputs(input * multiplier, primary); } else { transition_start_[primary].Reset(); } } if (!transition_start_[primary]) { - setAllOutputs(input); + setAllOutputs(input, primary); } } @@ -74,7 +74,7 @@ class TimerEffect final : public Effect { if (timing.TimeInMS() - start < transition_out_.LengthInMs()) { const ControlValue multiplier = transition_out_.OutValue(timing.TimeInMS() - start, timing); - setAllOutputs(input * multiplier); + setAllOutputs(input * multiplier, primary); } else { transition_start_[primary].Reset(); } diff --git a/theatre/effects/twinkleeffect.h b/theatre/effects/twinkleeffect.h index 4070d9d..b5fd59d 100644 --- a/theatre/effects/twinkleeffect.h +++ b/theatre/effects/twinkleeffect.h @@ -36,10 +36,9 @@ class TwinkleEffect final : public Effect { if (values[0]) { if (previous_time_[primary] == -1.0) previous_time_[primary] = timing.TimeInMS(); - inputs_[primary].resize(Connections().size()); - for (size_t i = 0; i != Connections().size(); ++i) { - MixInput(Connections()[i], inputs_[primary][i], values[0], timing, - primary); + inputs_[primary].resize(NConnections()); + for (size_t i = 0; i != NConnections(); ++i) { + MixInput(i, inputs_[primary][i], values[0], timing, primary); } } previous_time_[primary] = timing.TimeInMS(); @@ -52,9 +51,8 @@ class TwinkleEffect final : public Effect { double state_timer = 0.0; }; - void MixInput(const std::pair& connection, - InputData& input, const ControlValue& value, - const Timing& timing, bool primary) { + void MixInput(size_t connection_index, InputData& input, + const ControlValue& value, const Timing& timing, bool primary) { const double time_passed = timing.TimeInMS() - previous_time_[primary]; input.state_timer -= time_passed; switch (input.state) { @@ -68,18 +66,17 @@ class TwinkleEffect final : public Effect { if (input.state_timer <= 0.0) { input.state_timer = hold_time_; input.state = State::Hold; - connection.first->MixInput(connection.second, value); + MixConnection(connection_index, value, primary); } else { const double transition_point = transition_out_.LengthInMs() - input.state_timer; const ControlValue transition_value = transition_in_.InValue(transition_point, timing); - connection.first->MixInput(connection.second, - transition_value * value); + MixConnection(connection_index, transition_value * value, primary); } break; case State::Hold: - connection.first->MixInput(connection.second, value); + MixConnection(connection_index, value, primary); if (input.state_timer <= 0.0) { input.state = State::TransitionOut; input.state_timer = transition_out_.LengthInMs(); @@ -94,8 +91,7 @@ class TwinkleEffect final : public Effect { } else { const ControlValue transition_value = transition_out_.InValue(input.state_timer, timing); - connection.first->MixInput(connection.second, - transition_value * value); + MixConnection(connection_index, transition_value * value, primary); } break; } diff --git a/theatre/effects/variableeffect.h b/theatre/effects/variableeffect.h index 5f8d66d..0e230a9 100644 --- a/theatre/effects/variableeffect.h +++ b/theatre/effects/variableeffect.h @@ -32,17 +32,20 @@ class VariableEffect final : public Effect { protected: virtual void MixImplementation(const ControlValue *values, const Timing &timing, bool primary) override { - for (const std::pair &connection : Connections()) { + for (size_t connection_index = 0; connection_index != NConnections(); + ++connection_index) { + const std::pair &connection = + GetConnection(connection_index); const size_t input_index = connection.second; switch (connection.first->InputType(input_index)) { case FunctionType::Red: - connection.first->MixInput(input_index, values[0]); + MixConnection(connection_index, values[0], primary); break; case FunctionType::Green: - connection.first->MixInput(input_index, values[1]); + MixConnection(connection_index, values[1], primary); break; case FunctionType::Blue: - connection.first->MixInput(input_index, values[2]); + MixConnection(connection_index, values[2], primary); break; default: break; diff --git a/theatre/fixture.h b/theatre/fixture.h index 9509d6d..b6cd6c6 100644 --- a/theatre/fixture.h +++ b/theatre/fixture.h @@ -22,7 +22,6 @@ class ValueSnapshot; class Fixture : public NamedObject { public: Fixture(Theatre &theatre, const FixtureMode &type, const std::string &name); - // Fixture(const Fixture &source, Theatre &theatre); static inline constexpr double kDefaultHeight = 5.0; static inline constexpr double kDefaultTilt = 0.25 * M_PI; diff --git a/theatre/fixturecontrol.h b/theatre/fixturecontrol.h index 39d93ba..1913384 100644 --- a/theatre/fixturecontrol.h +++ b/theatre/fixturecontrol.h @@ -45,9 +45,9 @@ class FixtureControl final : public Controllable { return {InputColor(index)}; } - size_t NOutputs() const override { return 0; } + size_t NConnections() const override { return 0; } - std::pair Output(size_t) const override { + std::pair GetConnection(size_t) const override { assert(false); return std::pair(nullptr, 0); } diff --git a/theatre/functiontype.h b/theatre/functiontype.h index c6b0331..d799e01 100644 --- a/theatre/functiontype.h +++ b/theatre/functiontype.h @@ -293,6 +293,48 @@ inline constexpr bool IsColor(FunctionType type) { return false; } +/** + * Returns true for function types for which it is possible + * to show intermediate values when going from value A to B. + */ +inline constexpr bool CanFade(FunctionType type) { + switch (type) { + case FunctionType::ColorMacro: + case FunctionType::ColorWheel: + case FunctionType::Combined: + case FunctionType::Effect: + case FunctionType::Focus: + case FunctionType::GoboWheel: + case FunctionType::Prism: + case FunctionType::Pulse: + case FunctionType::Strobe: + return false; + // Colors: + case FunctionType::Red: + case FunctionType::Green: + case FunctionType::Blue: + case FunctionType::White: + case FunctionType::Amber: + case FunctionType::UV: + case FunctionType::Lime: + case FunctionType::ColdWhite: + case FunctionType::WarmWhite: + // Other fadeables: + case FunctionType::ColorTemperature: + case FunctionType::Hue: + case FunctionType::Lightness: + case FunctionType::Master: + case FunctionType::Pan: + case FunctionType::RotationSpeed: + case FunctionType::Saturation: + case FunctionType::Tilt: + case FunctionType::Unknown: + case FunctionType::Zoom: + return true; + } + return false; +} + inline constexpr bool IsRgb(FunctionType type) { return type == FunctionType::Red || type == FunctionType::Green || type == FunctionType::Blue; diff --git a/theatre/management.cpp b/theatre/management.cpp index e5f11b0..af48fa5 100644 --- a/theatre/management.cpp +++ b/theatre/management.cpp @@ -185,23 +185,33 @@ void Management::MixAll(unsigned timestep_number, ValueSnapshot &primary, throw std::runtime_error("Cycle in dependencies"); for (bool is_primary : {false, true}) { - // Reset all inputs + // Reset all inputs (except if they are LTP) for (const std::unique_ptr &sv : _sourceValues) { + Controllable &controllable = sv->GetControllable(); for (size_t inputIndex = 0; inputIndex != sv->GetControllable().NInputs(); ++inputIndex) { - sv->GetControllable().InputValue(inputIndex) = ControlValue(0); + const MixStyle mix_style = + GetMixStyle(controllable.InputType(inputIndex)); + if (mix_style != MixStyle::LastTakesPrecedence) + controllable.InputValue(inputIndex) = ControlValue(0); } } // Process source values. These will output to controllables. if (is_primary) { - for (const std::unique_ptr &sv : _sourceValues) - sv->GetControllable().MixInput(sv->InputIndex(), - ControlValue(sv->PrimaryValue())); + for (const std::unique_ptr &sv : _sourceValues) { + const ControlValue value(sv->PrimaryValue()); + sv->GetControllable().MixInput(sv->InputIndex(), value, + sv->PreviousPrimary()); + sv->PreviousPrimary() = value; + } } else { - for (const std::unique_ptr &sv : _sourceValues) - sv->GetControllable().MixInput(sv->InputIndex(), - ControlValue(sv->SecondaryValue())); + for (const std::unique_ptr &sv : _sourceValues) { + const ControlValue value(sv->SecondaryValue()); + sv->GetControllable().MixInput(sv->InputIndex(), value, + sv->PreviousSecondary()); + sv->PreviousSecondary() = value; + } } // Process all controllables that follow @@ -574,8 +584,8 @@ bool Management::topologicalSortVisit(Controllable &controllable, std::vector &list) { if (controllable.VisitLevel() == 0) { controllable.SetVisitLevel(1); - for (size_t i = 0; i != controllable.NOutputs(); ++i) { - Controllable *other = controllable.Output(i).first; + for (size_t i = 0; i != controllable.NConnections(); ++i) { + Controllable *other = controllable.GetConnection(i).first; if (!topologicalSortVisit(*other, list)) return false; } controllable.SetVisitLevel(2); diff --git a/theatre/management.h b/theatre/management.h index 50432b4..b32b179 100644 --- a/theatre/management.h +++ b/theatre/management.h @@ -2,6 +2,7 @@ #define THEATRE_MANAGEMENT_H_ #include +#include #include #include #include @@ -92,6 +93,14 @@ class Management { } return list; } + /** + * Get the non-const version of this controller from a const pointer. The + * controller must be part of this management. + */ + Controllable &GetNonConst(const Controllable &controllable) { + assert(Contains(controllable)); + return const_cast(controllable); + } Folder &AddFolder(Folder &parent, const std::string &name); Folder &GetFolder(const std::string &path); diff --git a/theatre/mixstyle.h b/theatre/mixstyle.h index 8ed176b..ab05ed4 100644 --- a/theatre/mixstyle.h +++ b/theatre/mixstyle.h @@ -3,6 +3,8 @@ #include +#include "functiontype.h" + namespace glight::theatre { enum class MixStyle { @@ -12,7 +14,8 @@ enum class MixStyle { LowestValue, Multiply, First, - Second + Second, + LastTakesPrecedence }; inline std::string ToString(MixStyle mix_style) { @@ -32,6 +35,8 @@ inline std::string ToString(MixStyle mix_style) { return "first"; case MixStyle::Second: return "second"; + case MixStyle::LastTakesPrecedence: + return "last_takes_precedence"; } } @@ -40,6 +45,8 @@ inline MixStyle GetMixStyle(const std::string& str) { return MixStyle::HighestValue; else if (str == "sum") return MixStyle::Sum; + else if (str == "last_takes_precedence") + return MixStyle::LastTakesPrecedence; else if (str == "lowest_value") return MixStyle::LowestValue; else if (str == "multiply") @@ -52,6 +59,47 @@ inline MixStyle GetMixStyle(const std::string& str) { return MixStyle::Default; } +inline constexpr MixStyle GetMixStyle(FunctionType function_type) { + switch (function_type) { + // Positional function types: + case FunctionType::ColorMacro: + case FunctionType::ColorTemperature: + case FunctionType::ColorWheel: + case FunctionType::Combined: + case FunctionType::Focus: + case FunctionType::GoboWheel: + case FunctionType::Pan: + case FunctionType::Prism: + case FunctionType::Saturation: + case FunctionType::Tilt: + case FunctionType::Zoom: + case FunctionType::Unknown: + case FunctionType::Hue: + return MixStyle::LastTakesPrecedence; + // Effects: + case FunctionType::Effect: + case FunctionType::Pulse: + case FunctionType::RotationSpeed: + case FunctionType::Strobe: + return MixStyle::HighestValue; + // Colors: + case FunctionType::Red: + case FunctionType::Green: + case FunctionType::Blue: + case FunctionType::White: + case FunctionType::Amber: + case FunctionType::UV: + case FunctionType::Lime: + case FunctionType::ColdWhite: + case FunctionType::WarmWhite: + // other summed function types: + case FunctionType::Master: + case FunctionType::Lightness: + return MixStyle::Sum; + } + return MixStyle::Sum; +} + } // namespace glight::theatre #endif diff --git a/theatre/presetcollection.cpp b/theatre/presetcollection.cpp index c098394..a9b0c93 100644 --- a/theatre/presetcollection.cpp +++ b/theatre/presetcollection.cpp @@ -61,6 +61,7 @@ void PresetCollection::SetFromCurrentSituation(Management& management) { value->SetValue(sv->A().Value()); } } + connection_values_.assign(_presetValues.size(), {0, 0}); } void PresetCollection::SetFromCurrentFixtures( @@ -82,6 +83,7 @@ void PresetCollection::SetFromCurrentFixtures( } } } + connection_values_.assign(_presetValues.size(), {0, 0}); } } // namespace glight::theatre diff --git a/theatre/presetcollection.h b/theatre/presetcollection.h index 29f9795..ac80243 100644 --- a/theatre/presetcollection.h +++ b/theatre/presetcollection.h @@ -1,6 +1,7 @@ #ifndef THEATRE_PRESETCOLLECTION_H_ #define THEATRE_PRESETCOLLECTION_H_ +#include #include #include #include @@ -25,7 +26,10 @@ class PresetCollection final : public Controllable { : Controllable(name), _inputValue(0) {} ~PresetCollection() { Clear(); } - void Clear() { _presetValues.clear(); } + void Clear() { + _presetValues.clear(); + connection_values_.clear(); + } void SetFromCurrentSituation(Management &management); @@ -41,40 +45,44 @@ class PresetCollection final : public Controllable { FunctionType InputType(size_t) const override { return FunctionType::Master; } - size_t NOutputs() const override { return _presetValues.size(); } + size_t NConnections() const override { return _presetValues.size(); } - std::pair Output(size_t index) const override { + std::pair GetConnection( + size_t index) const override { return std::make_pair(&_presetValues[index]->GetControllable(), _presetValues[index]->InputIndex()); } void Mix(const Timing &timing, bool primary) override { unsigned leftHand = _inputValue.UInt(); - for (const std::unique_ptr &pv : _presetValues) { + for (size_t i = 0; i != _presetValues.size(); ++i) { + const std::unique_ptr &pv = _presetValues[i]; unsigned rightHand = pv->Value().UInt(); ControlValue value( ControlValue::Mix(leftHand, rightHand, MixStyle::Multiply)); - pv->GetControllable().MixInput(pv->InputIndex(), value); + pv->GetControllable().MixInput(pv->InputIndex(), value, + connection_values_[i][primary]); } } const std::vector> &PresetValues() const { return _presetValues; } PresetValue &AddPresetValue(const PresetValue &source) { - _presetValues.emplace_back(new PresetValue(source)); - return *_presetValues.back(); + connection_values_.emplace_back(); + return *_presetValues.emplace_back(new PresetValue(source)); } PresetValue &AddPresetValue(Controllable &controllable, size_t input) { - _presetValues.emplace_back(new PresetValue(controllable, input)); - return *_presetValues.back(); + connection_values_.emplace_back(); + return *_presetValues.emplace_back(new PresetValue(controllable, input)); } PresetValue &AddPresetValue(const PresetValue &source, Controllable &controllable) { - _presetValues.emplace_back(new PresetValue(source, controllable)); - return *_presetValues.back(); + connection_values_.emplace_back(); + return *_presetValues.emplace_back(new PresetValue(source, controllable)); } void RemovePresetValue(size_t index) { + connection_values_.erase(connection_values_.begin() + index); _presetValues.erase(_presetValues.begin() + index); } size_t Size() const { return _presetValues.size(); } @@ -82,6 +90,7 @@ class PresetCollection final : public Controllable { private: ControlValue _inputValue; std::vector> _presetValues; + std::vector> connection_values_; }; } // namespace glight::theatre diff --git a/theatre/scenes/controlsceneitem.h b/theatre/scenes/controlsceneitem.h index 2b69229..814e15e 100644 --- a/theatre/scenes/controlsceneitem.h +++ b/theatre/scenes/controlsceneitem.h @@ -29,7 +29,7 @@ class ControlSceneItem final : public SceneItem { const double ratio = (timing.TimeInMS() - OffsetInMS()) / DurationInMS(); const ControlValue value(_startValue.UInt() * (1.0 - ratio) + _endValue.UInt() * ratio); - _controllable.MixInput(_input, value); + _controllable.MixInput(_input, value, connection_value_); } Controllable &GetControllable() const { return _controllable; } size_t GetInput() const { return _input; } @@ -37,7 +37,9 @@ class ControlSceneItem final : public SceneItem { private: Controllable &_controllable; size_t _input; - ControlValue _startValue, _endValue; + ControlValue _startValue; + ControlValue _endValue; + ControlValue connection_value_; }; } // namespace glight::theatre diff --git a/theatre/scenes/scene.h b/theatre/scenes/scene.h index 904fb56..1afe18c 100644 --- a/theatre/scenes/scene.h +++ b/theatre/scenes/scene.h @@ -61,9 +61,10 @@ class Scene : public Controllable, private system::SyncListener { FunctionType InputType(size_t) const override { return FunctionType::Master; } - size_t NOutputs() const override { return controllables_.size(); } + size_t NConnections() const override { return controllables_.size(); } - std::pair Output(size_t index) const override { + std::pair GetConnection( + size_t index) const override { return controllables_[index]; } diff --git a/theatre/sourcevalue.h b/theatre/sourcevalue.h index 9f11e89..dd36631 100644 --- a/theatre/sourcevalue.h +++ b/theatre/sourcevalue.h @@ -157,11 +157,17 @@ class SourceValue { ControlValue::Invert(cross_fader_.TargetValue())); } + ControlValue& PreviousPrimary() { return previous_primary_; } + + ControlValue& PreviousSecondary() { return previous_secondary_; } + private: Input input_; SingleSourceValue a_; SingleSourceValue b_; SingleSourceValue cross_fader_; + ControlValue previous_primary_; + ControlValue previous_secondary_; sigc::signal signal_delete_; }; diff --git a/theatre/timesequence.h b/theatre/timesequence.h index 7902c16..6c5f441 100644 --- a/theatre/timesequence.h +++ b/theatre/timesequence.h @@ -5,7 +5,7 @@ #include "controllable.h" #include "controlvalue.h" -#include "sequence.h" +#include "input.h" #include "timing.h" #include "transition.h" #include "trigger.h" @@ -14,16 +14,7 @@ namespace glight::theatre { class TimeSequence final : public Controllable { public: - TimeSequence() - : _inputValue(), - _activeValue{ControlValue(), ControlValue()}, - _stepStart(), - _stepNumber{0, 0}, - _transitionTriggered{false, false}, - _sequence(), - _steps(), - _sustain(false), - _repeatCount(1) {} + TimeSequence() = default; std::unique_ptr CopyWithoutSequence() const { return std::unique_ptr(new TimeSequence(*this)); @@ -37,11 +28,12 @@ class TimeSequence final : public Controllable { return FunctionType::Master; } - size_t NOutputs() const override { return _sequence.List().size(); } + size_t NConnections() const override { return _sequence.size(); } - std::pair Output(size_t index) const override { - return std::make_pair(_sequence.List()[index].GetControllable(), - _sequence.List()[index].InputIndex()); + std::pair GetConnection( + size_t index) const override { + return std::make_pair(_sequence[index].GetControllable(), + _sequence[index].InputIndex()); } size_t RepeatCount() const { return _repeatCount; } @@ -53,7 +45,7 @@ class TimeSequence final : public Controllable { bool Sustain() const { return _sustain; } void SetSustain(bool sustain) { _sustain = sustain; } - virtual void Mix(const Timing &timing, bool primary) override { + void Mix(const Timing &timing, bool primary) override { ControlValue &activeValue = _activeValue[primary]; Timing &stepStart = _stepStart[primary]; size_t &stepNumber = _stepNumber[primary]; @@ -99,8 +91,11 @@ class TimeSequence final : public Controllable { } break; } if (!transitionTriggered) { - Input &input = _sequence.List()[stepNumber % _steps.size()]; - input.GetControllable()->MixInput(input.InputIndex(), activeValue); + const size_t step_index = stepNumber % _steps.size(); + Input &input = _sequence[step_index]; + input.GetControllable()->MixInput( + input.InputIndex(), activeValue, + connection_values_[step_index][primary]); } } if (transitionTriggered) { @@ -112,17 +107,27 @@ class TimeSequence final : public Controllable { } else { // Not there yet; transition to next state double transitionTime = timing.TimeInMS() - stepStart.TimeInMS(); - Input &a = _sequence.List()[stepNumber % _steps.size()]; - Input &b = _sequence.List()[(stepNumber + 1) % _steps.size()]; + const size_t a_index = stepNumber % _steps.size(); + const size_t b_index = (stepNumber + 1) % _steps.size(); + Input &a = _sequence[a_index]; + Input &b = _sequence[b_index]; if (transitionTime >= activeStep.transition.LengthInMs()) { ++stepNumber; stepStart = timing; transitionTriggered = false; - b.GetControllable()->MixInput(b.InputIndex(), activeValue); + b.GetControllable()->MixInput( + b.InputIndex(), activeValue, + connection_values_[b_index][primary]); } else { - activeStep.transition.Mix(*a.GetControllable(), a.InputIndex(), - *b.GetControllable(), b.InputIndex(), - transitionTime, activeValue, timing); + Connection connection_a{.to_controllable = a.GetControllable(), + .to_input_index = a.InputIndex(), + .values = connection_values_[a_index]}; + Connection connection_b{.to_controllable = b.GetControllable(), + .to_input_index = b.InputIndex(), + .values = connection_values_[b_index]}; + activeStep.transition.Mix(connection_a, connection_b, + transitionTime, activeValue, timing, + primary); } } } @@ -134,10 +139,14 @@ class TimeSequence final : public Controllable { } } - class Sequence &Sequence() { - return _sequence; + const std::vector &Sequence() const { return _sequence; } + + template + void SetSequence(VectorInput &&sequence) { + _sequence = std::forward(sequence); + connection_values_.assign(_sequence.size(), + {ControlValue(), ControlValue()}); } - const class Sequence &Sequence() const { return _sequence; } struct Step { Transition transition; @@ -147,12 +156,12 @@ class TimeSequence final : public Controllable { std::vector &Steps() { return _steps; } Step &AddStep(Controllable &controllable, size_t input) { - _sequence.Add(controllable, input); + _sequence.emplace_back(controllable, input); return _steps.emplace_back(); } void RemoveStep(size_t index) { - _sequence.Remove(index); + _sequence.erase(_sequence.begin() + index); _steps.erase(_steps.begin() + index); } @@ -183,13 +192,14 @@ class TimeSequence final : public Controllable { ControlValue _inputValue; std::array _activeValue; std::array _stepStart; - std::array _stepNumber; - std::array _transitionTriggered; + std::array _stepNumber = {0, 0}; + std::array _transitionTriggered = {false, false}; - class Sequence _sequence; + std::vector _sequence; + std::vector> connection_values_; std::vector _steps; - bool _sustain; - size_t _repeatCount; + bool _sustain = false; + size_t _repeatCount = 1; }; } // namespace glight::theatre diff --git a/theatre/transition.cpp b/theatre/transition.cpp index 87cbbd5..75bede5 100644 --- a/theatre/transition.cpp +++ b/theatre/transition.cpp @@ -198,47 +198,56 @@ ControlValue Transition::OutValue(double transition_time, /** * @param transitionTime value between 0 and _lengthInMS. */ -void Transition::Mix(Controllable &first, size_t first_input, - Controllable &second, size_t second_input, - double transition_time, const ControlValue &value, - const Timing &timing) const { +void Transition::Mix(Connection &first, Connection &second, + double transition_time, ControlValue value, + const Timing &timing, bool primary) const { const double ratio = std::clamp(transition_time / length_in_ms_, 0.0, 1.0); switch (type_) { case TransitionType::None: if (transition_time * 2.0 <= length_in_ms_) - first.MixInput(first_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); else - second.MixInput(second_input, value); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); break; case TransitionType::Fade: { const ControlValue second_value = value * ratio; - first.MixInput(first_input, value - second_value); - second.MixInput(second_input, second_value); + first.to_controllable->MixInput( + first.to_input_index, value - second_value, first.values[primary]); + second.to_controllable->MixInput(second.to_input_index, second_value, + second.values[primary]); } break; case TransitionType::FadeThroughBlack: { const unsigned scaled_ratio = (unsigned)(ratio * (65536 * 2.0)); if (scaled_ratio < 65536) { ControlValue firstValue( ((value.UInt() >> 8) * (65535 - scaled_ratio)) >> 8); - first.MixInput(first_input, firstValue); + first.to_controllable->MixInput(first.to_input_index, firstValue, + first.values[primary]); } else { ControlValue secondValue( ((value.UInt() >> 8) * (scaled_ratio - 65536)) >> 8); - second.MixInput(second_input, secondValue); + second.to_controllable->MixInput(second.to_input_index, secondValue, + second.values[primary]); } } break; case TransitionType::FadeThroughFull: { const unsigned scaled_ratio = (unsigned)(ratio * (65536 * 2.0)); if (scaled_ratio < 65536) { - first.MixInput(first_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); const ControlValue secondValue(((value.UInt() >> 8) * scaled_ratio) >> 8); - second.MixInput(second_input, secondValue); + second.to_controllable->MixInput(second.to_input_index, secondValue, + second.values[primary]); } else { const ControlValue firstValue( ((value.UInt() >> 8) * (512 - scaled_ratio)) >> 8); - first.MixInput(first_input, firstValue); - second.MixInput(second_input, value); + first.to_controllable->MixInput(first.to_input_index, firstValue, + first.values[primary]); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); } } break; case TransitionType::GlowFade: { @@ -252,25 +261,36 @@ void Transition::Mix(Controllable &first, size_t first_input, transition_point /= stage_split; const double a = (1.0 - transition_point) * (1.0 - glow_level) + glow_level; - first.MixInput(first_input, ControlValue(value.UInt() * a)); - second.MixInput(second_input, - ControlValue(value.UInt() * transition_point)); + first.to_controllable->MixInput(first.to_input_index, + ControlValue(value.UInt() * a), + first.values[primary]); + second.to_controllable->MixInput( + second.to_input_index, + ControlValue(value.UInt() * transition_point), + second.values[primary]); } else { transition_point = (transition_point - stage_split) / (1.0 - stage_split); const double a = (1.0 - transition_point) * glow_level; - first.MixInput(first_input, ControlValue(value.UInt() * a)); - second.MixInput(second_input, value); + first.to_controllable->MixInput(first.to_input_index, + ControlValue(value.UInt() * a), + first.values[primary]); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); } } break; case TransitionType::Stepped: { unsigned secondRatioValue = (unsigned)(ratio * 256.0); secondRatioValue = (secondRatioValue / 51) * 51; const unsigned firstRatioValue = 255 - secondRatioValue; - first.MixInput(first_input, - ControlValue((value.UInt() * firstRatioValue) >> 8)); - second.MixInput(second_input, - ControlValue((value.UInt() * secondRatioValue) >> 8)); + first.to_controllable->MixInput( + first.to_input_index, + ControlValue((value.UInt() * firstRatioValue) >> 8), + first.values[primary]); + second.to_controllable->MixInput( + second.to_input_index, + ControlValue((value.UInt() * secondRatioValue) >> 8), + second.values[primary]); } break; case TransitionType::ConstantAcceleration: { const double fade_value = (ratio <= 0.5) @@ -278,12 +298,14 @@ void Transition::Mix(Controllable &first, size_t first_input, : 1.0 - (ratio - 1.0) * (ratio - 1.0) * 2.0; unsigned secondRatioValue = (unsigned)(fade_value * 65536.0); const unsigned firstRatioValue = 65535 - secondRatioValue; - first.MixInput( - first_input, - ControlValue(((value.UInt() >> 8) * firstRatioValue) >> 8)); - second.MixInput( - second_input, - ControlValue(((value.UInt() >> 8) * secondRatioValue) >> 8)); + first.to_controllable->MixInput( + first.to_input_index, + ControlValue(((value.UInt() >> 8) * firstRatioValue) >> 8), + first.values[primary]); + second.to_controllable->MixInput( + second.to_input_index, + ControlValue(((value.UInt() >> 8) * secondRatioValue) >> 8), + second.values[primary]); } break; case TransitionType::Random: { const unsigned scaled_ratio = (unsigned)(ratio * 256); @@ -292,65 +314,89 @@ void Transition::Mix(Controllable &first, size_t first_input, const unsigned secondRatioValue = timing.DrawRandomValue(upper_bound - lower_bound) + lower_bound; const unsigned firstRatioValue = 255 - secondRatioValue; - first.MixInput(first_input, - ControlValue((value.UInt() * firstRatioValue) >> 8)); - second.MixInput(second_input, - ControlValue((value.UInt() * secondRatioValue) >> 8)); + first.to_controllable->MixInput( + first.to_input_index, + ControlValue((value.UInt() * firstRatioValue) >> 8), + first.values[primary]); + second.to_controllable->MixInput( + second.to_input_index, + ControlValue((value.UInt() * secondRatioValue) >> 8), + second.values[primary]); } break; case TransitionType::Erratic: { unsigned scaled_ratio = (unsigned)(ratio * ControlValue::MaxUInt()); if (scaled_ratio < timing.DrawRandomValue()) - first.MixInput(first_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); else - second.MixInput(second_input, value); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); } break; case TransitionType::SlowStrobe: if (timing.TimestepNumber() % 8 == 0) - first.MixInput(first_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); else if (timing.TimestepNumber() % 8 == 4) - second.MixInput(second_input, value); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); break; case TransitionType::FastStrobe: if (timing.TimestepNumber() % 2 == 0) - first.MixInput(first_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); else - second.MixInput(second_input, value); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); break; case TransitionType::StrobeAB: { if (timing.TimestepNumber() % 2 == 0) { if (transition_time * 2.0 < length_in_ms_) - first.MixInput(first_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); else - second.MixInput(second_input, value); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); } } break; case TransitionType::Black: break; case TransitionType::Full: - first.MixInput(first_input, value); - second.MixInput(second_input, value); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); break; case TransitionType::FadeFromBlack: { unsigned ratioValue = (unsigned)(ratio * 65536.0); - second.MixInput(second_input, - ControlValue(((value.UInt() >> 8) * ratioValue) >> 8)); + second.to_controllable->MixInput( + second.to_input_index, + ControlValue(((value.UInt() >> 8) * ratioValue) >> 8), + second.values[primary]); } break; case TransitionType::FadeToBlack: { unsigned ratioValue = 65535 - (unsigned)(ratio * 65536.0); - first.MixInput(second_input, - ControlValue(((value.UInt() >> 8) * ratioValue) >> 8)); + first.to_controllable->MixInput( + second.to_input_index, + ControlValue(((value.UInt() >> 8) * ratioValue) >> 8), + second.values[primary]); } break; case TransitionType::FadeFromFull: { const unsigned ratio_value = 65535 - (unsigned)(ratio * 65536.0); - first.MixInput(first_input, - ControlValue(((value.UInt() >> 8) * ratio_value) >> 8)); - second.MixInput(second_input, value); + first.to_controllable->MixInput( + first.to_input_index, + ControlValue(((value.UInt() >> 8) * ratio_value) >> 8), + first.values[primary]); + second.to_controllable->MixInput(second.to_input_index, value, + second.values[primary]); } break; case TransitionType::FadeToFull: { unsigned ratio_value = (unsigned)(ratio * 65536.0); - first.MixInput(first_input, value); - second.MixInput(second_input, - ControlValue(((value.UInt() >> 8) * ratio_value) >> 8)); + first.to_controllable->MixInput(first.to_input_index, value, + first.values[primary]); + second.to_controllable->MixInput( + second.to_input_index, + ControlValue(((value.UInt() >> 8) * ratio_value) >> 8), + second.values[primary]); } break; } } diff --git a/theatre/transition.h b/theatre/transition.h index 93d142f..aae30c4 100644 --- a/theatre/transition.h +++ b/theatre/transition.h @@ -1,7 +1,7 @@ #ifndef THEATRE_TRANSITION_H_ #define THEATRE_TRANSITION_H_ -#include "presetcollection.h" +#include "controllable.h" #include "timing.h" #include @@ -216,9 +216,8 @@ class Transition { * @param transition_time value between 0 and _lengthInMS. * @param timing used for randomness, etc. */ - void Mix(Controllable &first, size_t first_input, Controllable &second, - size_t second_input, double transition_time, - const ControlValue &value, const Timing &timing) const; + void Mix(Connection &first, Connection &second, double transition_time, + ControlValue value, const Timing &timing, bool primary) const; private: double length_in_ms_ = 250.0;