Skip to content

Commit 304f403

Browse files
committed
#4 Trigger or release voices depending on the stop enablement based on the current state of the keys.
1 parent 39fac33 commit 304f403

File tree

6 files changed

+124
-21
lines changed

6 files changed

+124
-21
lines changed

Source/aeolus/division.cpp

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Division::Division(Engine& engine, const String& name)
4444
, _swellFilterStateR{}
4545
, _stops{}
4646
, _activeVoices{}
47+
, _keysState{}
4748
, _triggerFlag{}
4849
, _volumeLevel{}
4950
{
@@ -327,28 +328,10 @@ void Division::noteOn(int note, int midiChannel)
327328

328329
_triggerFlag = true;
329330

330-
for (const auto& stop : _stops) {
331-
if (!stop.isEnabled())
332-
continue;
333-
334-
for (const auto& zone : stop.getZones()) {
335-
if (zone.isForKey(note)) {
336-
for (auto* rw : zone.rankwaves) {
337-
auto state = rw->trigger(note);
331+
for (int stopIndex = 0; stopIndex < (int)_stops.size(); ++stopIndex)
332+
triggerVoicesForStop(stopIndex, note);
338333

339-
// Rankwave may be in the middle of construction, in this case
340-
// we don't trigger a voice.
341-
if (state.isTriggered()) {
342-
state.gain = stop.getGain();
343-
state.chiffGain = stop.getChiffGain();
344-
345-
if (auto* voice = _engine.getVoicePool().trigger(state))
346-
_activeVoices.append(voice);
347-
}
348-
}
349-
}
350-
}
351-
}
334+
_keysState.set(note);
352335

353336
// Forward to the linked divisions
354337
for (auto& link : _linkedDivisions) {
@@ -377,6 +360,8 @@ void Division::noteOff(int note, int midiChannel)
377360
voice = voice->next();
378361
}
379362

363+
_keysState.reset(note);
364+
380365
// Forward to the linked divisions
381366
for (auto& link : _linkedDivisions) {
382367
if (link.enabled) {
@@ -387,6 +372,8 @@ void Division::noteOff(int note, int midiChannel)
387372

388373
void Division::allNotesOff()
389374
{
375+
_keysState.reset();
376+
390377
auto* voice = _activeVoices.first();
391378

392379
while (voice != nullptr) {
@@ -400,6 +387,9 @@ bool Division::process(AudioBuffer<float>& targetBuffer, AudioBuffer<float>& voi
400387
jassert(targetBuffer.getNumSamples() == SUB_FRAME_LENGTH);
401388
jassert(voiceBuffer.getNumSamples() == SUB_FRAME_LENGTH);
402389

390+
releaseVoicesOfDisabledStops();
391+
triggerVoicesOfEnabledStops();
392+
403393
auto* voice = _activeVoices.first();
404394

405395
if (voice == nullptr)
@@ -500,4 +490,90 @@ void Division::modulate(juce::AudioBuffer<float>& targetBuffer, const juce::Audi
500490
#endif
501491
}
502492

493+
void Division::releaseVoicesOfDisabledStops()
494+
{
495+
auto* voice = _activeVoices.first();
496+
497+
while (voice != nullptr) {
498+
if (voice->isActive()) {
499+
const int stopIndex = voice->stopIndex();
500+
501+
if (isPositiveAndBelow(stopIndex, _stops.size())) {
502+
const auto& stop = _stops[stopIndex];
503+
504+
if (!stop.isEnabled())
505+
voice->release();
506+
}
507+
}
508+
509+
voice = voice->next();
510+
}
511+
}
512+
513+
void Division::triggerVoicesOfEnabledStops()
514+
{
515+
for (int stopIndex = 0; stopIndex < _stops.size(); ++stopIndex) {
516+
auto& stop = _stops[stopIndex];
517+
518+
if (!stop.isEnabled())
519+
continue;
520+
521+
bool hasVoices = false;
522+
523+
auto* voice = _activeVoices.first();
524+
525+
while (voice != nullptr) {
526+
if (voice->stopIndex() == stopIndex) {
527+
hasVoices = true;
528+
break;
529+
}
530+
531+
voice = voice->next();
532+
}
533+
534+
// Trigger voices for enabled stops
535+
if (!hasVoices) {
536+
for (int note = 0; note < _keysState.size(); ++note) {
537+
if (_keysState[note])
538+
triggerVoicesForStop(stopIndex, note);
539+
}
540+
}
541+
}
542+
}
543+
544+
bool Division::triggerVoicesForStop(int stopIndex, int note)
545+
{
546+
jassert(isPositiveAndBelow(stopIndex, _stops.size()));
547+
548+
const auto& stop = _stops[stopIndex];
549+
550+
if (!stop.isEnabled())
551+
return false;
552+
553+
bool voiceTriggered = false;
554+
555+
for (const auto& zone : stop.getZones()) {
556+
if (zone.isForKey(note)) {
557+
for (auto* rw : zone.rankwaves) {
558+
auto state = rw->trigger(note);
559+
560+
// Rankwave may be in the middle of construction, in this case
561+
// we don't trigger a voice.
562+
if (state.isTriggered()) {
563+
state.gain = stop.getGain();
564+
state.chiffGain = stop.getChiffGain();
565+
566+
if (auto* voice = _engine.getVoicePool().trigger(state)) {
567+
voice->setStopIndex(stopIndex);
568+
_activeVoices.append(voice);
569+
voiceTriggered = true;
570+
}
571+
}
572+
}
573+
}
574+
}
575+
576+
return voiceTriggered;
577+
}
578+
503579
AEOLUS_NAMESPACE_END

Source/aeolus/division.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include <atomic>
3131
#include <vector>
32+
#include <bitset>
3233

3334
AEOLUS_NAMESPACE_BEGIN
3435

@@ -141,6 +142,9 @@ class Division
141142
bool process(juce::AudioBuffer<float>& targetBuffer, juce::AudioBuffer<float>& voiceBuffer);
142143
void modulate(juce::AudioBuffer<float>& targetBuffer, const juce::AudioBuffer<float>& tremulantBuffer);
143144

145+
void releaseVoicesOfDisabledStops();
146+
void triggerVoicesOfEnabledStops();
147+
144148
List<Voice>& getActiveVoices() noexcept { return _activeVoices; }
145149

146150
/**
@@ -158,6 +162,8 @@ class Division
158162

159163
private:
160164

165+
bool triggerVoicesForStop(int stopIndex, int note);
166+
161167
Engine& _engine;
162168

163169
juce::String _name; ///< The division name.
@@ -190,6 +196,8 @@ class Division
190196

191197
List<Voice> _activeVoices; ///< Active voices on this division.
192198

199+
std::bitset<128> _keysState; ///< MIDI keys state 1 = on, 0 = off.
200+
193201
/// Tells whether this division has been triggered.
194202
/// This is used to avoid a division to be triggered multiple
195203
/// times by the same not on/off even, which is the case

Source/aeolus/engine.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ class Engine
232232
*/
233233
void process(float* outL, float* outR, int numFrames, bool isNonRealtime = false);
234234

235+
// Multibus version of the processing (does not include the convolver).
235236
void process(juce::AudioBuffer<float>& out, bool isNonRealtime = false);
236237

237238
/**

Source/aeolus/globals.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ constexpr static int NOTE_MIN = 36;
6666
/// Highest possible note.
6767
constexpr static int NOTE_MAX = 96;
6868

69+
/// Total number of MIDI notes.
70+
constexpr static int TOTAL_NOTES = 128;
71+
6972
/// Maxumim number of pipes in combined stops (like mixtures)
7073
constexpr static int MAX_RANK = 5;
7174

Source/aeolus/voice.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ AEOLUS_NAMESPACE_BEGIN
2727
Voice::Voice(Engine& engine)
2828
: _engine(engine)
2929
, _state{}
30+
, _stopIndex{-1}
3031
, _buffer{0}
3132
, _delayLine{SAMPLE_RATE}
3233
, _delay{0.0f}
@@ -93,6 +94,7 @@ void Voice::release()
9394
void Voice::reset()
9495
{
9596
_state.reset();
97+
_stopIndex = -1;
9698

9799
memset(_buffer, 0, sizeof(float) * SUB_FRAME_LENGTH);
98100

@@ -140,6 +142,11 @@ bool Voice::isOver() const noexcept
140142
return (_state.env == Pipewave::Over) && _postReleaseCounter == 0;
141143
}
142144

145+
bool Voice::isActive() const noexcept
146+
{
147+
return _state.env == Pipewave::Attack;
148+
}
149+
143150
bool Voice::isForNote(int note) const noexcept
144151
{
145152
if (_state.pipewave != nullptr)

Source/aeolus/voice.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,23 @@ class Voice : public ListItem<Voice>
4848
void reset();
4949
void process(float* outL, float* outR);
5050
bool isOver() const noexcept;
51+
bool isActive() const noexcept;
5152
bool isForNote(int note) const noexcept;
5253

54+
void setStopIndex(int idx) noexcept { _stopIndex = idx; }
55+
int stopIndex() const noexcept { return _stopIndex; }
56+
5357
void resetAndReturnToPool();
5458

5559
float getPanPosition() const noexcept { return _panPosition; }
5660

5761
private:
5862
Engine& _engine;
5963
Pipewave::State _state; ///< Pipe state associated with this voice.
64+
65+
/// Index of the stop associated with this voice.
66+
/// This is used to tell which stops are voiced.
67+
int _stopIndex;
6068

6169
float _buffer[SUB_FRAME_LENGTH];
6270

0 commit comments

Comments
 (0)