Skip to content

Commit 1412b48

Browse files
committed
Persist sequencer state.
1 parent 62fa300 commit 1412b48

File tree

9 files changed

+208
-12
lines changed

9 files changed

+208
-12
lines changed

Source/PluginEditor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,10 @@ AeolusAudioProcessorEditor::AeolusAudioProcessorEditor (AeolusAudioProcessor& p)
114114
for (auto* divisionView : _divisionViews) {
115115
divisionView->cancelAllStops();
116116
divisionView->cancelAllLinks();
117+
divisionView->cancelTremulant();
117118
}
118119
};
120+
119121
addAndMakeVisible(_cancelButton);
120122

121123
addAndMakeVisible(_divisionsViewport);

Source/aeolus/division.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ void Division::initFromVar(const var& v)
133133

134134
var Division::getPersistentState() const
135135
{
136-
// Per division, but we have only one so far
137136
auto* divisionObj = new DynamicObject();
138137

139138
divisionObj->setProperty("midi_channel", getMIDIChannel());

Source/aeolus/engine.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,23 +388,29 @@ var Engine::getPersistentState() const
388388
{
389389
auto* obj = new DynamicObject();
390390

391+
// Save the IR.
391392
int irNum = _selectedIR;
392393
obj->setProperty("ir", irNum);
393394

395+
// Save divisions.
394396
Array<var> divisions;
395397

396398
for (auto* division : _divisions)
397399
divisions.add(division->getPersistentState());
398400

399401
obj->setProperty("divisions", divisions);
400402

403+
// Make sure to capture current configuration into the sequencer's step.
404+
_sequencer->captureCurrentStep();
405+
obj->setProperty("sequencer", _sequencer->getPersistentState());
406+
401407
return var{obj};
402408
}
403409

404410
void Engine::setPersistentState(const var& state)
405411
{
406412
if (const auto* obj = state.getDynamicObject()) {
407-
413+
// Restore the IR
408414
int irNum = obj->getProperty("ir");
409415

410416
if (MessageManager::getInstance()->isThisTheMessageThread())
@@ -414,6 +420,11 @@ void Engine::setPersistentState(const var& state)
414420

415421
postReverbIR(irNum);
416422

423+
// Restore the sequencer
424+
_sequencer->setPersistentState(obj->getProperty("sequencer"));
425+
426+
// Restore the divisions after the sequencer (in case we are restoring
427+
// from a state that did not have a sequencer before).
417428
if (const auto* divisions = obj->getProperty("divisions").getArray()) {
418429

419430
if (divisions->size() != _divisions.size()) {
@@ -426,6 +437,7 @@ void Engine::setPersistentState(const var& state)
426437
division->setPersistentState(divisions->getReference(divIdx));
427438
}
428439
}
440+
429441
}
430442
}
431443

Source/aeolus/sequencer.cpp

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,134 @@ using namespace juce;
2525

2626
AEOLUS_NAMESPACE_BEGIN
2727

28+
//==============================================================================
29+
30+
var Sequencer::DivisionState::getPersistentState() const
31+
{
32+
auto* obj = new DynamicObject();
33+
34+
Array<var> stopsArr;
35+
36+
for (const auto& s : stops)
37+
stopsArr.add(s);
38+
39+
obj->setProperty("stops", stopsArr);
40+
obj->setProperty("tremulant", tremulant);
41+
42+
return var{obj};
43+
}
44+
45+
void Sequencer::DivisionState::setPersistentState(const var& v)
46+
{
47+
if (const auto* obj = v.getDynamicObject()) {
48+
if (const auto* stopsArr = obj->getProperty("stops").getArray()) {
49+
if (stopsArr->size() == stops.size()) {
50+
for (int i = 0; i < stops.size(); ++i)
51+
stops[i] = stopsArr->getUnchecked(i);
52+
}
53+
}
54+
55+
tremulant = obj->getProperty("tremulant");
56+
}
57+
}
58+
59+
//==============================================================================
60+
61+
var Sequencer::OrganState::getPersistentState() const
62+
{
63+
auto* obj = new DynamicObject();
64+
65+
Array<var> divisionsArr;
66+
67+
for (const auto& division : divisions)
68+
divisionsArr.add(division.getPersistentState());
69+
70+
obj->setProperty("divisions", divisionsArr);
71+
72+
return var{obj};
73+
}
74+
75+
void Sequencer::OrganState::setPersistentState(const var& v)
76+
{
77+
if (const auto* obj = v.getDynamicObject()) {
78+
if (const auto* divisionsArr = obj->getProperty("divisions").getArray()) {
79+
if (divisionsArr->size() == divisions.size()) {
80+
for (int i = 0; i < divisions.size(); ++i)
81+
divisions[i].setPersistentState(divisionsArr->getUnchecked(i));
82+
}
83+
}
84+
}
85+
}
86+
87+
//==============================================================================
88+
2889
Sequencer::Sequencer(Engine& engine, int numSteps)
2990
: _engine{engine}
3091
, _steps(numSteps)
3192
, _currentStep{0}
93+
, _listeners{}
3294
{
3395
jassert(_steps.size() > 0);
3496

3597
initFromEngine();
3698
}
3799

100+
var Sequencer::getPersistentState() const
101+
{
102+
auto* sequencerObj = new DynamicObject();
103+
104+
Array<var> stepsArr;
105+
106+
for (int stepIdx = 0; stepIdx < _steps.size(); ++stepIdx)
107+
stepsArr.add(_steps[stepIdx].getPersistentState());
108+
109+
sequencerObj->setProperty("steps", stepsArr);
110+
sequencerObj->setProperty("current_step", _currentStep);
111+
112+
return var{sequencerObj};
113+
}
114+
115+
void Sequencer::setPersistentState(const var& v)
116+
{
117+
if (auto* sequencerObj = v.getDynamicObject()) {
118+
119+
if (auto* stepsArr = sequencerObj->getProperty("steps").getArray()) {
120+
if (stepsArr->size() == _steps.size()) {
121+
for (int stepIdx = 0; stepIdx < _steps.size(); ++stepIdx)
122+
_steps[stepIdx].setPersistentState(stepsArr->getUnchecked(stepIdx));
123+
}
124+
}
125+
126+
const int currentStep = sequencerObj->getProperty("current_step");
127+
128+
if (currentStep >= 0 && currentStep < (int)_steps.size()) {
129+
// Don't capture current state as it is unititialised
130+
// and should not go into the sequencer.
131+
setStep(currentStep, false);
132+
}
133+
}
134+
}
135+
38136
void Sequencer::captureCurrentStep()
39137
{
40138
captureState(_steps[_currentStep]);
41139
}
42140

43-
void Sequencer::setStep(int index)
141+
void Sequencer::setStep(int index, bool captureCurrentState)
44142
{
45143
jassert(index >= 0 && index < (int)_steps.size());
46144

47-
captureCurrentStep();
48-
_currentStep = index;
49-
recallState(_steps[_currentStep]);
145+
if (index != _currentStep) {
146+
if (captureCurrentState)
147+
captureCurrentStep();
148+
149+
_currentStep = index;
150+
recallState(_steps[_currentStep]);
151+
152+
_listeners.call([index](Listener& listener) {
153+
listener.sequencerStepChanged(index);
154+
});
155+
}
50156
}
51157

52158
void Sequencer::stepForward()

Source/aeolus/sequencer.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,24 @@ class Sequencer
3838
{
3939
std::vector<bool> stops; ///< Stops enablement mask.
4040
bool tremulant; ///< Tremulant enablement.
41+
42+
juce::var getPersistentState() const;
43+
void setPersistentState(const juce::var& v);
4144
};
4245

4346
struct OrganState
4447
{
4548
std::vector<DivisionState> divisions;
49+
50+
juce::var getPersistentState() const;
51+
void setPersistentState(const juce::var& v);
52+
};
53+
54+
class Listener
55+
{
56+
public:
57+
virtual ~Listener() {};
58+
virtual void sequencerStepChanged(int step) = 0;
4659
};
4760

4861
Sequencer() = delete;
@@ -51,12 +64,18 @@ class Sequencer
5164
int getStepsCount() const noexcept { return (int)_steps.size(); }
5265
int getCurrentStep() const noexcept { return _currentStep; }
5366

67+
void addListener(Listener* listener) { _listeners.add(listener); }
68+
void removeListener(Listener* listener) { _listeners.remove(listener); }
69+
70+
juce::var getPersistentState() const;
71+
void setPersistentState(const juce::var& v);
72+
5473
/**
5574
* Capture the organ state from the engine into the current step.
5675
*/
5776
void captureCurrentStep();
5877

59-
void setStep(int index);
78+
void setStep(int index, bool captureCurrentState = true);
6079

6180
void stepForward();
6281

@@ -70,6 +89,8 @@ class Sequencer
7089
std::vector<OrganState> _steps;
7190
int _currentStep;
7291

92+
juce::ListenerList<Listener> _listeners;
93+
7394
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Sequencer)
7495
};
7596

Source/ui/DivisionView.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ DivisionView::DivisionView(aeolus::Division* division)
5757

5858
void DivisionView::cancelAllStops()
5959
{
60-
if (_division == nullptr)
61-
return;
60+
jassert(_division != nullptr);
6261

6362
for (int i = 0; i < _division->getStopsCount(); ++i) {
6463
auto& stop = _division->getStopByIndex(i);
@@ -72,15 +71,21 @@ void DivisionView::cancelAllStops()
7271

7372
void DivisionView::cancelAllLinks()
7473
{
75-
if (_division == nullptr)
76-
return;
74+
jassert(_division != nullptr);
7775

7876
_division->cancelAllLinks();
7977

8078
for (auto& button : _linkButtons)
8179
button->setToggleState(false, dontSendNotification);
8280
}
8381

82+
void DivisionView::cancelTremulant()
83+
{
84+
jassert(_division != nullptr);
85+
86+
_division->setTremulantEnabled(false);
87+
}
88+
8489
constexpr int controlPanelWidth = 130;
8590

8691
int DivisionView::getEstimatedHeightForWidth(int width) const

Source/ui/DivisionView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class DivisionView : public juce::Component
3535

3636
void cancelAllStops();
3737
void cancelAllLinks();
38+
void cancelTremulant();
3839

3940
int getEstimatedHeightForWidth(int width) const;
4041

Source/ui/SequencerView.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,23 @@ SequencerView::SequencerView(aeolus::Sequencer* sequencer)
2727
: Component{}
2828
, _sequencer{sequencer}
2929
, _stepButtons{}
30+
, _advanceButton{">>"}
3031
{
3132
jassert(sequencer != nullptr);
3233
populateStepButtons();
34+
35+
_advanceButton.setColour(TextButton::buttonColourId, Colour(0x46, 0x60, 0x16));
36+
_advanceButton.onClick = [this]() {
37+
_sequencer->stepForward();
38+
};
39+
addAndMakeVisible(_advanceButton);
40+
41+
_sequencer->addListener(this);
42+
}
43+
44+
SequencerView::~SequencerView()
45+
{
46+
_sequencer->removeListener(this);
3347
}
3448

3549
void SequencerView::resized()
@@ -40,12 +54,25 @@ void SequencerView::resized()
4054
int buttonsWidth = buttonWidth * _sequencer->getStepsCount()
4155
+ buttonPadding * (_sequencer->getStepsCount() - 1);
4256

57+
// Account for the advance button
58+
buttonsWidth += 2 * (buttonWidth + buttonPadding);
59+
4360
int x = (getWidth() - buttonsWidth) / 2;
4461

4562
for (auto* button : _stepButtons) {
4663
button->setBounds(x, buttonPadding, buttonWidth, getHeight() - 2 * buttonPadding);
4764
x += buttonWidth + buttonPadding;
4865
}
66+
67+
x += buttonPadding;
68+
69+
_advanceButton.setBounds(x, buttonPadding, 2 * buttonWidth, getHeight() - 2 * buttonPadding);
70+
}
71+
72+
void SequencerView::sequencerStepChanged(int step)
73+
{
74+
if (step >= 0 && step < _stepButtons.size())
75+
_stepButtons[step]->setToggleState(true, juce::dontSendNotification);
4976
}
5077

5178
void SequencerView::populateStepButtons()
@@ -54,6 +81,20 @@ void SequencerView::populateStepButtons()
5481

5582
for (int i = 0; i < numSteps; ++i) {
5683
auto button = std::make_unique<TextButton>(String(i + 1));
84+
button->setClickingTogglesState(true);
85+
button->setColour(TextButton::buttonColourId, Colour(0x40, 0x33, 0x33));
86+
button->setColour(TextButton::buttonOnColourId, Colour(0xDF, 0xC0, 0x36));
87+
button->setRadioGroupId(radioGroupId);
88+
89+
if (_sequencer->getCurrentStep() == i)
90+
button->setToggleState(true, juce::dontSendNotification);
91+
92+
button->onClick = [ptr=button.get(), index = i, this]() {
93+
if (ptr->getToggleState()) {
94+
_sequencer->setStep(index);
95+
}
96+
};
97+
5798
addAndMakeVisible(button.get());
5899
_stepButtons.add(button.release());
59100
}

0 commit comments

Comments
 (0)