| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,315 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
|
|
||
| //============================================================================== | ||
| /** | ||
| A sequence of timestamped midi messages. | ||
| This allows the sequence to be manipulated, and also to be read from and | ||
| written to a standard midi file. | ||
| @see MidiMessage, MidiFile | ||
| @tags{Audio} | ||
| */ | ||
| class JUCE_API MidiMessageSequence | ||
| { | ||
| public: | ||
| //============================================================================== | ||
| /** Creates an empty midi sequence object. */ | ||
| MidiMessageSequence(); | ||
|
|
||
| /** Creates a copy of another sequence. */ | ||
| MidiMessageSequence (const MidiMessageSequence&); | ||
|
|
||
| /** Replaces this sequence with another one. */ | ||
| MidiMessageSequence& operator= (const MidiMessageSequence&); | ||
|
|
||
| /** Move constructor */ | ||
| MidiMessageSequence (MidiMessageSequence&&) noexcept; | ||
|
|
||
| /** Move assignment operator */ | ||
| MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; | ||
|
|
||
| //============================================================================== | ||
| /** Structure used to hold midi events in the sequence. | ||
| These structures act as 'handles' on the events as they are moved about in | ||
| the list, and make it quick to find the matching note-offs for note-on events. | ||
| @see MidiMessageSequence::getEventPointer | ||
| */ | ||
| class MidiEventHolder | ||
| { | ||
| public: | ||
| //============================================================================== | ||
| /** The message itself, whose timestamp is used to specify the event's time. */ | ||
| MidiMessage message; | ||
|
|
||
| /** The matching note-off event (if this is a note-on event). | ||
| If this isn't a note-on, this pointer will be nullptr. | ||
| Use the MidiMessageSequence::updateMatchedPairs() method to keep these | ||
| note-offs up-to-date after events have been moved around in the sequence | ||
| or deleted. | ||
| */ | ||
| MidiEventHolder* noteOffObject = nullptr; | ||
|
|
||
| private: | ||
| //============================================================================== | ||
| friend class MidiMessageSequence; | ||
| MidiEventHolder (const MidiMessage&); | ||
| MidiEventHolder (MidiMessage&&); | ||
| JUCE_LEAK_DETECTOR (MidiEventHolder) | ||
| }; | ||
|
|
||
| //============================================================================== | ||
| /** Clears the sequence. */ | ||
| void clear(); | ||
|
|
||
| /** Returns the number of events in the sequence. */ | ||
| int getNumEvents() const noexcept; | ||
|
|
||
| /** Returns a pointer to one of the events. */ | ||
| MidiEventHolder* getEventPointer (int index) const noexcept; | ||
|
|
||
| /** Iterator for the list of MidiEventHolders */ | ||
| MidiEventHolder** begin() noexcept; | ||
|
|
||
| /** Iterator for the list of MidiEventHolders */ | ||
| MidiEventHolder* const* begin() const noexcept; | ||
|
|
||
| /** Iterator for the list of MidiEventHolders */ | ||
| MidiEventHolder** end() noexcept; | ||
|
|
||
| /** Iterator for the list of MidiEventHolders */ | ||
| MidiEventHolder* const* end() const noexcept; | ||
|
|
||
| /** Returns the time of the note-up that matches the note-on at this index. | ||
| If the event at this index isn't a note-on, it'll just return 0. | ||
| @see MidiMessageSequence::MidiEventHolder::noteOffObject | ||
| */ | ||
| double getTimeOfMatchingKeyUp (int index) const noexcept; | ||
|
|
||
| /** Returns the index of the note-up that matches the note-on at this index. | ||
| If the event at this index isn't a note-on, it'll just return -1. | ||
| @see MidiMessageSequence::MidiEventHolder::noteOffObject | ||
| */ | ||
| int getIndexOfMatchingKeyUp (int index) const noexcept; | ||
|
|
||
| /** Returns the index of an event. */ | ||
| int getIndexOf (const MidiEventHolder* event) const noexcept; | ||
|
|
||
| /** Returns the index of the first event on or after the given timestamp. | ||
| If the time is beyond the end of the sequence, this will return the | ||
| number of events. | ||
| */ | ||
| int getNextIndexAtTime (double timeStamp) const noexcept; | ||
|
|
||
| //============================================================================== | ||
| /** Returns the timestamp of the first event in the sequence. | ||
| @see getEndTime | ||
| */ | ||
| double getStartTime() const noexcept; | ||
|
|
||
| /** Returns the timestamp of the last event in the sequence. | ||
| @see getStartTime | ||
| */ | ||
| double getEndTime() const noexcept; | ||
|
|
||
| /** Returns the timestamp of the event at a given index. | ||
| If the index is out-of-range, this will return 0.0 | ||
| */ | ||
| double getEventTime (int index) const noexcept; | ||
|
|
||
| //============================================================================== | ||
| /** Inserts a midi message into the sequence. | ||
| The index at which the new message gets inserted will depend on its timestamp, | ||
| because the sequence is kept sorted. | ||
| Remember to call updateMatchedPairs() after adding note-on events. | ||
| @param newMessage the new message to add (an internal copy will be made) | ||
| @param timeAdjustment an optional value to add to the timestamp of the message | ||
| that will be inserted | ||
| @see updateMatchedPairs | ||
| */ | ||
| MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0); | ||
|
|
||
| /** Inserts a midi message into the sequence. | ||
| The index at which the new message gets inserted will depend on its timestamp, | ||
| because the sequence is kept sorted. | ||
| Remember to call updateMatchedPairs() after adding note-on events. | ||
| @param newMessage the new message to add (an internal copy will be made) | ||
| @param timeAdjustment an optional value to add to the timestamp of the message | ||
| that will be inserted | ||
| @see updateMatchedPairs | ||
| */ | ||
| MidiEventHolder* addEvent (MidiMessage&& newMessage, double timeAdjustment = 0); | ||
|
|
||
| /** Deletes one of the events in the sequence. | ||
| Remember to call updateMatchedPairs() after removing events. | ||
| @param index the index of the event to delete | ||
| @param deleteMatchingNoteUp whether to also remove the matching note-off | ||
| if the event you're removing is a note-on | ||
| */ | ||
| void deleteEvent (int index, bool deleteMatchingNoteUp); | ||
|
|
||
| /** Merges another sequence into this one. | ||
| Remember to call updateMatchedPairs() after using this method. | ||
| @param other the sequence to add from | ||
| @param timeAdjustmentDelta an amount to add to the timestamps of the midi events | ||
| as they are read from the other sequence | ||
| @param firstAllowableDestTime events will not be added if their time is earlier | ||
| than this time. (This is after their time has been adjusted | ||
| by the timeAdjustmentDelta) | ||
| @param endOfAllowableDestTimes events will not be added if their time is equal to | ||
| or greater than this time. (This is after their time has | ||
| been adjusted by the timeAdjustmentDelta) | ||
| */ | ||
| void addSequence (const MidiMessageSequence& other, | ||
| double timeAdjustmentDelta, | ||
| double firstAllowableDestTime, | ||
| double endOfAllowableDestTimes); | ||
|
|
||
| /** Merges another sequence into this one. | ||
| Remember to call updateMatchedPairs() after using this method. | ||
| @param other the sequence to add from | ||
| @param timeAdjustmentDelta an amount to add to the timestamps of the midi events | ||
| as they are read from the other sequence | ||
| */ | ||
| void addSequence (const MidiMessageSequence& other, | ||
| double timeAdjustmentDelta); | ||
|
|
||
| //============================================================================== | ||
| /** Makes sure all the note-on and note-off pairs are up-to-date. | ||
| Call this after re-ordering messages or deleting/adding messages, and it | ||
| will scan the list and make sure all the note-offs in the MidiEventHolder | ||
| structures are pointing at the correct ones. | ||
| */ | ||
| void updateMatchedPairs() noexcept; | ||
|
|
||
| /** Forces a sort of the sequence. | ||
| You may need to call this if you've manually modified the timestamps of some | ||
| events such that the overall order now needs updating. | ||
| */ | ||
| void sort() noexcept; | ||
|
|
||
| //============================================================================== | ||
| /** Copies all the messages for a particular midi channel to another sequence. | ||
| @param channelNumberToExtract the midi channel to look for, in the range 1 to 16 | ||
| @param destSequence the sequence that the chosen events should be copied to | ||
| @param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific | ||
| channel) will also be copied across. | ||
| @see extractSysExMessages | ||
| */ | ||
| void extractMidiChannelMessages (int channelNumberToExtract, | ||
| MidiMessageSequence& destSequence, | ||
| bool alsoIncludeMetaEvents) const; | ||
|
|
||
| /** Copies all midi sys-ex messages to another sequence. | ||
| @param destSequence this is the sequence to which any sys-exes in this sequence | ||
| will be added | ||
| @see extractMidiChannelMessages | ||
| */ | ||
| void extractSysExMessages (MidiMessageSequence& destSequence) const; | ||
|
|
||
| /** Removes any messages in this sequence that have a specific midi channel. | ||
| @param channelNumberToRemove the midi channel to look for, in the range 1 to 16 | ||
| */ | ||
| void deleteMidiChannelMessages (int channelNumberToRemove); | ||
|
|
||
| /** Removes any sys-ex messages from this sequence. */ | ||
| void deleteSysExMessages(); | ||
|
|
||
| /** Adds an offset to the timestamps of all events in the sequence. | ||
| @param deltaTime the amount to add to each timestamp. | ||
| */ | ||
| void addTimeToMessages (double deltaTime) noexcept; | ||
|
|
||
| //============================================================================== | ||
| /** Scans through the sequence to determine the state of any midi controllers at | ||
| a given time. | ||
| This will create a sequence of midi controller changes that can be | ||
| used to set all midi controllers to the state they would be in at the | ||
| specified time within this sequence. | ||
| As well as controllers, it will also recreate the midi program number | ||
| and pitch bend position. | ||
| This function has special handling for the "bank select" and "data entry" | ||
| controllers (0x00, 0x20, 0x06, 0x26, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65). | ||
| If the sequence contains multiple bank select and program change messages, | ||
| only the bank select messages immediately preceding the final program change | ||
| message will be kept. | ||
| All "data increment" and "data decrement" messages will be retained. Some hardware will | ||
| ignore the requested increment/decrement values, so retaining all messages is the only | ||
| way to ensure compatibility with all hardware. | ||
| "Parameter number" changes will be slightly condensed. Only the parameter number | ||
| events immediately preceding each data entry event will be kept. The parameter number | ||
| will also be set to its final value at the end of the sequence, if necessary. | ||
| @param channelNumber the midi channel to look for, in the range 1 to 16. Controllers | ||
| for other channels will be ignored. | ||
| @param time the time at which you want to find out the state - there are | ||
| no explicit units for this time measurement, it's the same units | ||
| as used for the timestamps of the messages | ||
| @param resultMessages an array to which midi controller-change messages will be added. This | ||
| will be the minimum number of controller changes to recreate the | ||
| state at the required time. | ||
| */ | ||
| void createControllerUpdatesForTime (int channelNumber, double time, | ||
| Array<MidiMessage>& resultMessages); | ||
|
|
||
| //============================================================================== | ||
| /** Swaps this sequence with another one. */ | ||
| void swapWith (MidiMessageSequence&) noexcept; | ||
|
|
||
| private: | ||
| //============================================================================== | ||
| friend class MidiFile; | ||
| OwnedArray<MidiEventHolder> list; | ||
|
|
||
| MidiEventHolder* addEvent (MidiEventHolder*, double); | ||
|
|
||
| JUCE_LEAK_DETECTOR (MidiMessageSequence) | ||
| }; | ||
|
|
||
| } // namespace juce |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,375 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
|
|
||
| MidiRPNDetector::MidiRPNDetector() noexcept | ||
| { | ||
| } | ||
|
|
||
| MidiRPNDetector::~MidiRPNDetector() noexcept | ||
| { | ||
| } | ||
|
|
||
| bool MidiRPNDetector::parseControllerMessage (int midiChannel, | ||
| int controllerNumber, | ||
| int controllerValue, | ||
| MidiRPNMessage& result) noexcept | ||
| { | ||
| jassert (midiChannel > 0 && midiChannel <= 16); | ||
| jassert (controllerNumber >= 0 && controllerNumber < 128); | ||
| jassert (controllerValue >= 0 && controllerValue < 128); | ||
|
|
||
| return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); | ||
| } | ||
|
|
||
| void MidiRPNDetector::reset() noexcept | ||
| { | ||
| for (int i = 0; i < 16; ++i) | ||
| { | ||
| states[i].parameterMSB = 0xff; | ||
| states[i].parameterLSB = 0xff; | ||
| states[i].resetValue(); | ||
| states[i].isNRPN = false; | ||
| } | ||
| } | ||
|
|
||
| //============================================================================== | ||
| bool MidiRPNDetector::ChannelState::handleController (int channel, | ||
| int controllerNumber, | ||
| int value, | ||
| MidiRPNMessage& result) noexcept | ||
| { | ||
| switch (controllerNumber) | ||
| { | ||
| case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break; | ||
| case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break; | ||
|
|
||
| case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; | ||
| case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; | ||
|
|
||
| case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); | ||
| case 0x26: valueLSB = uint8 (value); break; | ||
|
|
||
| default: break; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| void MidiRPNDetector::ChannelState::resetValue() noexcept | ||
| { | ||
| valueMSB = 0xff; | ||
| valueLSB = 0xff; | ||
| } | ||
|
|
||
| //============================================================================== | ||
| bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept | ||
| { | ||
| if (parameterMSB < 0x80 && parameterLSB < 0x80) | ||
| { | ||
| if (valueMSB < 0x80) | ||
| { | ||
| result.channel = channel; | ||
| result.parameterNumber = (parameterMSB << 7) + parameterLSB; | ||
| result.isNRPN = isNRPN; | ||
|
|
||
| if (valueLSB < 0x80) | ||
| { | ||
| result.value = (valueMSB << 7) + valueLSB; | ||
| result.is14BitValue = true; | ||
| } | ||
| else | ||
| { | ||
| result.value = valueMSB; | ||
| result.is14BitValue = false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| //============================================================================== | ||
| MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message) | ||
| { | ||
| return generate (message.channel, | ||
| message.parameterNumber, | ||
| message.value, | ||
| message.isNRPN, | ||
| message.is14BitValue); | ||
| } | ||
|
|
||
| MidiBuffer MidiRPNGenerator::generate (int midiChannel, | ||
| int parameterNumber, | ||
| int value, | ||
| bool isNRPN, | ||
| bool use14BitValue) | ||
| { | ||
| jassert (midiChannel > 0 && midiChannel <= 16); | ||
| jassert (parameterNumber >= 0 && parameterNumber < 16384); | ||
| jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); | ||
|
|
||
| uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); | ||
| uint8 parameterMSB = uint8 (parameterNumber >> 7); | ||
|
|
||
| uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; | ||
| uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); | ||
|
|
||
| uint8 channelByte = uint8 (0xb0 + midiChannel - 1); | ||
|
|
||
| MidiBuffer buffer; | ||
|
|
||
| buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); | ||
| buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); | ||
|
|
||
| // sending the value LSB is optional, but must come before sending the value MSB: | ||
| if (use14BitValue) | ||
| buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); | ||
|
|
||
| buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); | ||
|
|
||
| return buffer; | ||
| } | ||
|
|
||
|
|
||
| //============================================================================== | ||
| //============================================================================== | ||
| #if JUCE_UNIT_TESTS | ||
|
|
||
| class MidiRPNDetectorTests : public UnitTest | ||
| { | ||
| public: | ||
| MidiRPNDetectorTests() | ||
| : UnitTest ("MidiRPNDetector class", UnitTestCategories::midi) | ||
| {} | ||
|
|
||
| void runTest() override | ||
| { | ||
| beginTest ("7-bit RPN"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | ||
| expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | ||
| expect (detector.parseControllerMessage (2, 6, 42, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 2); | ||
| expectEquals (rpn.parameterNumber, 7); | ||
| expectEquals (rpn.value, 42); | ||
| expect (! rpn.isNRPN); | ||
| expect (! rpn.is14BitValue); | ||
| } | ||
|
|
||
| beginTest ("14-bit RPN"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | ||
| expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | ||
| expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | ||
| expect (detector.parseControllerMessage (1, 6, 1, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 1); | ||
| expectEquals (rpn.parameterNumber, 300); | ||
| expectEquals (rpn.value, 222); | ||
| expect (! rpn.isNRPN); | ||
| expect (rpn.is14BitValue); | ||
| } | ||
|
|
||
| beginTest ("RPNs on multiple channels simultaneously"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | ||
| expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | ||
| expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | ||
| expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | ||
| expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | ||
| expect (detector.parseControllerMessage (2, 6, 42, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 2); | ||
| expectEquals (rpn.parameterNumber, 7); | ||
| expectEquals (rpn.value, 42); | ||
| expect (! rpn.isNRPN); | ||
| expect (! rpn.is14BitValue); | ||
|
|
||
| expect (detector.parseControllerMessage (1, 6, 1, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 1); | ||
| expectEquals (rpn.parameterNumber, 300); | ||
| expectEquals (rpn.value, 222); | ||
| expect (! rpn.isNRPN); | ||
| expect (rpn.is14BitValue); | ||
| } | ||
|
|
||
| beginTest ("14-bit RPN with value within 7-bit range"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | ||
| expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | ||
| expect (detector.parseControllerMessage (16, 6, 0, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 16); | ||
| expectEquals (rpn.parameterNumber, 0); | ||
| expectEquals (rpn.value, 3); | ||
| expect (! rpn.isNRPN); | ||
| expect (rpn.is14BitValue); | ||
| } | ||
|
|
||
| beginTest ("invalid RPN (wrong order)"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | ||
| expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | ||
| expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | ||
| } | ||
|
|
||
| beginTest ("14-bit RPN interspersed with unrelated CC messages"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (16, 3, 80, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | ||
| expect (! detector.parseControllerMessage (16, 4, 81, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 5, 82, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 5, 83, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 4, 84, rpn)); | ||
| expect (! detector.parseControllerMessage (16, 3, 85, rpn)); | ||
| expect (detector.parseControllerMessage (16, 6, 0, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 16); | ||
| expectEquals (rpn.parameterNumber, 0); | ||
| expectEquals (rpn.value, 3); | ||
| expect (! rpn.isNRPN); | ||
| expect (rpn.is14BitValue); | ||
| } | ||
|
|
||
| beginTest ("14-bit NRPN"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (1, 98, 44, rpn)); | ||
| expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); | ||
| expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | ||
| expect (detector.parseControllerMessage (1, 6, 1, rpn)); | ||
|
|
||
| expectEquals (rpn.channel, 1); | ||
| expectEquals (rpn.parameterNumber, 300); | ||
| expectEquals (rpn.value, 222); | ||
| expect (rpn.isNRPN); | ||
| expect (rpn.is14BitValue); | ||
| } | ||
|
|
||
| beginTest ("reset"); | ||
| { | ||
| MidiRPNDetector detector; | ||
| MidiRPNMessage rpn; | ||
| expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | ||
| detector.reset(); | ||
| expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | ||
| expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| static MidiRPNDetectorTests MidiRPNDetectorUnitTests; | ||
|
|
||
| //============================================================================== | ||
| class MidiRPNGeneratorTests : public UnitTest | ||
| { | ||
| public: | ||
| MidiRPNGeneratorTests() | ||
| : UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi) | ||
| {} | ||
|
|
||
| void runTest() override | ||
| { | ||
| beginTest ("generating RPN/NRPN"); | ||
| { | ||
| { | ||
| MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); | ||
| expectContainsRPN (buffer, 1, 23, 1337, true, true); | ||
| } | ||
| { | ||
| MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); | ||
| expectContainsRPN (buffer, 16, 101, 34, false, false); | ||
| } | ||
| { | ||
| MidiRPNMessage message = { 16, 101, 34, false, false }; | ||
| MidiBuffer buffer = MidiRPNGenerator::generate (message); | ||
| expectContainsRPN (buffer, message); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| //============================================================================== | ||
| void expectContainsRPN (const MidiBuffer& midiBuffer, | ||
| int channel, | ||
| int parameterNumber, | ||
| int value, | ||
| bool isNRPN, | ||
| bool is14BitValue) | ||
| { | ||
| MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; | ||
| expectContainsRPN (midiBuffer, expected); | ||
| } | ||
|
|
||
| //============================================================================== | ||
| void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) | ||
| { | ||
| MidiRPNMessage result = MidiRPNMessage(); | ||
| MidiRPNDetector detector; | ||
|
|
||
| for (const auto metadata : midiBuffer) | ||
| { | ||
| const auto midiMessage = metadata.getMessage(); | ||
|
|
||
| if (detector.parseControllerMessage (midiMessage.getChannel(), | ||
| midiMessage.getControllerNumber(), | ||
| midiMessage.getControllerValue(), | ||
| result)) | ||
| break; | ||
| } | ||
|
|
||
| expectEquals (result.channel, expected.channel); | ||
| expectEquals (result.parameterNumber, expected.parameterNumber); | ||
| expectEquals (result.value, expected.value); | ||
| expect (result.isNRPN == expected.isNRPN); | ||
| expect (result.is14BitValue == expected.is14BitValue); | ||
| } | ||
| }; | ||
|
|
||
| static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; | ||
|
|
||
| #endif | ||
|
|
||
| } // namespace juce |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
|
|
||
| //============================================================================== | ||
| /** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered | ||
| parameter number) message. | ||
| @tags{Audio} | ||
| */ | ||
| struct MidiRPNMessage | ||
| { | ||
| /** Midi channel of the message, in the range 1 to 16. */ | ||
| int channel; | ||
|
|
||
| /** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */ | ||
| int parameterNumber; | ||
|
|
||
| /** The parameter value, in the range 0 to 16383 (0x3fff). | ||
| If the message contains no value LSB, the value will be in the range | ||
| 0 to 127 (0x7f). | ||
| */ | ||
| int value; | ||
|
|
||
| /** True if this message is an NRPN; false if it is an RPN. */ | ||
| bool isNRPN; | ||
|
|
||
| /** True if the value uses 14-bit resolution (LSB + MSB); false if | ||
| the value is 7-bit (MSB only). | ||
| */ | ||
| bool is14BitValue; | ||
| }; | ||
|
|
||
| //============================================================================== | ||
| /** | ||
| Parses a stream of MIDI data to assemble RPN and NRPN messages from their | ||
| constituent MIDI CC messages. | ||
| The detector uses the following parsing rules: the parameter number | ||
| LSB/MSB can be sent/received in either order and must both come before the | ||
| parameter value; for the parameter value, LSB always has to be sent/received | ||
| before the value MSB, otherwise it will be treated as 7-bit (MSB only). | ||
| @tags{Audio} | ||
| */ | ||
| class JUCE_API MidiRPNDetector | ||
| { | ||
| public: | ||
| /** Constructor. */ | ||
| MidiRPNDetector() noexcept; | ||
|
|
||
| /** Destructor. */ | ||
| ~MidiRPNDetector() noexcept; | ||
|
|
||
| /** Resets the RPN detector's internal state, so that it forgets about | ||
| previously received MIDI CC messages. | ||
| */ | ||
| void reset() noexcept; | ||
|
|
||
| //============================================================================== | ||
| /** Takes the next in a stream of incoming MIDI CC messages and returns true | ||
| if it forms the last of a sequence that makes an RPN or NPRN. | ||
| If this returns true, then the RPNMessage object supplied will be | ||
| filled-out with the message's details. | ||
| (If it returns false then the RPNMessage object will be unchanged). | ||
| */ | ||
| bool parseControllerMessage (int midiChannel, | ||
| int controllerNumber, | ||
| int controllerValue, | ||
| MidiRPNMessage& result) noexcept; | ||
|
|
||
| private: | ||
| //============================================================================== | ||
| struct ChannelState | ||
| { | ||
| bool handleController (int channel, int controllerNumber, | ||
| int value, MidiRPNMessage&) noexcept; | ||
| void resetValue() noexcept; | ||
| bool sendIfReady (int channel, MidiRPNMessage&) noexcept; | ||
|
|
||
| uint8 parameterMSB = 0xff, parameterLSB = 0xff, valueMSB = 0xff, valueLSB = 0xff; | ||
| bool isNRPN = false; | ||
| }; | ||
|
|
||
| //============================================================================== | ||
| ChannelState states[16]; | ||
|
|
||
| JUCE_LEAK_DETECTOR (MidiRPNDetector) | ||
| }; | ||
|
|
||
| //============================================================================== | ||
| /** | ||
| Generates an appropriate sequence of MIDI CC messages to represent an RPN | ||
| or NRPN message. | ||
| This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput. | ||
| @tags{Audio} | ||
| */ | ||
| class JUCE_API MidiRPNGenerator | ||
| { | ||
| public: | ||
| //============================================================================== | ||
| /** Generates a MIDI sequence representing the given RPN or NRPN message. */ | ||
| static MidiBuffer generate (MidiRPNMessage message); | ||
|
|
||
| //============================================================================== | ||
| /** Generates a MIDI sequence representing an RPN or NRPN message with the | ||
| given parameters. | ||
| @param channel The MIDI channel of the RPN/NRPN message. | ||
| @param parameterNumber The parameter number, in the range 0 to 16383. | ||
| @param value The parameter value, in the range 0 to 16383, or | ||
| in the range 0 to 127 if sendAs14BitValue is false. | ||
| @param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). | ||
| @param use14BitValue If true (default), the value will have 14-bit precision | ||
| (two MIDI bytes). If false, instead the value will have | ||
| 7-bit precision (a single MIDI byte). | ||
| */ | ||
| static MidiBuffer generate (int channel, | ||
| int parameterNumber, | ||
| int value, | ||
| bool isNRPN = false, | ||
| bool use14BitValue = true); | ||
| }; | ||
|
|
||
| } // namespace juce |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #include "../juce_MidiDataConcatenator.h" | ||
|
|
||
| #include "juce_UMPProtocols.h" | ||
| #include "juce_UMPUtils.h" | ||
| #include "juce_UMPacket.h" | ||
| #include "juce_UMPSysEx7.h" | ||
| #include "juce_UMPView.h" | ||
| #include "juce_UMPIterator.h" | ||
| #include "juce_UMPackets.h" | ||
| #include "juce_UMPFactory.h" | ||
| #include "juce_UMPConversion.h" | ||
| #include "juce_UMPMidi1ToBytestreamTranslator.h" | ||
| #include "juce_UMPMidi1ToMidi2DefaultTranslator.h" | ||
| #include "juce_UMPConverters.h" | ||
| #include "juce_UMPDispatcher.h" | ||
| #include "juce_UMPReceiver.h" | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace ump = universal_midi_packets; | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,330 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Functions to assist conversion of UMP messages to/from other formats, | ||
| especially older 'bytestream' formatted MidiMessages. | ||
| @tags{Audio} | ||
| */ | ||
| struct Conversion | ||
| { | ||
| /** Converts from a MIDI 1 bytestream to MIDI 1 on Universal MIDI Packets. | ||
| `callback` is a function which accepts a single View argument. | ||
| */ | ||
| template <typename PacketCallbackFunction> | ||
| static void toMidi1 (const MidiMessage& m, PacketCallbackFunction&& callback) | ||
| { | ||
| const auto* data = m.getRawData(); | ||
| const auto firstByte = data[0]; | ||
| const auto size = m.getRawDataSize(); | ||
|
|
||
| if (firstByte != 0xf0) | ||
| { | ||
| const auto mask = [size]() -> uint32_t | ||
| { | ||
| switch (size) | ||
| { | ||
| case 0: return 0xff000000; | ||
| case 1: return 0xffff0000; | ||
| case 2: return 0xffffff00; | ||
| case 3: return 0xffffffff; | ||
| } | ||
|
|
||
| return 0x00000000; | ||
| }(); | ||
|
|
||
| const auto extraByte = (uint8_t) ((((firstByte & 0xf0) == 0xf0) ? 0x1 : 0x2) << 0x4); | ||
| const PacketX1 packet { mask & Utils::bytesToWord (extraByte, data[0], data[1], data[2]) }; | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| const auto numSysExBytes = m.getSysExDataSize(); | ||
| const auto numMessages = SysEx7::getNumPacketsRequiredForDataSize ((uint32_t) numSysExBytes); | ||
| auto* dataOffset = m.getSysExData(); | ||
|
|
||
| if (numMessages <= 1) | ||
| { | ||
| const auto packet = Factory::makeSysExIn1Packet (0, (uint8_t) numSysExBytes, dataOffset); | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| constexpr auto byteIncrement = 6; | ||
|
|
||
| for (auto i = numSysExBytes; i > 0; i -= byteIncrement, dataOffset += byteIncrement) | ||
| { | ||
| const auto func = [&] | ||
| { | ||
| if (i == numSysExBytes) | ||
| return Factory::makeSysExStart; | ||
|
|
||
| if (i <= byteIncrement) | ||
| return Factory::makeSysExEnd; | ||
|
|
||
| return Factory::makeSysExContinue; | ||
| }(); | ||
|
|
||
| const auto bytesNow = std::min (byteIncrement, i); | ||
| const auto packet = func (0, (uint8_t) bytesNow, dataOffset); | ||
| callback (View (packet.data())); | ||
| } | ||
| } | ||
|
|
||
| /** Converts a MidiMessage to one or more messages in UMP format, using | ||
| the MIDI 1.0 Protocol. | ||
| `packets` is an out-param to allow the caller to control | ||
| allocation/deallocation. Returning a new Packets object would | ||
| require every call to toMidi1 to allocate. With this version, no | ||
| allocations will occur, provided that `packets` has adequate reserved | ||
| space. | ||
| */ | ||
| static void toMidi1 (const MidiMessage& m, Packets& packets) | ||
| { | ||
| toMidi1 (m, [&] (const View& view) { packets.add (view); }); | ||
| } | ||
|
|
||
| /** Widens a 7-bit MIDI 1.0 value to a 8-bit MIDI 2.0 value. */ | ||
| static uint8_t scaleTo8 (uint8_t word7Bit) | ||
| { | ||
| const auto shifted = (uint8_t) (word7Bit << 0x1); | ||
| const auto repeat = (uint8_t) (word7Bit & 0x3f); | ||
| const auto mask = (uint8_t) (word7Bit <= 0x40 ? 0x0 : 0xff); | ||
| return (uint8_t) (shifted | ((repeat >> 5) & mask)); | ||
| } | ||
|
|
||
| /** Widens a 7-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ | ||
| static uint16_t scaleTo16 (uint8_t word7Bit) | ||
| { | ||
| const auto shifted = (uint16_t) (word7Bit << 0x9); | ||
| const auto repeat = (uint16_t) (word7Bit & 0x3f); | ||
| const auto mask = (uint16_t) (word7Bit <= 0x40 ? 0x0 : 0xffff); | ||
| return (uint16_t) (shifted | (((repeat << 3) | (repeat >> 3)) & mask)); | ||
| } | ||
|
|
||
| /** Widens a 14-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ | ||
| static uint16_t scaleTo16 (uint16_t word14Bit) | ||
| { | ||
| const auto shifted = (uint16_t) (word14Bit << 0x2); | ||
| const auto repeat = (uint16_t) (word14Bit & 0x1fff); | ||
| const auto mask = (uint16_t) (word14Bit <= 0x2000 ? 0x0 : 0xffff); | ||
| return (uint16_t) (shifted | ((repeat >> 11) & mask)); | ||
| } | ||
|
|
||
| /** Widens a 7-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ | ||
| static uint32_t scaleTo32 (uint8_t word7Bit) | ||
| { | ||
| const auto shifted = (uint32_t) (word7Bit << 0x19); | ||
| const auto repeat = (uint32_t) (word7Bit & 0x3f); | ||
| const auto mask = (uint32_t) (word7Bit <= 0x40 ? 0x0 : 0xffffffff); | ||
| return (uint32_t) (shifted | (((repeat << 19) | ||
| | (repeat << 13) | ||
| | (repeat << 7) | ||
| | (repeat << 1) | ||
| | (repeat >> 5)) & mask)); | ||
| } | ||
|
|
||
| /** Widens a 14-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ | ||
| static uint32_t scaleTo32 (uint16_t word14Bit) | ||
| { | ||
| const auto shifted = (uint32_t) (word14Bit << 0x12); | ||
| const auto repeat = (uint32_t) (word14Bit & 0x1fff); | ||
| const auto mask = (uint32_t) (word14Bit <= 0x2000 ? 0x0 : 0xffffffff); | ||
| return (uint32_t) (shifted | (((repeat << 5) | (repeat >> 8)) & mask)); | ||
| } | ||
|
|
||
| /** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | ||
| static uint8_t scaleTo7 (uint8_t word8Bit) { return (uint8_t) (word8Bit >> 1); } | ||
|
|
||
| /** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | ||
| static uint8_t scaleTo7 (uint16_t word16Bit) { return (uint8_t) (word16Bit >> 9); } | ||
|
|
||
| /** Narrows a 32-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | ||
| static uint8_t scaleTo7 (uint32_t word32Bit) { return (uint8_t) (word32Bit >> 25); } | ||
|
|
||
| /** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ | ||
| static uint16_t scaleTo14 (uint16_t word16Bit) { return (uint16_t) (word16Bit >> 2); } | ||
|
|
||
| /** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ | ||
| static uint16_t scaleTo14 (uint32_t word32Bit) { return (uint16_t) (word32Bit >> 18); } | ||
|
|
||
| /** Converts UMP messages which may include MIDI 2.0 channel voice messages into | ||
| equivalent MIDI 1.0 messages (still in UMP format). | ||
| `callback` is a function that accepts a single View argument and will be | ||
| called with each converted packet. | ||
| Note that not all MIDI 2.0 messages have MIDI 1.0 equivalents, so such | ||
| messages will be ignored. | ||
| */ | ||
| template <typename Callback> | ||
| static void midi2ToMidi1DefaultTranslation (const View& v, Callback&& callback) | ||
| { | ||
| const auto firstWord = v[0]; | ||
|
|
||
| if (Utils::getMessageType (firstWord) != 0x4) | ||
| { | ||
| callback (v); | ||
| return; | ||
| } | ||
|
|
||
| const auto status = Utils::getStatus (firstWord); | ||
| const auto typeAndGroup = (uint8_t) ((0x2 << 0x4) | Utils::getGroup (firstWord)); | ||
|
|
||
| switch (status) | ||
| { | ||
| case 0x8: // note off | ||
| case 0x9: // note on | ||
| case 0xa: // poly pressure | ||
| case 0xb: // control change | ||
| { | ||
| const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | ||
| const auto byte2 = (uint8_t) ((firstWord >> 0x08) & 0xff); | ||
| const auto byte3 = scaleTo7 (v[1]); | ||
|
|
||
| // If this is a note-on, and the scaled byte is 0, | ||
| // the scaled velocity should be 1 instead of 0 | ||
| const auto needsCorrection = status == 0x9 && byte3 == 0; | ||
| const auto correctedByte = (uint8_t) (needsCorrection ? 1 : byte3); | ||
|
|
||
| const auto shouldIgnore = status == 0xb && [&] | ||
| { | ||
| switch (byte2) | ||
| { | ||
| case 0: | ||
| case 6: | ||
| case 32: | ||
| case 38: | ||
| case 98: | ||
| case 99: | ||
| case 100: | ||
| case 101: | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| }(); | ||
|
|
||
| if (shouldIgnore) | ||
| return; | ||
|
|
||
| const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | ||
| statusAndChannel, | ||
| byte2, | ||
| correctedByte) }; | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0xd: // channel pressure | ||
| { | ||
| const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | ||
| const auto byte2 = scaleTo7 (v[1]); | ||
|
|
||
| const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | ||
| statusAndChannel, | ||
| byte2, | ||
| 0) }; | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0x2: // rpn | ||
| case 0x3: // nrpn | ||
| { | ||
| const auto ccX = (uint8_t) (status == 0x2 ? 101 : 99); | ||
| const auto ccY = (uint8_t) (status == 0x2 ? 100 : 98); | ||
| const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); | ||
| const auto data = scaleTo14 (v[1]); | ||
|
|
||
| const PacketX1 packets[] | ||
| { | ||
| PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccX, (uint8_t) ((firstWord >> 0x8) & 0x7f)) }, | ||
| PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccY, (uint8_t) ((firstWord >> 0x0) & 0x7f)) }, | ||
| PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 6, (uint8_t) ((data >> 0x7) & 0x7f)) }, | ||
| PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 38, (uint8_t) ((data >> 0x0) & 0x7f)) }, | ||
| }; | ||
|
|
||
| for (const auto& packet : packets) | ||
| callback (View (packet.data())); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| case 0xc: // program change / bank select | ||
| { | ||
| if (firstWord & 1) | ||
| { | ||
| const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); | ||
| const auto secondWord = v[1]; | ||
|
|
||
| const PacketX1 packets[] | ||
| { | ||
| PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 0, (uint8_t) ((secondWord >> 0x8) & 0x7f)) }, | ||
| PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 32, (uint8_t) ((secondWord >> 0x0) & 0x7f)) }, | ||
| }; | ||
|
|
||
| for (const auto& packet : packets) | ||
| callback (View (packet.data())); | ||
| } | ||
|
|
||
| const auto statusAndChannel = (uint8_t) ((0xc << 0x4) | Utils::getChannel (firstWord)); | ||
| const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | ||
| statusAndChannel, | ||
| (uint8_t) ((v[1] >> 0x18) & 0x7f), | ||
| 0) }; | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0xe: // pitch bend | ||
| { | ||
| const auto data = scaleTo14 (v[1]); | ||
| const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | ||
| const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | ||
| statusAndChannel, | ||
| (uint8_t) (data & 0x7f), | ||
| (uint8_t) ((data >> 7) & 0x7f)) }; | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| default: // other message types do not translate | ||
| return; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
| /** | ||
| Allows conversion from bytestream- or Universal MIDI Packet-formatted | ||
| messages to MIDI 1.0 messages in UMP format. | ||
| @tags{Audio} | ||
| */ | ||
| struct ToUMP1Converter | ||
| { | ||
| template <typename Fn> | ||
| void convert (const MidiMessage& m, Fn&& fn) | ||
| { | ||
| Conversion::toMidi1 (m, std::forward<Fn> (fn)); | ||
| } | ||
|
|
||
| template <typename Fn> | ||
| void convert (const View& v, Fn&& fn) | ||
| { | ||
| Conversion::midi2ToMidi1DefaultTranslation (v, std::forward<Fn> (fn)); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| Allows conversion from bytestream- or Universal MIDI Packet-formatted | ||
| messages to MIDI 2.0 messages in UMP format. | ||
| @tags{Audio} | ||
| */ | ||
| struct ToUMP2Converter | ||
| { | ||
| template <typename Fn> | ||
| void convert (const MidiMessage& m, Fn&& fn) | ||
| { | ||
| Conversion::toMidi1 (m, [&] (const View& v) | ||
| { | ||
| translator.dispatch (v, fn); | ||
| }); | ||
| } | ||
|
|
||
| template <typename Fn> | ||
| void convert (const View& v, Fn&& fn) | ||
| { | ||
| translator.dispatch (v, std::forward<Fn> (fn)); | ||
| } | ||
|
|
||
| void reset() | ||
| { | ||
| translator.reset(); | ||
| } | ||
|
|
||
| Midi1ToMidi2DefaultTranslator translator; | ||
| }; | ||
|
|
||
| /** | ||
| Allows conversion from bytestream- or Universal MIDI Packet-formatted | ||
| messages to UMP format. | ||
| The packet protocol can be selected using the constructor parameter. | ||
| @tags{Audio} | ||
| */ | ||
| class GenericUMPConverter | ||
| { | ||
| public: | ||
| explicit GenericUMPConverter (PacketProtocol m) | ||
| : mode (m) {} | ||
|
|
||
| void reset() | ||
| { | ||
| std::get<1> (converters).reset(); | ||
| } | ||
|
|
||
| template <typename Fn> | ||
| void convert (const MidiMessage& m, Fn&& fn) | ||
| { | ||
| switch (mode) | ||
| { | ||
| case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (m, std::forward<Fn> (fn)); | ||
| case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (m, std::forward<Fn> (fn)); | ||
| } | ||
| } | ||
|
|
||
| template <typename Fn> | ||
| void convert (const View& v, Fn&& fn) | ||
| { | ||
| switch (mode) | ||
| { | ||
| case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (v, std::forward<Fn> (fn)); | ||
| case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (v, std::forward<Fn> (fn)); | ||
| } | ||
| } | ||
|
|
||
| template <typename Fn> | ||
| void convert (Iterator begin, Iterator end, Fn&& fn) | ||
| { | ||
| std::for_each (begin, end, [&] (const View& v) | ||
| { | ||
| convert (v, fn); | ||
| }); | ||
| } | ||
|
|
||
| PacketProtocol getProtocol() const noexcept { return mode; } | ||
|
|
||
| private: | ||
| std::tuple<ToUMP1Converter, ToUMP2Converter> converters; | ||
| const PacketProtocol mode{}; | ||
| }; | ||
|
|
||
| /** | ||
| Allows conversion from bytestream- or Universal MIDI Packet-formatted | ||
| messages to bytestream format. | ||
| @tags{Audio} | ||
| */ | ||
| struct ToBytestreamConverter | ||
| { | ||
| explicit ToBytestreamConverter (int storageSize) | ||
| : translator (storageSize) {} | ||
|
|
||
| template <typename Fn> | ||
| void convert (const MidiMessage& m, Fn&& fn) | ||
| { | ||
| fn (m); | ||
| } | ||
|
|
||
| template <typename Fn> | ||
| void convert (const View& v, double time, Fn&& fn) | ||
| { | ||
| Conversion::midi2ToMidi1DefaultTranslation (v, [&] (const View& midi1) | ||
| { | ||
| translator.dispatch (midi1, time, fn); | ||
| }); | ||
| } | ||
|
|
||
| void reset() { translator.reset(); } | ||
|
|
||
| Midi1ToBytestreamTranslator translator; | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Parses a raw stream of uint32_t, and calls a user-provided callback every time | ||
| a full Universal MIDI Packet is encountered. | ||
| @tags{Audio} | ||
| */ | ||
| class Dispatcher | ||
| { | ||
| public: | ||
| /** Clears the dispatcher. */ | ||
| void reset() { currentPacketLen = 0; } | ||
|
|
||
| /** Calls `callback` with a View of each packet encountered in the range delimited | ||
| by `begin` and `end`. | ||
| If the range ends part-way through a packet, the next call to `dispatch` will | ||
| continue from that point in the packet (unless `reset` is called first). | ||
| */ | ||
| template <typename PacketCallbackFunction> | ||
| void dispatch (const uint32_t* begin, | ||
| const uint32_t* end, | ||
| double timeStamp, | ||
| PacketCallbackFunction&& callback) | ||
| { | ||
| std::for_each (begin, end, [&] (uint32_t word) | ||
| { | ||
| nextPacket[currentPacketLen++] = word; | ||
|
|
||
| if (currentPacketLen == Utils::getNumWordsForMessageType (nextPacket.front())) | ||
| { | ||
| callback (View (nextPacket.data()), timeStamp); | ||
| currentPacketLen = 0; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private: | ||
| std::array<uint32_t, 4> nextPacket; | ||
| size_t currentPacketLen = 0; | ||
| }; | ||
|
|
||
| //============================================================================== | ||
| /** | ||
| Parses a stream of bytes representing a sequence of bytestream-encoded MIDI 1.0 messages, | ||
| converting the messages to UMP format and passing the packets to a user-provided callback | ||
| as they become ready. | ||
| @tags{Audio} | ||
| */ | ||
| class BytestreamToUMPDispatcher | ||
| { | ||
| public: | ||
| /** Initialises the dispatcher. | ||
| Channel messages will be converted to the requested protocol format `pp`. | ||
| `storageSize` bytes will be allocated to store incomplete messages. | ||
| */ | ||
| explicit BytestreamToUMPDispatcher (PacketProtocol pp, int storageSize) | ||
| : concatenator (storageSize), | ||
| converter (pp) | ||
| {} | ||
|
|
||
| void reset() | ||
| { | ||
| concatenator.reset(); | ||
| converter.reset(); | ||
| } | ||
|
|
||
| /** Calls `callback` with a View of each converted packet as it becomes ready. | ||
| @param begin the first byte in a range of bytes representing bytestream-encoded MIDI messages. | ||
| @param end one-past the last byte in a range of bytes representing bytestream-encoded MIDI messages. | ||
| @param timestamp a timestamp to apply to the created packets. | ||
| @param callback a callback which will be passed a View pointing to each new packet as it becomes ready. | ||
| */ | ||
| template <typename PacketCallbackFunction> | ||
| void dispatch (const uint8_t* begin, | ||
| const uint8_t* end, | ||
| double timestamp, | ||
| PacketCallbackFunction&& callback) | ||
| { | ||
| using CallbackPtr = decltype (std::addressof (callback)); | ||
|
|
||
| #if JUCE_MINGW | ||
| #define JUCE_MINGW_HIDDEN_VISIBILITY __attribute__ ((visibility ("hidden"))) | ||
| #else | ||
| #define JUCE_MINGW_HIDDEN_VISIBILITY | ||
| #endif | ||
|
|
||
| struct JUCE_MINGW_HIDDEN_VISIBILITY Callback | ||
| { | ||
| Callback (BytestreamToUMPDispatcher& d, CallbackPtr c) | ||
| : dispatch (d), callbackPtr (c) {} | ||
|
|
||
| void handleIncomingMidiMessage (void*, const MidiMessage& msg) const | ||
| { | ||
| Conversion::toMidi1 (msg, [&] (const View& view) | ||
| { | ||
| dispatch.converter.convert (view, *callbackPtr); | ||
| }); | ||
| } | ||
|
|
||
| void handlePartialSysexMessage (void*, const uint8_t*, int, double) const {} | ||
|
|
||
| BytestreamToUMPDispatcher& dispatch; | ||
| CallbackPtr callbackPtr = nullptr; | ||
| }; | ||
|
|
||
| #undef JUCE_MINGW_HIDDEN_VISIBILITY | ||
|
|
||
| Callback inputCallback { *this, &callback }; | ||
| concatenator.pushMidiData (begin, int (end - begin), timestamp, (void*) nullptr, inputCallback); | ||
| } | ||
|
|
||
| private: | ||
| MidiDataConcatenator concatenator; | ||
| GenericUMPConverter converter; | ||
| }; | ||
|
|
||
| //============================================================================== | ||
| /** | ||
| Parses a stream of 32-bit words representing a sequence of UMP-encoded MIDI messages, | ||
| converting the messages to MIDI 1.0 bytestream format and passing them to a user-provided | ||
| callback as they become ready. | ||
| @tags{Audio} | ||
| */ | ||
| class ToBytestreamDispatcher | ||
| { | ||
| public: | ||
| /** Initialises the dispatcher. | ||
| `storageSize` bytes will be allocated to store incomplete messages. | ||
| */ | ||
| explicit ToBytestreamDispatcher (int storageSize) | ||
| : converter (storageSize) {} | ||
|
|
||
| /** Clears the dispatcher. */ | ||
| void reset() | ||
| { | ||
| dispatcher.reset(); | ||
| converter.reset(); | ||
| } | ||
|
|
||
| /** Calls `callback` with converted bytestream-formatted MidiMessage whenever | ||
| a new message becomes available. | ||
| @param begin the first word in a stream of words representing UMP-encoded MIDI packets. | ||
| @param end one-past the last word in a stream of words representing UMP-encoded MIDI packets. | ||
| @param timestamp a timestamp to apply to converted messages. | ||
| @param callback a callback which will be passed a MidiMessage each time a new message becomes ready. | ||
| */ | ||
| template <typename BytestreamMessageCallback> | ||
| void dispatch (const uint32_t* begin, | ||
| const uint32_t* end, | ||
| double timestamp, | ||
| BytestreamMessageCallback&& callback) | ||
| { | ||
| dispatcher.dispatch (begin, end, timestamp, [&] (const View& view, double time) | ||
| { | ||
| converter.convert (view, time, callback); | ||
| }); | ||
| } | ||
|
|
||
| private: | ||
| Dispatcher dispatcher; | ||
| ToBytestreamConverter converter; | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Enables iteration over a collection of Universal MIDI Packets stored as | ||
| a contiguous range of 32-bit words. | ||
| This iterator is used by Packets to allow access to the messages | ||
| that it contains. | ||
| @tags{Audio} | ||
| */ | ||
| class Iterator | ||
| { | ||
| public: | ||
| /** Creates an invalid (singular) iterator. */ | ||
| Iterator() noexcept = default; | ||
|
|
||
| /** Creates an iterator pointing at `ptr`. */ | ||
| explicit Iterator (const uint32_t* ptr, size_t bytes) noexcept | ||
| : view (ptr) | ||
| #if JUCE_DEBUG | ||
| , bytesRemaining (bytes) | ||
| #endif | ||
| { | ||
| ignoreUnused (bytes); | ||
| } | ||
|
|
||
| using difference_type = std::iterator_traits<const uint32_t*>::difference_type; | ||
| using value_type = View; | ||
| using reference = const View&; | ||
| using pointer = const View*; | ||
| using iterator_category = std::forward_iterator_tag; | ||
|
|
||
| /** Moves this iterator to the next packet in the range. */ | ||
| Iterator& operator++() noexcept | ||
| { | ||
| const auto increment = view.size(); | ||
|
|
||
| #if JUCE_DEBUG | ||
| // If you hit this, the memory region contained a truncated or otherwise | ||
| // malformed Universal MIDI Packet. | ||
| // The Iterator can only be used on regions containing complete packets! | ||
| jassert (increment <= bytesRemaining); | ||
| bytesRemaining -= increment; | ||
| #endif | ||
|
|
||
| view = View (view.data() + increment); | ||
| return *this; | ||
| } | ||
|
|
||
| /** Moves this iterator to the next packet in the range, | ||
| returning the value of the iterator before it was | ||
| incremented. | ||
| */ | ||
| Iterator operator++ (int) noexcept | ||
| { | ||
| auto copy = *this; | ||
| ++(*this); | ||
| return copy; | ||
| } | ||
|
|
||
| /** Returns true if this iterator points to the same address | ||
| as another iterator. | ||
| */ | ||
| bool operator== (const Iterator& other) const noexcept | ||
| { | ||
| return view == other.view; | ||
| } | ||
|
|
||
| /** Returns false if this iterator points to the same address | ||
| as another iterator. | ||
| */ | ||
| bool operator!= (const Iterator& other) const noexcept | ||
| { | ||
| return ! operator== (other); | ||
| } | ||
|
|
||
| /** Returns a reference to a View of the packet currently | ||
| pointed-to by this iterator. | ||
| The View can be queried for its size and content. | ||
| */ | ||
| reference operator*() noexcept { return view; } | ||
|
|
||
| /** Returns a pointer to a View of the packet currently | ||
| pointed-to by this iterator. | ||
| The View can be queried for its size and content. | ||
| */ | ||
| pointer operator->() noexcept { return &view; } | ||
|
|
||
| private: | ||
| View view; | ||
|
|
||
| #if JUCE_DEBUG | ||
| size_t bytesRemaining = 0; | ||
| #endif | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using | ||
| the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages. | ||
| @tags{Audio} | ||
| */ | ||
| class Midi1ToBytestreamTranslator | ||
| { | ||
| public: | ||
| /** Ensures that there is room in the internal buffer for a sysex message of at least | ||
| `initialBufferSize` bytes. | ||
| */ | ||
| explicit Midi1ToBytestreamTranslator (int initialBufferSize) | ||
| { | ||
| pendingSysExData.reserve (size_t (initialBufferSize)); | ||
| } | ||
|
|
||
| /** Clears the concatenator. */ | ||
| void reset() | ||
| { | ||
| pendingSysExData.clear(); | ||
| pendingSysExTime = 0.0; | ||
| } | ||
|
|
||
| /** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to | ||
| an equivalent MidiMessage. Accumulates SysEx packets into a single | ||
| MidiMessage, as appropriate. | ||
| @param packet a packet which is using the MIDI 1.0 Protocol. | ||
| @param time the timestamp to be applied to these messages. | ||
| @param callback a callback which will be called with each converted MidiMessage. | ||
| */ | ||
| template <typename MessageCallback> | ||
| void dispatch (const View& packet, double time, MessageCallback&& callback) | ||
| { | ||
| const auto firstWord = *packet.data(); | ||
|
|
||
| if (! pendingSysExData.empty() && shouldPacketTerminateSysExEarly (firstWord)) | ||
| pendingSysExData.clear(); | ||
|
|
||
| switch (packet.size()) | ||
| { | ||
| case 1: | ||
| { | ||
| // Utility messages don't translate to bytestream format | ||
| if (Utils::getMessageType (firstWord) != 0x00) | ||
| callback (fromUmp (PacketX1 { firstWord }, time)); | ||
|
|
||
| break; | ||
| } | ||
|
|
||
| case 2: | ||
| { | ||
| if (Utils::getMessageType (firstWord) == 0x3) | ||
| processSysEx (PacketX2 { packet[0], packet[1] }, time, callback); | ||
|
|
||
| break; | ||
| } | ||
|
|
||
| case 3: // no 3-word packets in the current spec | ||
| case 4: // no 4-word packets translate to bytestream format | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| /** Converts from a Universal MIDI Packet to MIDI 1 bytestream format. | ||
| This is only capable of converting a single Universal MIDI Packet to | ||
| an equivalent bytestream MIDI message. This function cannot understand | ||
| multi-packet messages, like SysEx7 messages. | ||
| To convert multi-packet messages, use `Midi1ToBytestreamTranslator` | ||
| to convert from a UMP MIDI 1.0 stream, or `ToBytestreamDispatcher` | ||
| to convert from both MIDI 2.0 and MIDI 1.0. | ||
| */ | ||
| static MidiMessage fromUmp (const PacketX1& m, double time = 0) | ||
| { | ||
| const auto word = m.front(); | ||
| jassert (Utils::getNumWordsForMessageType (word) == 1); | ||
|
|
||
| const std::array<uint8_t, 3> bytes { { uint8_t ((word >> 0x10) & 0xff), | ||
| uint8_t ((word >> 0x08) & 0xff), | ||
| uint8_t ((word >> 0x00) & 0xff) } }; | ||
| const auto numBytes = MidiMessage::getMessageLengthFromFirstByte (bytes.front()); | ||
| return MidiMessage (bytes.data(), numBytes, time); | ||
| } | ||
|
|
||
| private: | ||
| template <typename MessageCallback> | ||
| void processSysEx (const PacketX2& packet, | ||
| double time, | ||
| MessageCallback&& callback) | ||
| { | ||
| switch (getSysEx7Kind (packet[0])) | ||
| { | ||
| case SysEx7::Kind::complete: | ||
| startSysExMessage (time); | ||
| pushBytes (packet); | ||
| terminateSysExMessage (callback); | ||
| break; | ||
|
|
||
| case SysEx7::Kind::begin: | ||
| startSysExMessage (time); | ||
| pushBytes (packet); | ||
| break; | ||
|
|
||
| case SysEx7::Kind::continuation: | ||
| if (pendingSysExData.empty()) | ||
| break; | ||
|
|
||
| pushBytes (packet); | ||
| break; | ||
|
|
||
| case SysEx7::Kind::end: | ||
| if (pendingSysExData.empty()) | ||
| break; | ||
|
|
||
| pushBytes (packet); | ||
| terminateSysExMessage (callback); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| void pushBytes (const PacketX2& packet) | ||
| { | ||
| const auto bytes = SysEx7::getDataBytes (packet); | ||
| pendingSysExData.insert (pendingSysExData.end(), | ||
| bytes.data.begin(), | ||
| bytes.data.begin() + bytes.size); | ||
| } | ||
|
|
||
| void startSysExMessage (double time) | ||
| { | ||
| pendingSysExTime = time; | ||
| pendingSysExData.push_back (0xf0); | ||
| } | ||
|
|
||
| template <typename MessageCallback> | ||
| void terminateSysExMessage (MessageCallback&& callback) | ||
| { | ||
| pendingSysExData.push_back (0xf7); | ||
| callback (MidiMessage (pendingSysExData.data(), | ||
| int (pendingSysExData.size()), | ||
| pendingSysExTime)); | ||
| pendingSysExData.clear(); | ||
| } | ||
|
|
||
| static bool shouldPacketTerminateSysExEarly (uint32_t firstWord) | ||
| { | ||
| return ! (isSysExContinuation (firstWord) | ||
| || isSystemRealTime (firstWord) | ||
| || isJROrNOP (firstWord)); | ||
| } | ||
|
|
||
| static SysEx7::Kind getSysEx7Kind (uint32_t word) | ||
| { | ||
| return SysEx7::Kind ((word >> 0x14) & 0xf); | ||
| } | ||
|
|
||
| static bool isJROrNOP (uint32_t word) | ||
| { | ||
| return Utils::getMessageType (word) == 0x0; | ||
| } | ||
|
|
||
| static bool isSysExContinuation (uint32_t word) | ||
| { | ||
| if (Utils::getMessageType (word) != 0x3) | ||
| return false; | ||
|
|
||
| const auto kind = getSysEx7Kind (word); | ||
| return kind == SysEx7::Kind::continuation || kind == SysEx7::Kind::end; | ||
| } | ||
|
|
||
| static bool isSystemRealTime (uint32_t word) | ||
| { | ||
| return Utils::getMessageType (word) == 0x1 && ((word >> 0x10) & 0xff) >= 0xf8; | ||
| } | ||
|
|
||
| std::vector<uint8_t> pendingSysExData; | ||
|
|
||
| double pendingSysExTime = 0.0; | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| PacketX2 Midi1ToMidi2DefaultTranslator::processNoteOnOrOff (const HelperValues helpers) | ||
| { | ||
| const auto velocity = helpers.byte2; | ||
| const auto needsConversion = (helpers.byte0 >> 0x4) == 0x9 && velocity == 0; | ||
| const auto firstByte = needsConversion ? (uint8_t) ((0x8 << 0x4) | (helpers.byte0 & 0xf)) | ||
| : helpers.byte0; | ||
|
|
||
| return PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, firstByte, helpers.byte1, 0), | ||
| (uint32_t) (Conversion::scaleTo16 (velocity) << 0x10) | ||
| }; | ||
| } | ||
|
|
||
| PacketX2 Midi1ToMidi2DefaultTranslator::processPolyPressure (const HelperValues helpers) | ||
| { | ||
| return PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, helpers.byte1, 0), | ||
| Conversion::scaleTo32 (helpers.byte2) | ||
| }; | ||
| } | ||
|
|
||
| bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues helpers, | ||
| PacketX2& packet) | ||
| { | ||
| const auto statusAndChannel = helpers.byte0; | ||
| const auto cc = helpers.byte1; | ||
|
|
||
| const auto shouldAccumulate = [&] | ||
| { | ||
| switch (cc) | ||
| { | ||
| case 6: | ||
| case 38: | ||
| case 98: | ||
| case 99: | ||
| case 100: | ||
| case 101: | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| }(); | ||
|
|
||
| const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); | ||
| const auto channel = (uint8_t) (statusAndChannel & 0xf); | ||
| const auto byte = helpers.byte2; | ||
|
|
||
| if (shouldAccumulate) | ||
| { | ||
| auto& accumulator = groupAccumulators[group][channel]; | ||
|
|
||
| if (accumulator.addByte (cc, byte)) | ||
| { | ||
| const auto& bytes = accumulator.getBytes(); | ||
| const auto bank = bytes[0]; | ||
| const auto index = bytes[1]; | ||
| const auto msb = bytes[2]; | ||
| const auto lsb = bytes[3]; | ||
|
|
||
| const auto value = (uint16_t) (((msb & 0x7f) << 7) | (lsb & 0x7f)); | ||
|
|
||
| const auto newStatus = (uint8_t) (accumulator.getKind() == PnKind::nrpn ? 0x3 : 0x2); | ||
|
|
||
| packet = PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, (uint8_t) ((newStatus << 0x4) | channel), bank, index), | ||
| Conversion::scaleTo32 (value) | ||
| }; | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| if (cc == 0) | ||
| { | ||
| groupBanks[group][channel].setMsb (byte); | ||
| return false; | ||
| } | ||
|
|
||
| if (cc == 32) | ||
| { | ||
| groupBanks[group][channel].setLsb (byte); | ||
| return false; | ||
| } | ||
|
|
||
| packet = PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, statusAndChannel, cc, 0), | ||
| Conversion::scaleTo32 (helpers.byte2) | ||
| }; | ||
| return true; | ||
| } | ||
|
|
||
| PacketX2 Midi1ToMidi2DefaultTranslator::processProgramChange (const HelperValues helpers) const | ||
| { | ||
| const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); | ||
| const auto channel = (uint8_t) (helpers.byte0 & 0xf); | ||
| const auto bank = groupBanks[group][channel]; | ||
| const auto valid = bank.isValid(); | ||
|
|
||
| return PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, valid ? 1 : 0), | ||
| Utils::bytesToWord (helpers.byte1, 0, valid ? bank.getMsb() : 0, valid ? bank.getLsb() : 0) | ||
| }; | ||
| } | ||
|
|
||
| PacketX2 Midi1ToMidi2DefaultTranslator::processChannelPressure (const HelperValues helpers) | ||
| { | ||
| return PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), | ||
| Conversion::scaleTo32 (helpers.byte1) | ||
| }; | ||
| } | ||
|
|
||
| PacketX2 Midi1ToMidi2DefaultTranslator::processPitchBend (const HelperValues helpers) | ||
| { | ||
| const auto lsb = helpers.byte1; | ||
| const auto msb = helpers.byte2; | ||
| const auto value = (uint16_t) (msb << 7 | lsb); | ||
|
|
||
| return PacketX2 | ||
| { | ||
| Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), | ||
| Conversion::scaleTo32 (value) | ||
| }; | ||
| } | ||
|
|
||
| bool Midi1ToMidi2DefaultTranslator::PnAccumulator::addByte (uint8_t cc, uint8_t byte) | ||
| { | ||
| const auto isStart = cc == 99 || cc == 101; | ||
|
|
||
| if (isStart) | ||
| { | ||
| kind = cc == 99 ? PnKind::nrpn : PnKind::rpn; | ||
| index = 0; | ||
| } | ||
|
|
||
| bytes[index] = byte; | ||
|
|
||
| const auto shouldContinue = [&] | ||
| { | ||
| switch (index) | ||
| { | ||
| case 0: return isStart; | ||
| case 1: return kind == PnKind::nrpn ? cc == 98 : cc == 100; | ||
| case 2: return cc == 6; | ||
| case 3: return cc == 38; | ||
| } | ||
|
|
||
| return false; | ||
| }(); | ||
|
|
||
| index = shouldContinue ? index + 1 : 0; | ||
|
|
||
| if (index != bytes.size()) | ||
| return false; | ||
|
|
||
| index = 0; | ||
| return true; | ||
| } | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Translates a series of MIDI 1 Universal MIDI Packets to corresponding MIDI 2 | ||
| packets. | ||
| @tags{Audio} | ||
| */ | ||
| class Midi1ToMidi2DefaultTranslator | ||
| { | ||
| public: | ||
| Midi1ToMidi2DefaultTranslator() = default; | ||
|
|
||
| /** Converts MIDI 1 Universal MIDI Packets to corresponding MIDI 2 packets, | ||
| calling `callback` with each converted packet. | ||
| In some cases (such as RPN/NRPN messages) multiple MIDI 1 packets will | ||
| convert to a single MIDI 2 packet. In these cases, the translator will | ||
| accumulate the full message internally, and send a single callback with | ||
| the completed message, once all the individual MIDI 1 packets have been | ||
| processed. | ||
| */ | ||
| template <typename PacketCallback> | ||
| void dispatch (const View& v, PacketCallback&& callback) | ||
| { | ||
| const auto firstWord = v[0]; | ||
| const auto messageType = Utils::getMessageType (firstWord); | ||
|
|
||
| if (messageType != 0x2) | ||
| { | ||
| callback (v); | ||
| return; | ||
| } | ||
|
|
||
| const HelperValues helperValues | ||
| { | ||
| (uint8_t) ((0x4 << 0x4) | Utils::getGroup (firstWord)), | ||
| (uint8_t) ((firstWord >> 0x10) & 0xff), | ||
| (uint8_t) ((firstWord >> 0x08) & 0x7f), | ||
| (uint8_t) ((firstWord >> 0x00) & 0x7f), | ||
| }; | ||
|
|
||
| switch (Utils::getStatus (firstWord)) | ||
| { | ||
| case 0x8: | ||
| case 0x9: | ||
| { | ||
| const auto packet = processNoteOnOrOff (helperValues); | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0xa: | ||
| { | ||
| const auto packet = processPolyPressure (helperValues); | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0xb: | ||
| { | ||
| PacketX2 packet; | ||
|
|
||
| if (processControlChange (helperValues, packet)) | ||
| callback (View (packet.data())); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| case 0xc: | ||
| { | ||
| const auto packet = processProgramChange (helperValues); | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0xd: | ||
| { | ||
| const auto packet = processChannelPressure (helperValues); | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
|
|
||
| case 0xe: | ||
| { | ||
| const auto packet = processPitchBend (helperValues); | ||
| callback (View (packet.data())); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void reset() | ||
| { | ||
| groupAccumulators = {}; | ||
| groupBanks = {}; | ||
| } | ||
|
|
||
| private: | ||
| enum class PnKind { nrpn, rpn }; | ||
|
|
||
| struct HelperValues | ||
| { | ||
| uint8_t typeAndGroup; | ||
| uint8_t byte0; | ||
| uint8_t byte1; | ||
| uint8_t byte2; | ||
| }; | ||
|
|
||
| static PacketX2 processNoteOnOrOff (const HelperValues helpers); | ||
| static PacketX2 processPolyPressure (const HelperValues helpers); | ||
|
|
||
| bool processControlChange (const HelperValues helpers, PacketX2& packet); | ||
|
|
||
| PacketX2 processProgramChange (const HelperValues helpers) const; | ||
|
|
||
| static PacketX2 processChannelPressure (const HelperValues helpers); | ||
| static PacketX2 processPitchBend (const HelperValues helpers); | ||
|
|
||
| class PnAccumulator | ||
| { | ||
| public: | ||
| bool addByte (uint8_t cc, uint8_t byte); | ||
|
|
||
| const std::array<uint8_t, 4>& getBytes() const noexcept { return bytes; } | ||
| PnKind getKind() const noexcept { return kind; } | ||
|
|
||
| private: | ||
| std::array<uint8_t, 4> bytes; | ||
| uint8_t index = 0; | ||
| PnKind kind = PnKind::nrpn; | ||
| }; | ||
|
|
||
| class Bank | ||
| { | ||
| public: | ||
| bool isValid() const noexcept { return ! (msb & 0x80); } | ||
|
|
||
| uint8_t getMsb() const noexcept { return msb & 0x7f; } | ||
| uint8_t getLsb() const noexcept { return lsb & 0x7f; } | ||
|
|
||
| void setMsb (uint8_t i) noexcept { msb = i & 0x7f; } | ||
| void setLsb (uint8_t i) noexcept { msb &= 0x7f; lsb = i & 0x7f; } | ||
|
|
||
| private: | ||
| // We use the top bit to indicate whether this bank is valid. | ||
| // After reading the spec, it's not clear how we should determine whether | ||
| // there are valid values, so we'll just assume that the bank is valid | ||
| // once either the lsb or msb have been written. | ||
| uint8_t msb = 0x80; | ||
| uint8_t lsb = 0x00; | ||
| }; | ||
|
|
||
| using ChannelAccumulators = std::array<PnAccumulator, 16>; | ||
| std::array<ChannelAccumulators, 16> groupAccumulators; | ||
|
|
||
| using ChannelBanks = std::array<Bank, 16>; | ||
| std::array<ChannelBanks, 16> groupBanks; | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** The kinds of MIDI protocol that can be formatted into Universal MIDI Packets. */ | ||
| enum class PacketProtocol | ||
| { | ||
| MIDI_1_0, | ||
| MIDI_2_0, | ||
| }; | ||
|
|
||
| /** All kinds of MIDI protocol understood by JUCE. */ | ||
| enum class MidiProtocol | ||
| { | ||
| bytestream, | ||
| UMP_MIDI_1_0, | ||
| UMP_MIDI_2_0, | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| A base class for classes which receive Universal MIDI Packets from an input. | ||
| @tags{Audio} | ||
| */ | ||
| struct Receiver | ||
| { | ||
| virtual ~Receiver() noexcept = default; | ||
|
|
||
| /** This will be called each time a new packet is ready for processing. */ | ||
| virtual void packetReceived (const View& packet, double time) = 0; | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| uint32_t SysEx7::getNumPacketsRequiredForDataSize (uint32_t size) | ||
| { | ||
| constexpr auto denom = 6; | ||
| return (size / denom) + ((size % denom) != 0); | ||
| } | ||
|
|
||
| SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet) | ||
| { | ||
| const auto numBytes = Utils::getChannel (packet[0]); | ||
| constexpr uint8_t maxBytes = 6; | ||
| jassert (numBytes <= maxBytes); | ||
|
|
||
| return | ||
| { | ||
| { { packet.getU8<2>(), | ||
| packet.getU8<3>(), | ||
| packet.getU8<4>(), | ||
| packet.getU8<5>(), | ||
| packet.getU8<6>(), | ||
| packet.getU8<7>() } }, | ||
| jmin (numBytes, maxBytes) | ||
| }; | ||
| } | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| This struct acts as a single-file namespace for Universal MIDI Packet | ||
| functionality related to 7-bit SysEx. | ||
| @tags{Audio} | ||
| */ | ||
| struct SysEx7 | ||
| { | ||
| /** Returns the number of 64-bit packets required to hold a series of | ||
| SysEx bytes. | ||
| The number passed to this function should exclude the leading/trailing | ||
| SysEx bytes used in an old midi bytestream, as these are not required | ||
| when using Universal MIDI Packets. | ||
| */ | ||
| static uint32_t getNumPacketsRequiredForDataSize (uint32_t); | ||
|
|
||
| /** The different kinds of UMP SysEx-7 message. */ | ||
| enum class Kind : uint8_t | ||
| { | ||
| /** The whole message fits in a single 2-word packet. */ | ||
| complete = 0, | ||
|
|
||
| /** The packet begins a SysEx message that will continue in subsequent packets. */ | ||
| begin = 1, | ||
|
|
||
| /** The packet is a continuation of an ongoing SysEx message. */ | ||
| continuation = 2, | ||
|
|
||
| /** The packet terminates an ongoing SysEx message. */ | ||
| end = 3 | ||
| }; | ||
|
|
||
| /** Holds the bytes from a single SysEx-7 packet. */ | ||
| struct PacketBytes | ||
| { | ||
| std::array<uint8_t, 6> data; | ||
| uint8_t size; | ||
| }; | ||
|
|
||
| /** Extracts the data bytes from a 64-bit data message. */ | ||
| static PacketBytes getDataBytes (const PacketX2& packet); | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| uint32_t Utils::getNumWordsForMessageType (uint32_t mt) | ||
| { | ||
| switch (Utils::getMessageType (mt)) | ||
| { | ||
| case 0x0: | ||
| case 0x1: | ||
| case 0x2: | ||
| case 0x6: | ||
| case 0x7: | ||
| return 1; | ||
| case 0x3: | ||
| case 0x4: | ||
| case 0x8: | ||
| case 0x9: | ||
| case 0xa: | ||
| return 2; | ||
| case 0xb: | ||
| case 0xc: | ||
| return 3; | ||
| case 0x5: | ||
| case 0xd: | ||
| case 0xe: | ||
| case 0xf: | ||
| return 4; | ||
| } | ||
|
|
||
| jassertfalse; | ||
| return 1; | ||
| } | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Helpful types and functions for interacting with Universal MIDI Packets. | ||
| @tags{Audio} | ||
| */ | ||
| struct Utils | ||
| { | ||
| /** Joins 4 bytes into a single 32-bit word. */ | ||
| static constexpr uint32_t bytesToWord (uint8_t a, uint8_t b, uint8_t c, uint8_t d) | ||
| { | ||
| return uint32_t (a << 0x18 | b << 0x10 | c << 0x08 | d << 0x00); | ||
| } | ||
|
|
||
| /** Returns the expected number of 32-bit words in a Universal MIDI Packet, given | ||
| the first word of the packet. | ||
| The result will be between 1 and 4 inclusive. | ||
| A result of 1 means that the word is itself a complete packet. | ||
| */ | ||
| static uint32_t getNumWordsForMessageType (uint32_t); | ||
|
|
||
| /** | ||
| Helper functions for setting/getting 4-bit ranges inside a 32-bit word. | ||
| */ | ||
| template <size_t Index> | ||
| struct U4 | ||
| { | ||
| static constexpr uint32_t shift = (uint32_t) 0x1c - Index * 4; | ||
|
|
||
| static constexpr uint32_t set (uint32_t word, uint8_t value) | ||
| { | ||
| return (word & ~((uint32_t) 0xf << shift)) | (uint32_t) ((value & 0xf) << shift); | ||
| } | ||
|
|
||
| static constexpr uint8_t get (uint32_t word) | ||
| { | ||
| return (uint8_t) ((word >> shift) & 0xf); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| Helper functions for setting/getting 8-bit ranges inside a 32-bit word. | ||
| */ | ||
| template <size_t Index> | ||
| struct U8 | ||
| { | ||
| static constexpr uint32_t shift = (uint32_t) 0x18 - Index * 8; | ||
|
|
||
| static constexpr uint32_t set (uint32_t word, uint8_t value) | ||
| { | ||
| return (word & ~((uint32_t) 0xff << shift)) | (uint32_t) (value << shift); | ||
| } | ||
|
|
||
| static constexpr uint8_t get (uint32_t word) | ||
| { | ||
| return (uint8_t) ((word >> shift) & 0xff); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| Helper functions for setting/getting 16-bit ranges inside a 32-bit word. | ||
| */ | ||
| template <size_t Index> | ||
| struct U16 | ||
| { | ||
| static constexpr uint32_t shift = (uint32_t) 0x10 - Index * 16; | ||
|
|
||
| static constexpr uint32_t set (uint32_t word, uint16_t value) | ||
| { | ||
| return (word & ~((uint32_t) 0xffff << shift)) | (uint32_t) (value << shift); | ||
| } | ||
|
|
||
| static constexpr uint16_t get (uint32_t word) | ||
| { | ||
| return (uint16_t) ((word >> shift) & 0xffff); | ||
| } | ||
| }; | ||
|
|
||
| static constexpr uint8_t getMessageType (uint32_t w) noexcept { return U4<0>::get (w); } | ||
| static constexpr uint8_t getGroup (uint32_t w) noexcept { return U4<1>::get (w); } | ||
| static constexpr uint8_t getStatus (uint32_t w) noexcept { return U4<2>::get (w); } | ||
| static constexpr uint8_t getChannel (uint32_t w) noexcept { return U4<3>::get (w); } | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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 | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| uint32_t View::size() const noexcept | ||
| { | ||
| jassert (ptr != nullptr); | ||
| return Utils::getNumWordsForMessageType (*ptr); | ||
| } | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| /* | ||
| ============================================================================== | ||
| This file is part of the JUCE library. | ||
| Copyright (c) 2022 - 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. | ||
| ============================================================================== | ||
| */ | ||
|
|
||
| #ifndef DOXYGEN | ||
|
|
||
| namespace juce | ||
| { | ||
| namespace universal_midi_packets | ||
| { | ||
|
|
||
| /** | ||
| Points to a single Universal MIDI Packet. | ||
| The packet must be well-formed for member functions to work correctly. | ||
| Specifically, the constructor argument must be the beginning of a region of | ||
| uint32_t that contains at least `getNumWordsForMessageType(*ddata)` items, | ||
| where `data` is the constructor argument. | ||
| NOTE: Instances of this class do not own the memory that they point to! | ||
| If you need to store a packet pointed-to by a View for later use, copy | ||
| the view contents to a Packets collection, or use the Utils::PacketX types. | ||
| @tags{Audio} | ||
| */ | ||
| class View | ||
| { | ||
| public: | ||
| /** Create an invalid view. */ | ||
| View() noexcept = default; | ||
|
|
||
| /** Create a view of the packet starting at address `d`. */ | ||
| explicit View (const uint32_t* data) noexcept : ptr (data) {} | ||
|
|
||
| /** Get a pointer to the first word in the Universal MIDI Packet currently | ||
| pointed-to by this view. | ||
| */ | ||
| const uint32_t* data() const noexcept { return ptr; } | ||
|
|
||
| /** Get the number of 32-words (between 1 and 4 inclusive) in the Universal | ||
| MIDI Packet currently pointed-to by this view. | ||
| */ | ||
| uint32_t size() const noexcept; | ||
|
|
||
| /** Get a specific word from this packet. | ||
| Passing an `index` that is greater than or equal to the result of `size` | ||
| will cause undefined behaviour. | ||
| */ | ||
| const uint32_t& operator[] (size_t index) const noexcept { return ptr[index]; } | ||
|
|
||
| /** Get an iterator pointing to the first word in the packet. */ | ||
| const uint32_t* begin() const noexcept { return ptr; } | ||
| const uint32_t* cbegin() const noexcept { return ptr; } | ||
|
|
||
| /** Get an iterator pointing one-past the last word in the packet. */ | ||
| const uint32_t* end() const noexcept { return ptr + size(); } | ||
| const uint32_t* cend() const noexcept { return ptr + size(); } | ||
|
|
||
| /** Return true if this view is pointing to the same address as another view. */ | ||
| bool operator== (const View& other) const noexcept { return ptr == other.ptr; } | ||
|
|
||
| /** Return false if this view is pointing to the same address as another view. */ | ||
| bool operator!= (const View& other) const noexcept { return ! operator== (other); } | ||
|
|
||
| private: | ||
| const uint32_t* ptr = nullptr; | ||
| }; | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif |