git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
494
deps/juce/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp
vendored
Normal file
494
deps/juce/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp
vendored
Normal file
@ -0,0 +1,494 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
MPEChannelAssigner::MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse)
|
||||
: zone (new MPEZoneLayout::Zone (zoneToUse)),
|
||||
channelIncrement (zone->isLowerZone() ? 1 : -1),
|
||||
numChannels (zone->numMemberChannels),
|
||||
firstChannel (zone->getFirstMemberChannel()),
|
||||
lastChannel (zone->getLastMemberChannel()),
|
||||
midiChannelLastAssigned (firstChannel - channelIncrement)
|
||||
{
|
||||
// must be an active MPE zone!
|
||||
jassert (numChannels > 0);
|
||||
}
|
||||
|
||||
MPEChannelAssigner::MPEChannelAssigner (Range<int> channelRange)
|
||||
: isLegacy (true),
|
||||
channelIncrement (1),
|
||||
numChannels (channelRange.getLength()),
|
||||
firstChannel (channelRange.getStart()),
|
||||
lastChannel (channelRange.getEnd() - 1),
|
||||
midiChannelLastAssigned (firstChannel - channelIncrement)
|
||||
{
|
||||
// must have at least one channel!
|
||||
jassert (! channelRange.isEmpty());
|
||||
}
|
||||
|
||||
int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
|
||||
{
|
||||
if (numChannels <= 1)
|
||||
return firstChannel;
|
||||
|
||||
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||
{
|
||||
if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
|
||||
{
|
||||
midiChannelLastAssigned = ch;
|
||||
midiChannels[ch].notes.add (noteNumber);
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
|
||||
{
|
||||
if (ch == lastChannel + channelIncrement) // loop wrap-around
|
||||
ch = firstChannel;
|
||||
|
||||
if (midiChannels[ch].isFree())
|
||||
{
|
||||
midiChannelLastAssigned = ch;
|
||||
midiChannels[ch].notes.add (noteNumber);
|
||||
return ch;
|
||||
}
|
||||
|
||||
if (ch == midiChannelLastAssigned)
|
||||
break; // no free channels!
|
||||
}
|
||||
|
||||
midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
|
||||
midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
|
||||
|
||||
return midiChannelLastAssigned;
|
||||
}
|
||||
|
||||
void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)
|
||||
{
|
||||
const auto removeNote = [] (MidiChannel& ch, int noteNum)
|
||||
{
|
||||
if (ch.notes.removeAllInstancesOf (noteNum) > 0)
|
||||
{
|
||||
ch.lastNotePlayed = noteNum;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (midiChannel >= 0 && midiChannel <= 16)
|
||||
{
|
||||
removeNote (midiChannels[midiChannel], noteNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& ch : midiChannels)
|
||||
{
|
||||
if (removeNote (ch, noteNumber))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MPEChannelAssigner::allNotesOff()
|
||||
{
|
||||
for (auto& ch : midiChannels)
|
||||
{
|
||||
if (ch.notes.size() > 0)
|
||||
ch.lastNotePlayed = ch.notes.getLast();
|
||||
|
||||
ch.notes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
|
||||
{
|
||||
auto channelWithClosestNote = firstChannel;
|
||||
int closestNoteDistance = 127;
|
||||
|
||||
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||
{
|
||||
for (auto note : midiChannels[ch].notes)
|
||||
{
|
||||
auto noteDistance = std::abs (note - noteNumber);
|
||||
|
||||
if (noteDistance > 0 && noteDistance < closestNoteDistance)
|
||||
{
|
||||
closestNoteDistance = noteDistance;
|
||||
channelWithClosestNote = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channelWithClosestNote;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap)
|
||||
: zone (zoneToRemap),
|
||||
channelIncrement (zone.isLowerZone() ? 1 : -1),
|
||||
firstChannel (zone.getFirstMemberChannel()),
|
||||
lastChannel (zone.getLastMemberChannel())
|
||||
{
|
||||
// must be an active MPE zone!
|
||||
jassert (zone.numMemberChannels > 0);
|
||||
zeroArrays();
|
||||
}
|
||||
|
||||
void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept
|
||||
{
|
||||
auto channel = message.getChannel();
|
||||
|
||||
if (! zone.isUsingChannelAsMemberChannel (channel))
|
||||
return;
|
||||
|
||||
if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
|
||||
{
|
||||
clearSource (mpeSourceID);
|
||||
return;
|
||||
}
|
||||
|
||||
auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
|
||||
|
||||
if (messageIsNoteData (message))
|
||||
{
|
||||
++counter;
|
||||
|
||||
// fast path - no remap
|
||||
if (applyRemapIfExisting (channel, sourceAndChannelID, message))
|
||||
return;
|
||||
|
||||
// find existing remap
|
||||
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
|
||||
if (applyRemapIfExisting (chan, sourceAndChannelID, message))
|
||||
return;
|
||||
|
||||
// no remap necessary
|
||||
if (sourceAndChannel[channel] == notMPE)
|
||||
{
|
||||
lastUsed[channel] = counter;
|
||||
sourceAndChannel[channel] = sourceAndChannelID;
|
||||
return;
|
||||
}
|
||||
|
||||
// remap source & channel to new channel
|
||||
auto chan = getBestChanToReuse();
|
||||
|
||||
sourceAndChannel[chan] = sourceAndChannelID;
|
||||
lastUsed[chan] = counter;
|
||||
message.setChannel (chan);
|
||||
}
|
||||
}
|
||||
|
||||
void MPEChannelRemapper::reset() noexcept
|
||||
{
|
||||
for (auto& s : sourceAndChannel)
|
||||
s = notMPE;
|
||||
}
|
||||
|
||||
void MPEChannelRemapper::clearChannel (int channel) noexcept
|
||||
{
|
||||
sourceAndChannel[channel] = notMPE;
|
||||
}
|
||||
|
||||
void MPEChannelRemapper::clearSource (uint32 mpeSourceID)
|
||||
{
|
||||
for (auto& s : sourceAndChannel)
|
||||
{
|
||||
if (uint32 (s >> 5) == mpeSourceID)
|
||||
{
|
||||
s = notMPE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept
|
||||
{
|
||||
if (sourceAndChannel[channel] == sourceAndChannelID)
|
||||
{
|
||||
if (m.isNoteOff())
|
||||
sourceAndChannel[channel] = notMPE;
|
||||
else
|
||||
lastUsed[channel] = counter;
|
||||
|
||||
m.setChannel (channel);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int MPEChannelRemapper::getBestChanToReuse() const noexcept
|
||||
{
|
||||
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
|
||||
if (sourceAndChannel[chan] == notMPE)
|
||||
return chan;
|
||||
|
||||
auto bestChan = firstChannel;
|
||||
auto bestLastUse = counter;
|
||||
|
||||
for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
|
||||
{
|
||||
if (lastUsed[chan] < bestLastUse)
|
||||
{
|
||||
bestLastUse = lastUsed[chan];
|
||||
bestChan = chan;
|
||||
}
|
||||
}
|
||||
|
||||
return bestChan;
|
||||
}
|
||||
|
||||
void MPEChannelRemapper::zeroArrays()
|
||||
{
|
||||
for (int i = 0; i < 17; ++i)
|
||||
{
|
||||
sourceAndChannel[i] = 0;
|
||||
lastUsed[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct MPEUtilsUnitTests : public UnitTest
|
||||
{
|
||||
MPEUtilsUnitTests()
|
||||
: UnitTest ("MPE Utilities", UnitTestCategories::midi)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("MPEChannelAssigner");
|
||||
{
|
||||
MPEZoneLayout layout;
|
||||
|
||||
// lower
|
||||
{
|
||||
layout.setLowerZone (15);
|
||||
|
||||
// lower zone
|
||||
MPEChannelAssigner channelAssigner (layout.getLowerZone());
|
||||
|
||||
// check that channels are assigned in correct order
|
||||
int noteNum = 60;
|
||||
for (int ch = 2; ch <= 16; ++ch)
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||
|
||||
// check that note-offs are processed
|
||||
channelAssigner.noteOff (60);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
|
||||
|
||||
channelAssigner.noteOff (61);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
|
||||
|
||||
// check that assigned channel was last to play note
|
||||
channelAssigner.noteOff (65);
|
||||
channelAssigner.noteOff (66);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
|
||||
|
||||
// find closest channel playing nonequal note
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
|
||||
|
||||
// all notes off
|
||||
channelAssigner.allNotesOff();
|
||||
|
||||
// last note played
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
|
||||
|
||||
// normal assignment
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
|
||||
}
|
||||
|
||||
// upper
|
||||
{
|
||||
layout.setUpperZone (15);
|
||||
|
||||
// upper zone
|
||||
MPEChannelAssigner channelAssigner (layout.getUpperZone());
|
||||
|
||||
// check that channels are assigned in correct order
|
||||
int noteNum = 60;
|
||||
for (int ch = 15; ch >= 1; --ch)
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||
|
||||
// check that note-offs are processed
|
||||
channelAssigner.noteOff (60);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
|
||||
|
||||
channelAssigner.noteOff (61);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
|
||||
|
||||
// check that assigned channel was last to play note
|
||||
channelAssigner.noteOff (65);
|
||||
channelAssigner.noteOff (66);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
|
||||
|
||||
// find closest channel playing nonequal note
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
|
||||
|
||||
// all notes off
|
||||
channelAssigner.allNotesOff();
|
||||
|
||||
// last note played
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
|
||||
|
||||
// normal assignment
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
|
||||
}
|
||||
|
||||
// legacy
|
||||
{
|
||||
MPEChannelAssigner channelAssigner;
|
||||
|
||||
// check that channels are assigned in correct order
|
||||
int noteNum = 60;
|
||||
for (int ch = 1; ch <= 16; ++ch)
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||
|
||||
// check that note-offs are processed
|
||||
channelAssigner.noteOff (60);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
|
||||
|
||||
channelAssigner.noteOff (61);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
|
||||
|
||||
// check that assigned channel was last to play note
|
||||
channelAssigner.noteOff (65);
|
||||
channelAssigner.noteOff (66);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
|
||||
|
||||
// find closest channel playing nonequal note
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
|
||||
|
||||
// all notes off
|
||||
channelAssigner.allNotesOff();
|
||||
|
||||
// last note played
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
|
||||
|
||||
// normal assignment
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("MPEChannelRemapper");
|
||||
{
|
||||
// 3 different MPE 'sources', constant IDs
|
||||
const int sourceID1 = 0;
|
||||
const int sourceID2 = 1;
|
||||
const int sourceID3 = 2;
|
||||
|
||||
MPEZoneLayout layout;
|
||||
|
||||
{
|
||||
layout.setLowerZone (15);
|
||||
|
||||
// lower zone
|
||||
MPEChannelRemapper channelRemapper (layout.getLowerZone());
|
||||
|
||||
// first source, shouldn't remap
|
||||
for (int ch = 2; ch <= 16; ++ch)
|
||||
{
|
||||
auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
|
||||
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
|
||||
expectEquals (noteOn.getChannel(), ch);
|
||||
}
|
||||
|
||||
auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
|
||||
|
||||
// remap onto oldest last-used channel
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
|
||||
expectEquals (noteOn.getChannel(), 2);
|
||||
|
||||
// remap onto oldest last-used channel
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
|
||||
expectEquals (noteOn.getChannel(), 3);
|
||||
|
||||
// remap to correct channel for source ID
|
||||
auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
|
||||
expectEquals (noteOff.getChannel(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
layout.setUpperZone (15);
|
||||
|
||||
// upper zone
|
||||
MPEChannelRemapper channelRemapper (layout.getUpperZone());
|
||||
|
||||
// first source, shouldn't remap
|
||||
for (int ch = 15; ch >= 1; --ch)
|
||||
{
|
||||
auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
|
||||
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
|
||||
expectEquals (noteOn.getChannel(), ch);
|
||||
}
|
||||
|
||||
auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
|
||||
|
||||
// remap onto oldest last-used channel
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
|
||||
expectEquals (noteOn.getChannel(), 15);
|
||||
|
||||
// remap onto oldest last-used channel
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
|
||||
expectEquals (noteOn.getChannel(), 14);
|
||||
|
||||
// remap to correct channel for source ID
|
||||
auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
|
||||
channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
|
||||
expectEquals (noteOff.getChannel(), 14);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static MPEUtilsUnitTests MPEUtilsUnitTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user