Skip to content

Commit 3eb0ef2

Browse files
committed
Add global tuning settings.
1 parent c8cbde0 commit 3eb0ef2

File tree

14 files changed

+331
-10
lines changed

14 files changed

+331
-10
lines changed

Aeolus.jucer

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
<FILE id="ABDlK8" name="DivisionView.cpp" compile="1" resource="0"
7373
file="Source/ui/DivisionView.cpp"/>
7474
<FILE id="oWzSbD" name="DivisionView.h" compile="0" resource="0" file="Source/ui/DivisionView.h"/>
75+
<FILE id="uSLj0t" name="GlobalTuningComponent.cpp" compile="1" resource="0"
76+
file="Source/ui/GlobalTuningComponent.cpp"/>
77+
<FILE id="LrvnWd" name="GlobalTuningComponent.h" compile="0" resource="0"
78+
file="Source/ui/GlobalTuningComponent.h"/>
7579
<FILE id="si86HY" name="LevelIndicator.cpp" compile="1" resource="0"
7680
file="Source/ui/LevelIndicator.cpp"/>
7781
<FILE id="neJNeV" name="LevelIndicator.h" compile="0" resource="0"

Source/PluginEditor.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "aeolus/engine.h"
2121
#include "ui/CustomLookAndFeel.h"
22+
#include "ui/GlobalTuningComponent.h"
2223

2324
#include "PluginProcessor.h"
2425
#include "PluginEditor.h"
@@ -47,6 +48,7 @@ AeolusAudioProcessorEditor::AeolusAudioProcessorEditor (AeolusAudioProcessor& p)
4748
, _volumeSlider{*p.getParametersContainer().volume, juce::Slider::LinearHorizontal}
4849
, _volumeLevelL{p.getEngine().getVolumeLevel().left, ui::LevelIndicator::Orientation::Horizontal}
4950
, _volumeLevelR{p.getEngine().getVolumeLevel().right, ui::LevelIndicator::Orientation::Horizontal}
51+
, _tuningButton{"Tune"}
5052
, _panicButton{"PANIC"}
5153
, _cancelButton{"Cancel"}
5254
, _midiControlChannelLabel{{}, {"Control channel"}}
@@ -106,6 +108,31 @@ AeolusAudioProcessorEditor::AeolusAudioProcessorEditor (AeolusAudioProcessor& p)
106108
_volumeSlider.setSkewFactor(0.5f);
107109
_volumeSlider.setLookAndFeel(&ui::CustomLookAndFeel::getInstance());
108110

111+
addAndMakeVisible(_tuningButton);
112+
_tuningButton.onClick = [this] {
113+
auto content = std::make_unique<ui::GlobalTuningComponent>();
114+
content->setSize(240, 140);
115+
auto* contentPtr = content.get();
116+
117+
auto& box = CallOutBox::launchAsynchronously(std::move(content), _tuningButton.getBounds(), this);
118+
contentPtr->onCancel = [&box] { box.dismiss(); };
119+
contentPtr->onOk = [&box, contentPtr] {
120+
const float freq = contentPtr->getTuningFrequency();
121+
const auto scaleType = contentPtr->getTuningScaleType();
122+
123+
auto* g = aeolus::EngineGlobal::getInstance();
124+
const bool changed = (g->getTuningFrequency() != freq) || (g->getScale().getType() != scaleType);
125+
126+
if (changed) {
127+
g->setTuningFrequency(freq);
128+
g->setScaleType(scaleType);
129+
g->rebuildRankwaves();
130+
}
131+
132+
box.dismiss();
133+
};
134+
};
135+
109136
_panicButton.setColour(TextButton::textColourOffId, Colour(0xFF, 0xFF, 0xFF));
110137
_panicButton.setColour(TextButton::buttonColourId, Colour(0xCC, 0x33, 0x00));
111138
addAndMakeVisible(_panicButton);
@@ -185,7 +212,7 @@ void AeolusAudioProcessorEditor::resized()
185212

186213
constexpr int margin = 5;
187214

188-
_versionLabel.setBounds(getWidth() - 60, margin, 60 - margin, 20);
215+
_versionLabel.setBounds(getWidth() - 60, getHeight() - 20, 60 - margin, 20);
189216

190217
_cpuLoadLabel.setBounds(margin, margin, 70, 20);
191218
_cpuLoadValueLabel.setBounds(_cpuLoadLabel.getRight() + margin, margin, 36, 20);
@@ -202,7 +229,9 @@ void AeolusAudioProcessorEditor::resized()
202229
_volumeLevelL.setBounds(_volumeSlider.getX() + 5, _volumeSlider.getY() + 2, _volumeSlider.getWidth() - 10, 2);
203230
_volumeLevelR.setBounds(_volumeSlider.getX() + 5, _volumeSlider.getY() + _volumeSlider.getHeight() - 4, _volumeSlider.getWidth() - 10, 2);
204231

205-
_panicButton.setBounds(_volumeSlider.getRight() + 40, margin, 50, 20);
232+
_tuningButton.setBounds(_volumeSlider.getRight() + 40, margin, 60, 20);
233+
234+
_panicButton.setBounds(getWidth() - 90, margin, 50, 20);
206235

207236
constexpr int T = margin * 2 + 20;
208237
constexpr int sequencerHeight = 26;

Source/PluginEditor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class AeolusAudioProcessorEditor : public juce::AudioProcessorEditor,
8989
ui::LevelIndicator _volumeLevelL;
9090
ui::LevelIndicator _volumeLevelR;
9191

92+
juce::TextButton _tuningButton;
93+
9294
/// Kill all active voices button
9395
juce::TextButton _panicButton;
9496

Source/PluginProcessor.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ AeolusAudioProcessor::AeolusAudioProcessor()
3333
, _panicRequest{false}
3434
{
3535
_engine.getMidiKeyboardState().addListener(this);
36+
37+
aeolus::EngineGlobal::getInstance()->registerProcessorProxy(this);
3638
}
3739

3840
AeolusAudioProcessor::~AeolusAudioProcessor()
3941
{
4042
// If we don't do this there is a leaked AccessibilityHandler instance detected.
4143
_engine.getMidiKeyboardState().removeListener(this);
44+
45+
aeolus::EngineGlobal::getInstance()->unregisterProcessorProxy(this);
4246
}
4347

4448
//==============================================================================

Source/PluginProcessor.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,25 @@
2929
/**
3030
*/
3131
class AeolusAudioProcessor : public juce::AudioProcessor,
32-
juce::MidiKeyboardState::Listener
32+
juce::MidiKeyboardState::Listener,
33+
aeolus::EngineGlobal::ProcessorProxy
3334
{
3435
public:
3536
//==============================================================================
3637
AeolusAudioProcessor();
3738
~AeolusAudioProcessor() override;
3839

39-
aeolus::Engine& getEngine() noexcept { return _engine; }
40-
4140
Parameters& getParametersContainer() noexcept { return _parameters; }
4241

4342
void panic() noexcept { _panicRequest = true; }
4443

44+
//==============================================================================
45+
// aeolus::EngineGlobal::ProcessorProxy
46+
juce::AudioProcessor* getAudioProcessor() override { return this; }
47+
aeolus::Engine& getEngine() override { return _engine; }
48+
void killAllVoices() override { panic(); }
49+
int getNumberOfActiveVoices() override { return _engine.getVoiceCount(); }
50+
4551
//==============================================================================
4652
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
4753
void releaseResources() override;

Source/aeolus/engine.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,19 @@ EngineGlobal::EngineGlobal()
6060
loadIRs();
6161
}
6262

63+
void EngineGlobal::registerProcessorProxy(ProcessorProxy* proxy)
64+
{
65+
jassert(proxy != nullptr);
66+
_processors.addIfNotAlreadyThere(proxy);
67+
}
68+
69+
void EngineGlobal::unregisterProcessorProxy(ProcessorProxy* proxy)
70+
{
71+
jassert(proxy != nullptr);
72+
_processors.removeAllInstancesOf(proxy);
73+
}
74+
75+
6376
StringArray EngineGlobal::getAllStopNames() const
6477
{
6578
StringArray names;
@@ -80,6 +93,8 @@ Rankwave* EngineGlobal::getStopByName(const String& name)
8093

8194
void EngineGlobal::updateStops(float sampleRate)
8295
{
96+
_sampleRate = sampleRate;
97+
8398
ThreadPool threadPool;
8499
std::atomic<int> done((int)_rankwaves.size());
85100
WaitableEvent wait;
@@ -102,6 +117,35 @@ void EngineGlobal::updateStops(float sampleRate)
102117
*/
103118
}
104119

120+
void EngineGlobal::rebuildRankwaves()
121+
{
122+
// Prepare all the rankwaves to be retuned
123+
for (auto* rw : _rankwaves) {
124+
rw->retunePipes(_scale, _tuningFrequency);
125+
}
126+
127+
// Kill all the active voices
128+
int numActiveVoices = 0;
129+
130+
for (auto* processor : _processors) {
131+
processor->killAllVoices();
132+
numActiveVoices += processor->getNumberOfActiveVoices();
133+
}
134+
135+
Thread::sleep(10);
136+
137+
// Wait for the voices to stop
138+
while (numActiveVoices > 0) {
139+
numActiveVoices = 0;
140+
for (auto* processor : _processors) {
141+
numActiveVoices += processor->getNumberOfActiveVoices();
142+
Thread::sleep(100);
143+
}
144+
}
145+
146+
updateStops(_sampleRate);
147+
}
148+
105149
void EngineGlobal::loadRankwaves()
106150
{
107151
auto& model = *Model::getInstance();

Source/aeolus/engine.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
AEOLUS_NAMESPACE_BEGIN
3838

39+
class Engine;
40+
3941
/**
4042
* @brief A global shared instance of the organ engine.
4143
*
@@ -45,6 +47,18 @@ class EngineGlobal : public juce::DeletedAtShutdown
4547
{
4648
public:
4749

50+
class ProcessorProxy
51+
{
52+
public:
53+
virtual juce::AudioProcessor* getAudioProcessor() = 0;
54+
virtual Engine& getEngine() = 0;
55+
virtual void killAllVoices() = 0;
56+
virtual int getNumberOfActiveVoices() = 0;
57+
};
58+
59+
void registerProcessorProxy(ProcessorProxy* proxy);
60+
void unregisterProcessorProxy(ProcessorProxy* proxy);
61+
4862
/**
4963
* Impulse response descriptor for IRs embedded as binary resources.
5064
*/
@@ -72,11 +86,13 @@ class EngineGlobal : public juce::DeletedAtShutdown
7286
void updateStops(float sampleRate);
7387

7488
float getTuningFrequency() const noexcept { return _tuningFrequency; }
75-
void setTuningFrequenct(float f) noexcept { _tuningFrequency = f; }
89+
void setTuningFrequency(float f) noexcept { _tuningFrequency = f; }
7690

7791
const Scale& getScale() const noexcept { return _scale; }
7892
void setScaleType(Scale::Type type) noexcept { _scale.setType(type); }
7993

94+
void rebuildRankwaves();
95+
8096
JUCE_DECLARE_SINGLETON (EngineGlobal, false)
8197

8298
private:
@@ -86,12 +102,15 @@ class EngineGlobal : public juce::DeletedAtShutdown
86102
void loadRankwaves();
87103
void loadIRs();
88104

105+
juce::Array<ProcessorProxy*> _processors;
106+
89107
juce::OwnedArray<Rankwave> _rankwaves;
90108
juce::HashMap<juce::String, Rankwave*> _rankwavesByName;
91109

92110
std::vector<IR> _irs;
93111
int _longestIRLength; ///< Longest IR length in samples
94112

113+
float _sampleRate;
95114
Scale _scale;
96115
float _tuningFrequency;
97116
};

Source/aeolus/globals.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ constexpr static float TREMULANT_LEVEL = 1.0f;
8282
/// Number of steps in the sequencer.
8383
constexpr static int SEQUENCER_N_STEPS = 16;
8484

85+
/// mid-A tuning frequency.
86+
constexpr static float TUNING_FREQUENCY_MIN = 350.0f;
87+
constexpr static float TUNING_FREQUENCY_MAX = 550.0f;
88+
constexpr static float TUNING_FREQUENCY_STEP = 1.0f;
89+
constexpr static float TUNING_FREQUENCY_DEFAULT = 440.0f;
90+
8591
//==============================================================================
8692

8793
// MIDI controls

Source/aeolus/rankwave.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Pipewave::Pipewave(Addsynth& model, int note, float freq)
3030
: _model(model)
3131
, _note(note)
3232
, _freq(freq)
33+
, _needsToBeRebuilt{true}
3334
{
3435

3536
}
@@ -41,7 +42,7 @@ float Pipewave::getPipeFrequency() const noexcept
4142

4243
void Pipewave::prepateToPlay(float sampleRate)
4344
{
44-
if (_wavetable.size() == 0 || _sampleRate != sampleRate) {
45+
if (_wavetable.size() == 0 || _sampleRate != sampleRate || _needsToBeRebuilt.load()) {
4546
_sampleRate = sampleRate;
4647

4748
genwave();
@@ -51,8 +52,12 @@ void Pipewave::prepateToPlay(float sampleRate)
5152
Pipewave::State Pipewave::trigger()
5253
{
5354
Pipewave::State state = {};
54-
state.pipewave = this;
55-
state.env = Pipewave::Attack;
55+
56+
if (!_needsToBeRebuilt.load()) {
57+
state.pipewave = this;
58+
state.env = Pipewave::Attack;
59+
}
60+
5661
return state;
5762
}
5863

@@ -312,6 +317,8 @@ void Pipewave::genwave()
312317

313318
for (int i = 0; i < _sampleStep * (SUB_FRAME_LENGTH + 4); ++i)
314319
_attackStartPtr[i + _attackLength + _loopLength] = _attackStartPtr[i + _attackLength];
320+
321+
_needsToBeRebuilt = false;
315322
}
316323

317324
void Pipewave::looplen(float f, float sampleRate, int lmax, int& aa, int& bb)
@@ -424,7 +431,7 @@ void Rankwave::retunePipes(const Scale& scale, float tuningFrequency)
424431
for (int i = _noteMin; i <= _noteMax; ++i) {
425432
Pipewave* pipe = _pipes[i - _noteMin];
426433
pipe->setFrequency(ldexpf(fbase * s[i % 12], i/12 - 5));
427-
434+
pipe->setNeedsToBeRebuilt(true);
428435
}
429436
}
430437

Source/aeolus/rankwave.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ class Pipewave
7777
// After changing the frequency of the pipe, the wavetable must be regenerated
7878
// by calling prepareToPlay() method.
7979
void setFrequency(float f) noexcept { _freq = f; }
80+
void setNeedsToBeRebuilt(bool v) noexcept { _needsToBeRebuilt = v; }
81+
bool doesNeedToBeRebuilt() const noexcept { return _needsToBeRebuilt.load(); }
8082

8183
int getNote() const noexcept { return _note + _model.getNoteMin(); }
8284
float getFreqency() const noexcept { return _freq; }
@@ -100,6 +102,10 @@ class Pipewave
100102
float _freq;
101103
float _sampleRate;
102104

105+
// Tells whether this pipewave needs to be re-generated.
106+
// This is required for example when changing the tuninig.
107+
std::atomic<bool> _needsToBeRebuilt;
108+
103109
int _attackLength; // _l0
104110
int _loopLength; // _l1
105111
int _sampleStep; // _k_s

0 commit comments

Comments
 (0)