Skip to content

Commit 7aaba70

Browse files
authored
Merge pull request #35 from Archie3d/34-division-coupling-trigger
#34 division coupling trigger
2 parents 461fbf5 + 7dacdc9 commit 7aaba70

6 files changed

Lines changed: 107 additions & 13 deletions

File tree

CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,10 @@ endif()
224224

225225
if(APPLE)
226226
target_compile_definitions(${TARGET} PUBLIC JUCE_AU=1)
227-
#target_compile_options(${TARGET} PRIVATE "-mfma")
227+
228+
# This option is relevant to x86_64 target architecture,
229+
# but I do not see how to detect the target architecture
230+
# during CMake generation time. So this will cause warnings when
231+
# compiling for arm64.
232+
target_compile_options(${TARGET} PRIVATE "-mfma")
228233
endif()

Source/aeolus/division.cpp

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,19 @@ void Division::setPersistentState(const juce::var& v)
181181
}
182182
}
183183

184-
void Division::populateLinkedDivisions()
184+
void Division::clearLinkedDivisions()
185185
{
186186
_linkedDivisions.clear();
187+
_linkedFromDivisions.clear();
188+
}
187189

190+
void Division::populateLinkedDivisions()
191+
{
188192
for (const auto& name : _linkedDivisionNames) {
189193
if (auto* division = _engine.getDivisionByName(name)) {
190-
Link link{division, false};
194+
Link link{ division, false };
191195
_linkedDivisions.push_back(link);
196+
division->_linkedFromDivisions.push_back(this);
192197
}
193198
}
194199
}
@@ -358,7 +363,10 @@ void Division::noteOn(int note, int midiChannel)
358363
for (int stopIndex = 0; stopIndex < (int)_stops.size(); ++stopIndex)
359364
triggerVoicesForStop(stopIndex, note);
360365

361-
_keysState.set(note);
366+
if (midiChannel != 0) {
367+
// Update keys state only when triggered by the assigned MIDI channel
368+
_keysState.set(note);
369+
}
362370

363371
// Forward to the linked divisions
364372
for (auto& link : _linkedDivisions) {
@@ -387,7 +395,10 @@ void Division::noteOff(int note, int midiChannel)
387395
voice = voice->next();
388396
}
389397

390-
_keysState.reset(note);
398+
if (midiChannel != 0) {
399+
// Update keys state only when triggered by the assigned MIDI channel.
400+
_keysState.reset(note);
401+
}
391402

392403
// Forward to the linked divisions
393404
for (auto& link : _linkedDivisions) {
@@ -438,6 +449,7 @@ bool Division::process(AudioBuffer<float>& targetBuffer, AudioBuffer<float>& voi
438449
jassert(targetBuffer.getNumSamples() == SUB_FRAME_LENGTH);
439450
jassert(voiceBuffer.getNumSamples() == SUB_FRAME_LENGTH);
440451

452+
updateAggregatedKeysState();
441453
releaseVoicesOfDisabledStops();
442454
triggerVoicesOfEnabledStops();
443455

@@ -554,28 +566,38 @@ void Division::releaseVoicesOfDisabledStops()
554566
auto* voice = _activeVoices.first();
555567

556568
while (voice != nullptr) {
569+
bool shouldRelease{ false };
570+
557571
if (voice->isActive()) {
558572
const int stopIndex = voice->stopIndex();
559573

560574
if (isPositiveAndBelow(stopIndex, _stops.size())) {
561575
const auto& stop = _stops[stopIndex];
562576

563577
if (!stop.isEnabled())
564-
voice->release();
578+
shouldRelease = true;
579+
580+
if (voice->getNote() >= 0 && !_aggregatedKeysState[voice->getNote()])
581+
shouldRelease = true;
565582
}
566583
}
567584

585+
if (shouldRelease)
586+
voice->release();
587+
568588
voice = voice->next();
569589
}
570590
}
571591

572592
void Division::triggerVoicesOfEnabledStops()
573593
{
574-
if (_keysState.none()) {
594+
if (_aggregatedKeysState.none()) {
575595
// No keys are pressed
576596
return;
577597
}
578598

599+
std::bitset<TOTAL_NOTES> missingNotes{ _aggregatedKeysState };
600+
579601
for (int stopIndex = 0; stopIndex < _stops.size(); ++stopIndex) {
580602
auto& stop = _stops[stopIndex];
581603

@@ -587,8 +609,11 @@ void Division::triggerVoicesOfEnabledStops()
587609
auto* voice = _activeVoices.first();
588610

589611
while (voice != nullptr) {
590-
if (voice->stopIndex() == stopIndex) {
612+
const int voiceNote{ voice->getNote() };
613+
614+
if (voice->stopIndex() == stopIndex && voiceNote >= 0 && _aggregatedKeysState[voiceNote]) {
591615
hasVoices = true;
616+
missingNotes[voiceNote] = 0;
592617
break;
593618
}
594619

@@ -597,18 +622,35 @@ void Division::triggerVoicesOfEnabledStops()
597622

598623
// Trigger voices for enabled stops
599624
if (!hasVoices) {
600-
for (int note = 0; note < _keysState.size(); ++note) {
601-
if (_keysState[note])
625+
for (int note = 0; note < missingNotes.size(); ++note) {
626+
if (missingNotes[note])
602627
triggerVoicesForStop(stopIndex, note);
603628
}
604629
}
605630
}
606631
}
607632

633+
void Division::updateAggregatedKeysState()
634+
{
635+
_aggregatedKeysState = _keysState;
636+
637+
for (const auto* division : _linkedFromDivisions) {
638+
for (const auto& link : division->_linkedDivisions) {
639+
if (link.division == this && link.enabled) {
640+
_aggregatedKeysState |= division->_aggregatedKeysState;
641+
break;
642+
}
643+
}
644+
}
645+
}
646+
608647
bool Division::triggerVoicesForStop(int stopIndex, int note)
609648
{
610649
jassert(isPositiveAndBelow(stopIndex, _stops.size()));
611650

651+
if (isAlreadyVoiced(stopIndex, note))
652+
return true;
653+
612654
const auto& stop = _stops[stopIndex];
613655

614656
if (!stop.isEnabled())
@@ -640,4 +682,19 @@ bool Division::triggerVoicesForStop(int stopIndex, int note)
640682
return voiceTriggered;
641683
}
642684

685+
bool Division::isAlreadyVoiced(int stopIndex, int note)
686+
{
687+
auto* voice = _activeVoices.first();
688+
689+
while (voice != nullptr) {
690+
if (voice->isActive() && voice->stopIndex() == stopIndex && voice->isForNote(note)) {
691+
return true;
692+
}
693+
694+
voice = voice->next();
695+
}
696+
697+
return false;
698+
}
699+
643700
AEOLUS_NAMESPACE_END

Source/aeolus/division.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ class Division
8585
juce::String getName() const { return _name; }
8686
juce::String getMnemonic() const { return _mnemonic; }
8787

88+
/**
89+
* Remove all the links between the divisions.
90+
*/
91+
void clearLinkedDivisions();
92+
8893
/**
8994
* Populate linked divisions from the division names.
9095
* This method must be called by the engine when all the divisions
@@ -164,8 +169,16 @@ class Division
164169

165170
private:
166171

172+
/**
173+
* This will construct the keys aggregated state from the division's keys state
174+
* and all the coupled from divisions.
175+
*/
176+
void updateAggregatedKeysState();
177+
167178
bool triggerVoicesForStop(int stopIndex, int note);
168179

180+
bool isAlreadyVoiced(int stopIndex, int node);
181+
169182
Engine& _engine;
170183

171184
juce::String _name; ///< The division name.
@@ -174,6 +187,7 @@ class Division
174187
/// List of linked divisions names.
175188
juce::StringArray _linkedDivisionNames;
176189
std::vector<Link> _linkedDivisions;
190+
std::vector<Division*> _linkedFromDivisions;
177191

178192
bool _hasSwell; ///< Whetehr this division has a swell control.
179193
bool _hasTremulant; ///< Whether this division has a remulant control.
@@ -203,6 +217,7 @@ class Division
203217
List<Voice> _activeVoices; ///< Active voices on this division.
204218

205219
std::bitset<TOTAL_NOTES> _keysState; ///< MIDI keys state 1 = on, 0 = off.
220+
std::bitset<TOTAL_NOTES> _aggregatedKeysState; ///< MIDI keys state aggregated from the coupled divisions.
206221

207222
/// Tells whether this division has been triggered.
208223
/// This is used to avoid a division to be triggered multiple

Source/aeolus/engine.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,18 +732,26 @@ void Engine::populateDivisions()
732732
loadDivisionsFromConfig(stream);
733733
}
734734

735+
// Remove all the links if any.
736+
for (auto* division : _divisions) {
737+
division->clearLinkedDivisions();
738+
}
739+
740+
735741
// Update division links after they've been loaded.
736742
for (auto* division : _divisions) {
737743
division->populateLinkedDivisions();
738744
}
745+
746+
// @todo Do we want the divisions to be reordered by the couplings?
739747
}
740748

741749
// @internal Helper to populate key switches from a single number or a list
742750
static void populateKeySwitchesVector(std::vector<int>& switches, const var& v)
743751
{
744752
if (v.isVoid())
745753
return;
746-
754+
747755
switches.clear();
748756

749757
if (v.isInt()) {

Source/aeolus/voice.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ bool Voice::isForNote(int note) const noexcept
155155
return false;
156156
}
157157

158+
int Voice::getNote() const
159+
{
160+
if (_state.pipewave != nullptr)
161+
return _state.pipewave->getNote();
162+
163+
return -1;
164+
}
165+
158166
void Voice::resetAndReturnToPool()
159167
{
160168
_engine.getVoicePool().resetAndReturnToPool(this);

Source/aeolus/voice.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Voice : public ListItem<Voice>
5050
bool isOver() const noexcept;
5151
bool isActive() const noexcept;
5252
bool isForNote(int note) const noexcept;
53+
int getNote() const;
5354

5455
void setStopIndex(int idx) noexcept { _stopIndex = idx; }
5556
int stopIndex() const noexcept { return _stopIndex; }
@@ -61,7 +62,7 @@ class Voice : public ListItem<Voice>
6162
private:
6263
Engine& _engine;
6364
Pipewave::State _state; ///< Pipe state associated with this voice.
64-
65+
6566
/// Index of the stop associated with this voice.
6667
/// This is used to tell which stops are voiced.
6768
int _stopIndex;
@@ -80,7 +81,7 @@ class Voice : public ListItem<Voice>
8081

8182
/// Stereo spatial modeller.
8283
dsp::SpatialSource _spatialSource;
83-
84+
8485
/// Counter to account for the delayed sound before recycling the voice.
8586
size_t _postReleaseCounter;
8687
};

0 commit comments

Comments
 (0)