git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
320
deps/juce/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp
vendored
Normal file
320
deps/juce/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp
vendored
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace MidiBufferHelpers
|
||||
{
|
||||
inline int getEventTime (const void* d) noexcept
|
||||
{
|
||||
return readUnaligned<int32> (d);
|
||||
}
|
||||
|
||||
inline uint16 getEventDataSize (const void* d) noexcept
|
||||
{
|
||||
return readUnaligned<uint16> (static_cast<const char*> (d) + sizeof (int32));
|
||||
}
|
||||
|
||||
inline uint16 getEventTotalSize (const void* d) noexcept
|
||||
{
|
||||
return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16));
|
||||
}
|
||||
|
||||
static int findActualEventLength (const uint8* data, int maxBytes) noexcept
|
||||
{
|
||||
auto byte = (unsigned int) *data;
|
||||
|
||||
if (byte == 0xf0 || byte == 0xf7)
|
||||
{
|
||||
int i = 1;
|
||||
|
||||
while (i < maxBytes)
|
||||
if (data[i++] == 0xf7)
|
||||
break;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
if (byte == 0xff)
|
||||
{
|
||||
if (maxBytes == 1)
|
||||
return 1;
|
||||
|
||||
const auto var = MidiMessage::readVariableLengthValue (data + 1, maxBytes - 1);
|
||||
return jmin (maxBytes, var.value + 2 + var.bytesUsed);
|
||||
}
|
||||
|
||||
if (byte >= 0x80)
|
||||
return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept
|
||||
{
|
||||
while (d < endData && getEventTime (d) <= samplePosition)
|
||||
d += getEventTotalSize (d);
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiBufferIterator& MidiBufferIterator::operator++() noexcept
|
||||
{
|
||||
data += sizeof (int32) + sizeof (uint16) + size_t (MidiBufferHelpers::getEventDataSize (data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
MidiBufferIterator MidiBufferIterator::operator++ (int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
MidiBufferIterator::reference MidiBufferIterator::operator*() const noexcept
|
||||
{
|
||||
return { data + sizeof (int32) + sizeof (uint16),
|
||||
MidiBufferHelpers::getEventDataSize (data),
|
||||
MidiBufferHelpers::getEventTime (data) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept
|
||||
{
|
||||
addEvent (message, 0);
|
||||
}
|
||||
|
||||
void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); }
|
||||
void MidiBuffer::clear() noexcept { data.clearQuick(); }
|
||||
void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); }
|
||||
bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; }
|
||||
|
||||
void MidiBuffer::clear (int startSample, int numSamples)
|
||||
{
|
||||
auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1);
|
||||
auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1);
|
||||
|
||||
data.removeRange ((int) (start - data.begin()), (int) (end - start));
|
||||
}
|
||||
|
||||
bool MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber)
|
||||
{
|
||||
return addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
|
||||
}
|
||||
|
||||
bool MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber)
|
||||
{
|
||||
auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes);
|
||||
|
||||
if (numBytes <= 0)
|
||||
return true;
|
||||
|
||||
if (std::numeric_limits<uint16>::max() < numBytes)
|
||||
{
|
||||
// This method only supports messages smaller than (1 << 16) bytes
|
||||
return false;
|
||||
}
|
||||
|
||||
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16);
|
||||
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin());
|
||||
|
||||
data.insertMultiple (offset, 0, (int) newItemSize);
|
||||
|
||||
auto* d = data.begin() + offset;
|
||||
writeUnaligned<int32> (d, sampleNumber);
|
||||
d += sizeof (int32);
|
||||
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes));
|
||||
d += sizeof (uint16);
|
||||
memcpy (d, newData, (size_t) numBytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer,
|
||||
int startSample, int numSamples, int sampleDeltaToAdd)
|
||||
{
|
||||
for (auto i = otherBuffer.findNextSamplePosition (startSample); i != otherBuffer.cend(); ++i)
|
||||
{
|
||||
const auto metadata = *i;
|
||||
|
||||
if (metadata.samplePosition >= startSample + numSamples && numSamples >= 0)
|
||||
break;
|
||||
|
||||
addEvent (metadata.data, metadata.numBytes, metadata.samplePosition + sampleDeltaToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
int MidiBuffer::getNumEvents() const noexcept
|
||||
{
|
||||
int n = 0;
|
||||
auto end = data.end();
|
||||
|
||||
for (auto d = data.begin(); d < end; ++n)
|
||||
d += MidiBufferHelpers::getEventTotalSize (d);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int MidiBuffer::getFirstEventTime() const noexcept
|
||||
{
|
||||
return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0;
|
||||
}
|
||||
|
||||
int MidiBuffer::getLastEventTime() const noexcept
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return 0;
|
||||
|
||||
auto endData = data.end();
|
||||
|
||||
for (auto d = data.begin();;)
|
||||
{
|
||||
auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d);
|
||||
|
||||
if (nextOne >= endData)
|
||||
return MidiBufferHelpers::getEventTime (d);
|
||||
|
||||
d = nextOne;
|
||||
}
|
||||
}
|
||||
|
||||
MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const noexcept
|
||||
{
|
||||
return std::find_if (cbegin(), cend(), [&] (const MidiMessageMetadata& metadata) noexcept
|
||||
{
|
||||
return metadata.samplePosition >= samplePosition;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
|
||||
|
||||
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept
|
||||
: buffer (b), iterator (b.data.begin())
|
||||
{
|
||||
}
|
||||
|
||||
void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept
|
||||
{
|
||||
iterator = buffer.findNextSamplePosition (samplePosition);
|
||||
}
|
||||
|
||||
bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept
|
||||
{
|
||||
if (iterator == buffer.cend())
|
||||
return false;
|
||||
|
||||
const auto metadata = *iterator++;
|
||||
midiData = metadata.data;
|
||||
numBytes = metadata.numBytes;
|
||||
samplePosition = metadata.samplePosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept
|
||||
{
|
||||
if (iterator == buffer.cend())
|
||||
return false;
|
||||
|
||||
const auto metadata = *iterator++;
|
||||
result = metadata.getMessage();
|
||||
samplePosition = metadata.samplePosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct MidiBufferTest : public UnitTest
|
||||
{
|
||||
MidiBufferTest()
|
||||
: UnitTest ("MidiBuffer", UnitTestCategories::midi)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Clear messages");
|
||||
{
|
||||
const auto message = MidiMessage::noteOn (1, 64, 0.5f);
|
||||
|
||||
const auto testBuffer = [&]
|
||||
{
|
||||
MidiBuffer buffer;
|
||||
buffer.addEvent (message, 0);
|
||||
buffer.addEvent (message, 10);
|
||||
buffer.addEvent (message, 20);
|
||||
buffer.addEvent (message, 30);
|
||||
return buffer;
|
||||
}();
|
||||
|
||||
{
|
||||
auto buffer = testBuffer;
|
||||
buffer.clear (10, 0);
|
||||
expectEquals (buffer.getNumEvents(), 4);
|
||||
}
|
||||
|
||||
{
|
||||
auto buffer = testBuffer;
|
||||
buffer.clear (10, 1);
|
||||
expectEquals (buffer.getNumEvents(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto buffer = testBuffer;
|
||||
buffer.clear (10, 10);
|
||||
expectEquals (buffer.getNumEvents(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto buffer = testBuffer;
|
||||
buffer.clear (10, 20);
|
||||
expectEquals (buffer.getNumEvents(), 2);
|
||||
}
|
||||
|
||||
{
|
||||
auto buffer = testBuffer;
|
||||
buffer.clear (10, 30);
|
||||
expectEquals (buffer.getNumEvents(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto buffer = testBuffer;
|
||||
buffer.clear (10, 300);
|
||||
expectEquals (buffer.getNumEvents(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static MidiBufferTest midiBufferTest;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
345
deps/juce/modules/juce_audio_basics/midi/juce_MidiBuffer.h
vendored
Normal file
345
deps/juce/modules/juce_audio_basics/midi/juce_MidiBuffer.h
vendored
Normal file
@ -0,0 +1,345 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A view of MIDI message data stored in a contiguous buffer.
|
||||
|
||||
Instances of this class do *not* own the midi data bytes that they point to.
|
||||
Instead, they expect the midi data to live in a separate buffer that outlives
|
||||
the MidiMessageMetadata instance.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
struct MidiMessageMetadata final
|
||||
{
|
||||
MidiMessageMetadata() noexcept = default;
|
||||
|
||||
MidiMessageMetadata (const uint8* dataIn, int numBytesIn, int positionIn) noexcept
|
||||
: data (dataIn), numBytes (numBytesIn), samplePosition (positionIn)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructs a new MidiMessage instance from the data that this object is viewing.
|
||||
|
||||
Note that MidiMessage owns its data storage, whereas MidiMessageMetadata does not.
|
||||
*/
|
||||
MidiMessage getMessage() const { return MidiMessage (data, numBytes, samplePosition); }
|
||||
|
||||
/** Pointer to the first byte of a MIDI message. */
|
||||
const uint8* data = nullptr;
|
||||
|
||||
/** The number of bytes in the MIDI message. */
|
||||
int numBytes = 0;
|
||||
|
||||
/** The MIDI message's timestamp. */
|
||||
int samplePosition = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An iterator to move over contiguous raw MIDI data, which Allows iterating
|
||||
over a MidiBuffer using C++11 range-for syntax.
|
||||
|
||||
In the following example, we log all three-byte messages in a midi buffer.
|
||||
@code
|
||||
void processBlock (AudioBuffer<float>&, MidiBuffer& midiBuffer) override
|
||||
{
|
||||
for (const MidiMessageMetadata metadata : midiBuffer)
|
||||
if (metadata.numBytes == 3)
|
||||
Logger::writeToLog (metadata.getMessage().getDescription());
|
||||
}
|
||||
@endcode
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiBufferIterator
|
||||
{
|
||||
using Ptr = const uint8*;
|
||||
|
||||
public:
|
||||
MidiBufferIterator() = default;
|
||||
|
||||
/** Constructs an iterator pointing at the message starting at the byte `dataIn`.
|
||||
`dataIn` must point to the start of a valid MIDI message. If it does not,
|
||||
calling other member functions on the iterator will result in undefined
|
||||
behaviour.
|
||||
*/
|
||||
explicit MidiBufferIterator (const uint8* dataIn) noexcept
|
||||
: data (dataIn)
|
||||
{
|
||||
}
|
||||
|
||||
using difference_type = std::iterator_traits<Ptr>::difference_type;
|
||||
using value_type = MidiMessageMetadata;
|
||||
using reference = MidiMessageMetadata;
|
||||
using pointer = void;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
/** Make this iterator point to the next message in the buffer. */
|
||||
MidiBufferIterator& operator++() noexcept;
|
||||
|
||||
/** Create a copy of this object, make this iterator point to the next message in
|
||||
the buffer, then return the copy.
|
||||
*/
|
||||
MidiBufferIterator operator++ (int) noexcept;
|
||||
|
||||
/** Return true if this iterator points to the same message as another
|
||||
iterator instance, otherwise return false.
|
||||
*/
|
||||
bool operator== (const MidiBufferIterator& other) const noexcept { return data == other.data; }
|
||||
|
||||
/** Return false if this iterator points to the same message as another
|
||||
iterator instance, otherwise returns true.
|
||||
*/
|
||||
bool operator!= (const MidiBufferIterator& other) const noexcept { return ! operator== (other); }
|
||||
|
||||
/** Return an instance of MidiMessageMetadata which describes the message to which
|
||||
the iterator is currently pointing.
|
||||
*/
|
||||
reference operator*() const noexcept;
|
||||
|
||||
private:
|
||||
Ptr data = nullptr;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Holds a sequence of time-stamped midi events.
|
||||
|
||||
Analogous to the AudioBuffer, this holds a set of midi events with
|
||||
integer time-stamps. The buffer is kept sorted in order of the time-stamps.
|
||||
|
||||
If you're working with a sequence of midi events that may need to be manipulated
|
||||
or read/written to a midi file, then MidiMessageSequence is probably a more
|
||||
appropriate container. MidiBuffer is designed for lower-level streams of raw
|
||||
midi data.
|
||||
|
||||
@see MidiMessage
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiBuffer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty MidiBuffer. */
|
||||
MidiBuffer() noexcept = default;
|
||||
|
||||
/** Creates a MidiBuffer containing a single midi message. */
|
||||
explicit MidiBuffer (const MidiMessage& message) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all events from the buffer. */
|
||||
void clear() noexcept;
|
||||
|
||||
/** Removes all events between two times from the buffer.
|
||||
|
||||
All events for which (start <= event position < start + numSamples) will
|
||||
be removed.
|
||||
*/
|
||||
void clear (int start, int numSamples);
|
||||
|
||||
/** Returns true if the buffer is empty.
|
||||
To actually retrieve the events, use a MidiBufferIterator object
|
||||
*/
|
||||
bool isEmpty() const noexcept;
|
||||
|
||||
/** Counts the number of events in the buffer.
|
||||
|
||||
This is actually quite a slow operation, as it has to iterate through all
|
||||
the events, so you might prefer to call isEmpty() if that's all you need
|
||||
to know.
|
||||
*/
|
||||
int getNumEvents() const noexcept;
|
||||
|
||||
/** Adds an event to the buffer.
|
||||
|
||||
The sample number will be used to determine the position of the event in
|
||||
the buffer, which is always kept sorted. The MidiMessage's timestamp is
|
||||
ignored.
|
||||
|
||||
If an event is added whose sample position is the same as one or more events
|
||||
already in the buffer, the new event will be placed after the existing ones.
|
||||
|
||||
To retrieve events, use a MidiBufferIterator object.
|
||||
|
||||
Returns true on success, or false on failure.
|
||||
*/
|
||||
bool addEvent (const MidiMessage& midiMessage, int sampleNumber);
|
||||
|
||||
/** Adds an event to the buffer from raw midi data.
|
||||
|
||||
The sample number will be used to determine the position of the event in
|
||||
the buffer, which is always kept sorted.
|
||||
|
||||
If an event is added whose sample position is the same as one or more events
|
||||
already in the buffer, the new event will be placed after the existing ones.
|
||||
|
||||
The event data will be inspected to calculate the number of bytes in length that
|
||||
the midi event really takes up, so maxBytesOfMidiData may be longer than the data
|
||||
that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes,
|
||||
it'll actually only store 3 bytes. If the midi data is invalid, it might not
|
||||
add an event at all.
|
||||
|
||||
To retrieve events, use a MidiBufferIterator object.
|
||||
|
||||
Returns true on success, or false on failure.
|
||||
*/
|
||||
bool addEvent (const void* rawMidiData,
|
||||
int maxBytesOfMidiData,
|
||||
int sampleNumber);
|
||||
|
||||
/** Adds some events from another buffer to this one.
|
||||
|
||||
@param otherBuffer the buffer containing the events you want to add
|
||||
@param startSample the lowest sample number in the source buffer for which
|
||||
events should be added. Any source events whose timestamp is
|
||||
less than this will be ignored
|
||||
@param numSamples the valid range of samples from the source buffer for which
|
||||
events should be added - i.e. events in the source buffer whose
|
||||
timestamp is greater than or equal to (startSample + numSamples)
|
||||
will be ignored. If this value is less than 0, all events after
|
||||
startSample will be taken.
|
||||
@param sampleDeltaToAdd a value which will be added to the source timestamps of the events
|
||||
that are added to this buffer
|
||||
*/
|
||||
void addEvents (const MidiBuffer& otherBuffer,
|
||||
int startSample,
|
||||
int numSamples,
|
||||
int sampleDeltaToAdd);
|
||||
|
||||
/** Returns the sample number of the first event in the buffer.
|
||||
If the buffer's empty, this will just return 0.
|
||||
*/
|
||||
int getFirstEventTime() const noexcept;
|
||||
|
||||
/** Returns the sample number of the last event in the buffer.
|
||||
If the buffer's empty, this will just return 0.
|
||||
*/
|
||||
int getLastEventTime() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Exchanges the contents of this buffer with another one.
|
||||
|
||||
This is a quick operation, because no memory allocating or copying is done, it
|
||||
just swaps the internal state of the two buffers.
|
||||
*/
|
||||
void swapWith (MidiBuffer&) noexcept;
|
||||
|
||||
/** Preallocates some memory for the buffer to use.
|
||||
This helps to avoid needing to reallocate space when the buffer has messages
|
||||
added to it.
|
||||
*/
|
||||
void ensureSize (size_t minimumNumBytes);
|
||||
|
||||
/** Get a read-only iterator pointing to the beginning of this buffer. */
|
||||
MidiBufferIterator begin() const noexcept { return cbegin(); }
|
||||
|
||||
/** Get a read-only iterator pointing one past the end of this buffer. */
|
||||
MidiBufferIterator end() const noexcept { return cend(); }
|
||||
|
||||
/** Get a read-only iterator pointing to the beginning of this buffer. */
|
||||
MidiBufferIterator cbegin() const noexcept { return MidiBufferIterator (data.begin()); }
|
||||
|
||||
/** Get a read-only iterator pointing one past the end of this buffer. */
|
||||
MidiBufferIterator cend() const noexcept { return MidiBufferIterator (data.end()); }
|
||||
|
||||
/** Get an iterator pointing to the first event with a timestamp greater-than or
|
||||
equal-to `samplePosition`.
|
||||
*/
|
||||
MidiBufferIterator findNextSamplePosition (int samplePosition) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** This class is now deprecated in favour of MidiBufferIterator.
|
||||
|
||||
Used to iterate through the events in a MidiBuffer.
|
||||
|
||||
Note that altering the buffer while an iterator is using it will produce
|
||||
undefined behaviour.
|
||||
|
||||
@see MidiBuffer
|
||||
*/
|
||||
class [[deprecated]] JUCE_API Iterator
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an Iterator for this MidiBuffer. */
|
||||
Iterator (const MidiBuffer& b) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Repositions the iterator so that the next event retrieved will be the first
|
||||
one whose sample position is at greater than or equal to the given position.
|
||||
*/
|
||||
void setNextSamplePosition (int samplePosition) noexcept;
|
||||
|
||||
/** Retrieves a copy of the next event from the buffer.
|
||||
|
||||
@param result on return, this will be the message. The MidiMessage's timestamp
|
||||
is set to the same value as samplePosition.
|
||||
@param samplePosition on return, this will be the position of the event, as a
|
||||
sample index in the buffer
|
||||
@returns true if an event was found, or false if the iterator has reached
|
||||
the end of the buffer
|
||||
*/
|
||||
bool getNextEvent (MidiMessage& result,
|
||||
int& samplePosition) noexcept;
|
||||
|
||||
/** Retrieves the next event from the buffer.
|
||||
|
||||
@param midiData on return, this pointer will be set to a block of data containing
|
||||
the midi message. Note that to make it fast, this is a pointer
|
||||
directly into the MidiBuffer's internal data, so is only valid
|
||||
temporarily until the MidiBuffer is altered.
|
||||
@param numBytesOfMidiData on return, this is the number of bytes of data used by the
|
||||
midi message
|
||||
@param samplePosition on return, this will be the position of the event, as a
|
||||
sample index in the buffer
|
||||
@returns true if an event was found, or false if the iterator has reached
|
||||
the end of the buffer
|
||||
*/
|
||||
bool getNextEvent (const uint8* &midiData,
|
||||
int& numBytesOfMidiData,
|
||||
int& samplePosition) noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
const MidiBuffer& buffer;
|
||||
MidiBufferIterator iterator;
|
||||
};
|
||||
#endif
|
||||
|
||||
/** The raw data holding this buffer.
|
||||
Obviously access to this data is provided at your own risk. Its internal format could
|
||||
change in future, so don't write code that relies on it!
|
||||
*/
|
||||
Array<uint8> data;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (MidiBuffer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
188
deps/juce/modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h
vendored
Normal file
188
deps/juce/modules/juce_audio_basics/midi/juce_MidiDataConcatenator.h
vendored
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Helper class that takes chunks of incoming midi bytes, packages them into
|
||||
messages, and dispatches them to a midi callback.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class MidiDataConcatenator
|
||||
{
|
||||
public:
|
||||
MidiDataConcatenator (int initialBufferSize)
|
||||
: pendingSysexData ((size_t) initialBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
currentMessageLen = 0;
|
||||
pendingSysexSize = 0;
|
||||
pendingSysexTime = 0;
|
||||
}
|
||||
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void pushMidiData (const void* inputData, int numBytes, double time,
|
||||
UserDataType* input, CallbackType& callback)
|
||||
{
|
||||
auto d = static_cast<const uint8*> (inputData);
|
||||
|
||||
while (numBytes > 0)
|
||||
{
|
||||
auto nextByte = *d;
|
||||
|
||||
if (pendingSysexSize != 0 || nextByte == 0xf0)
|
||||
{
|
||||
processSysex (d, numBytes, time, input, callback);
|
||||
currentMessageLen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
++d;
|
||||
--numBytes;
|
||||
|
||||
if (isRealtimeMessage (nextByte))
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (nextByte, time));
|
||||
// These can be embedded in the middle of a normal message, so we won't
|
||||
// reset the currentMessageLen here.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isInitialByte (nextByte))
|
||||
{
|
||||
currentMessage[0] = nextByte;
|
||||
currentMessageLen = 1;
|
||||
}
|
||||
else if (currentMessageLen > 0 && currentMessageLen < 3)
|
||||
{
|
||||
currentMessage[currentMessageLen++] = nextByte;
|
||||
}
|
||||
else
|
||||
{
|
||||
// message is too long or invalid MIDI - abandon it and start again with the next byte
|
||||
currentMessageLen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto expectedLength = MidiMessage::getMessageLengthFromFirstByte (currentMessage[0]);
|
||||
|
||||
if (expectedLength == currentMessageLen)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (currentMessage, expectedLength, time));
|
||||
currentMessageLen = 1; // reset, but leave the first byte to use as the running status byte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void processSysex (const uint8*& d, int& numBytes, double time,
|
||||
UserDataType* input, CallbackType& callback)
|
||||
{
|
||||
if (*d == 0xf0)
|
||||
{
|
||||
pendingSysexSize = 0;
|
||||
pendingSysexTime = time;
|
||||
}
|
||||
|
||||
pendingSysexData.ensureSize ((size_t) (pendingSysexSize + numBytes), false);
|
||||
auto totalMessage = static_cast<uint8*> (pendingSysexData.getData());
|
||||
auto dest = totalMessage + pendingSysexSize;
|
||||
|
||||
do
|
||||
{
|
||||
if (pendingSysexSize > 0 && isStatusByte (*d))
|
||||
{
|
||||
if (*d == 0xf7)
|
||||
{
|
||||
*dest++ = *d++;
|
||||
++pendingSysexSize;
|
||||
--numBytes;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*d >= 0xfa || *d == 0xf8)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time));
|
||||
++d;
|
||||
--numBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingSysexSize = 0;
|
||||
int used = 0;
|
||||
const MidiMessage m (d, numBytes, used, 0, time);
|
||||
|
||||
if (used > 0)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, m);
|
||||
numBytes -= used;
|
||||
d += used;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*dest++ = *d++;
|
||||
++pendingSysexSize;
|
||||
--numBytes;
|
||||
}
|
||||
}
|
||||
while (numBytes > 0);
|
||||
|
||||
if (pendingSysexSize > 0)
|
||||
{
|
||||
if (totalMessage [pendingSysexSize - 1] == 0xf7)
|
||||
{
|
||||
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingSysexSize, pendingSysexTime));
|
||||
pendingSysexSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.handlePartialSysexMessage (input, totalMessage, pendingSysexSize, pendingSysexTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool isRealtimeMessage (uint8 byte) { return byte >= 0xf8 && byte <= 0xfe; }
|
||||
static bool isStatusByte (uint8 byte) { return byte >= 0x80; }
|
||||
static bool isInitialByte (uint8 byte) { return isStatusByte (byte) && byte != 0xf7; }
|
||||
|
||||
uint8 currentMessage[3];
|
||||
int currentMessageLen = 0;
|
||||
|
||||
MemoryBlock pendingSysexData;
|
||||
double pendingSysexTime = 0;
|
||||
int pendingSysexSize = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator)
|
||||
};
|
||||
|
||||
} // namespace juce
|
807
deps/juce/modules/juce_audio_basics/midi/juce_MidiFile.cpp
vendored
Normal file
807
deps/juce/modules/juce_audio_basics/midi/juce_MidiFile.cpp
vendored
Normal file
@ -0,0 +1,807 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace MidiFileHelpers
|
||||
{
|
||||
static void writeVariableLengthInt (OutputStream& out, uint32 v)
|
||||
{
|
||||
auto buffer = v & 0x7f;
|
||||
|
||||
while ((v >>= 7) != 0)
|
||||
{
|
||||
buffer <<= 8;
|
||||
buffer |= ((v & 0x7f) | 0x80);
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
out.writeByte ((char) buffer);
|
||||
|
||||
if (buffer & 0x80)
|
||||
buffer >>= 8;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
struct Optional
|
||||
{
|
||||
Optional() = default;
|
||||
|
||||
Optional (const Value& v)
|
||||
: value (v), valid (true) {}
|
||||
|
||||
Value value = Value();
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
template <typename Integral>
|
||||
struct ReadTrait;
|
||||
|
||||
template <>
|
||||
struct ReadTrait<uint32> { static constexpr auto read = ByteOrder::bigEndianInt; };
|
||||
|
||||
template <>
|
||||
struct ReadTrait<uint16> { static constexpr auto read = ByteOrder::bigEndianShort; };
|
||||
|
||||
template <typename Integral>
|
||||
Optional<Integral> tryRead (const uint8*& data, size_t& remaining)
|
||||
{
|
||||
using Trait = ReadTrait<Integral>;
|
||||
constexpr auto size = sizeof (Integral);
|
||||
|
||||
if (remaining < size)
|
||||
return {};
|
||||
|
||||
const Optional<Integral> result { Trait::read (data) };
|
||||
|
||||
data += size;
|
||||
remaining -= size;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct HeaderDetails
|
||||
{
|
||||
size_t bytesRead = 0;
|
||||
short timeFormat = 0;
|
||||
short fileType = 0;
|
||||
short numberOfTracks = 0;
|
||||
};
|
||||
|
||||
static Optional<HeaderDetails> parseMidiHeader (const uint8* const initialData,
|
||||
const size_t maxSize)
|
||||
{
|
||||
auto* data = initialData;
|
||||
auto remaining = maxSize;
|
||||
|
||||
auto ch = tryRead<uint32> (data, remaining);
|
||||
|
||||
if (! ch.valid)
|
||||
return {};
|
||||
|
||||
if (ch.value != ByteOrder::bigEndianInt ("MThd"))
|
||||
{
|
||||
auto ok = false;
|
||||
|
||||
if (ch.value == ByteOrder::bigEndianInt ("RIFF"))
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
ch = tryRead<uint32> (data, remaining);
|
||||
|
||||
if (! ch.valid)
|
||||
return {};
|
||||
|
||||
if (ch.value == ByteOrder::bigEndianInt ("MThd"))
|
||||
{
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! ok)
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto bytesRemaining = tryRead<uint32> (data, remaining);
|
||||
|
||||
if (! bytesRemaining.valid || bytesRemaining.value > remaining)
|
||||
return {};
|
||||
|
||||
const auto optFileType = tryRead<uint16> (data, remaining);
|
||||
|
||||
if (! optFileType.valid || 2 < optFileType.value)
|
||||
return {};
|
||||
|
||||
const auto optNumTracks = tryRead<uint16> (data, remaining);
|
||||
|
||||
if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1))
|
||||
return {};
|
||||
|
||||
const auto optTimeFormat = tryRead<uint16> (data, remaining);
|
||||
|
||||
if (! optTimeFormat.valid)
|
||||
return {};
|
||||
|
||||
HeaderDetails result;
|
||||
|
||||
result.fileType = (short) optFileType.value;
|
||||
result.timeFormat = (short) optTimeFormat.value;
|
||||
result.numberOfTracks = (short) optNumTracks.value;
|
||||
result.bytesRead = maxSize - remaining;
|
||||
|
||||
return { result };
|
||||
}
|
||||
|
||||
static double convertTicksToSeconds (double time,
|
||||
const MidiMessageSequence& tempoEvents,
|
||||
int timeFormat)
|
||||
{
|
||||
if (timeFormat < 0)
|
||||
return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
|
||||
|
||||
double lastTime = 0, correctedTime = 0;
|
||||
auto tickLen = 1.0 / (timeFormat & 0x7fff);
|
||||
auto secsPerTick = 0.5 * tickLen;
|
||||
auto numEvents = tempoEvents.getNumEvents();
|
||||
|
||||
for (int i = 0; i < numEvents; ++i)
|
||||
{
|
||||
auto& m = tempoEvents.getEventPointer(i)->message;
|
||||
auto eventTime = m.getTimeStamp();
|
||||
|
||||
if (eventTime >= time)
|
||||
break;
|
||||
|
||||
correctedTime += (eventTime - lastTime) * secsPerTick;
|
||||
lastTime = eventTime;
|
||||
|
||||
if (m.isTempoMetaEvent())
|
||||
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
|
||||
|
||||
while (i + 1 < numEvents)
|
||||
{
|
||||
auto& m2 = tempoEvents.getEventPointer(i + 1)->message;
|
||||
|
||||
if (m2.getTimeStamp() != eventTime)
|
||||
break;
|
||||
|
||||
if (m2.isTempoMetaEvent())
|
||||
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return correctedTime + (time - lastTime) * secsPerTick;
|
||||
}
|
||||
|
||||
template <typename MethodType>
|
||||
static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
|
||||
MidiMessageSequence& results,
|
||||
MethodType method)
|
||||
{
|
||||
for (auto* track : tracks)
|
||||
{
|
||||
auto numEvents = track->getNumEvents();
|
||||
|
||||
for (int j = 0; j < numEvents; ++j)
|
||||
{
|
||||
auto& m = track->getEventPointer(j)->message;
|
||||
|
||||
if ((m.*method)())
|
||||
results.addEvent (m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MidiMessageSequence readTrack (const uint8* data, int size)
|
||||
{
|
||||
double time = 0;
|
||||
uint8 lastStatusByte = 0;
|
||||
|
||||
MidiMessageSequence result;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
const auto delay = MidiMessage::readVariableLengthValue (data, (int) size);
|
||||
|
||||
if (! delay.isValid())
|
||||
break;
|
||||
|
||||
data += delay.bytesUsed;
|
||||
size -= delay.bytesUsed;
|
||||
time += delay.value;
|
||||
|
||||
if (size <= 0)
|
||||
break;
|
||||
|
||||
int messSize = 0;
|
||||
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
|
||||
|
||||
if (messSize <= 0)
|
||||
break;
|
||||
|
||||
size -= messSize;
|
||||
data += messSize;
|
||||
|
||||
result.addEvent (mm);
|
||||
|
||||
auto firstByte = *(mm.getRawData());
|
||||
|
||||
if ((firstByte & 0xf0) != 0xf0)
|
||||
lastStatusByte = firstByte;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
|
||||
MidiFile::~MidiFile() {}
|
||||
|
||||
MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
|
||||
{
|
||||
tracks.addCopiesOf (other.tracks);
|
||||
}
|
||||
|
||||
MidiFile& MidiFile::operator= (const MidiFile& other)
|
||||
{
|
||||
tracks.clear();
|
||||
tracks.addCopiesOf (other.tracks);
|
||||
timeFormat = other.timeFormat;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MidiFile::MidiFile (MidiFile&& other)
|
||||
: tracks (std::move (other.tracks)),
|
||||
timeFormat (other.timeFormat)
|
||||
{
|
||||
}
|
||||
|
||||
MidiFile& MidiFile::operator= (MidiFile&& other)
|
||||
{
|
||||
tracks = std::move (other.tracks);
|
||||
timeFormat = other.timeFormat;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MidiFile::clear()
|
||||
{
|
||||
tracks.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int MidiFile::getNumTracks() const noexcept
|
||||
{
|
||||
return tracks.size();
|
||||
}
|
||||
|
||||
const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept
|
||||
{
|
||||
return tracks[index];
|
||||
}
|
||||
|
||||
void MidiFile::addTrack (const MidiMessageSequence& trackSequence)
|
||||
{
|
||||
tracks.add (new MidiMessageSequence (trackSequence));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
short MidiFile::getTimeFormat() const noexcept
|
||||
{
|
||||
return timeFormat;
|
||||
}
|
||||
|
||||
void MidiFile::setTicksPerQuarterNote (int ticks) noexcept
|
||||
{
|
||||
timeFormat = (short) ticks;
|
||||
}
|
||||
|
||||
void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept
|
||||
{
|
||||
timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const
|
||||
{
|
||||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent);
|
||||
}
|
||||
|
||||
void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const
|
||||
{
|
||||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent);
|
||||
}
|
||||
|
||||
void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const
|
||||
{
|
||||
MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent);
|
||||
}
|
||||
|
||||
double MidiFile::getLastTimestamp() const
|
||||
{
|
||||
double t = 0.0;
|
||||
|
||||
for (auto* ms : tracks)
|
||||
t = jmax (t, ms->getEndTime());
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MidiFile::readFrom (InputStream& sourceStream,
|
||||
bool createMatchingNoteOffs,
|
||||
int* fileType)
|
||||
{
|
||||
clear();
|
||||
MemoryBlock data;
|
||||
|
||||
const int maxSensibleMidiFileSize = 200 * 1024 * 1024;
|
||||
|
||||
// (put a sanity-check on the file size, as midi files are generally small)
|
||||
if (! sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
|
||||
return false;
|
||||
|
||||
auto size = data.getSize();
|
||||
auto d = static_cast<const uint8*> (data.getData());
|
||||
|
||||
const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size);
|
||||
|
||||
if (! optHeader.valid)
|
||||
return false;
|
||||
|
||||
const auto header = optHeader.value;
|
||||
timeFormat = header.timeFormat;
|
||||
|
||||
d += header.bytesRead;
|
||||
size -= (size_t) header.bytesRead;
|
||||
|
||||
for (int track = 0; track < header.numberOfTracks; ++track)
|
||||
{
|
||||
const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size);
|
||||
|
||||
if (! optChunkType.valid)
|
||||
return false;
|
||||
|
||||
const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size);
|
||||
|
||||
if (! optChunkSize.valid)
|
||||
return false;
|
||||
|
||||
const auto chunkSize = optChunkSize.value;
|
||||
|
||||
if (size < chunkSize)
|
||||
return false;
|
||||
|
||||
if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk"))
|
||||
readNextTrack (d, (int) chunkSize, createMatchingNoteOffs);
|
||||
|
||||
size -= chunkSize;
|
||||
d += chunkSize;
|
||||
}
|
||||
|
||||
const auto successful = (size == 0);
|
||||
|
||||
if (successful && fileType != nullptr)
|
||||
*fileType = header.fileType;
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
|
||||
{
|
||||
auto sequence = MidiFileHelpers::readTrack (data, size);
|
||||
|
||||
// sort so that we put all the note-offs before note-ons that have the same time
|
||||
std::stable_sort (sequence.list.begin(), sequence.list.end(),
|
||||
[] (const MidiMessageSequence::MidiEventHolder* a,
|
||||
const MidiMessageSequence::MidiEventHolder* b)
|
||||
{
|
||||
auto t1 = a->message.getTimeStamp();
|
||||
auto t2 = b->message.getTimeStamp();
|
||||
|
||||
if (t1 < t2) return true;
|
||||
if (t2 < t1) return false;
|
||||
|
||||
return a->message.isNoteOff() && b->message.isNoteOn();
|
||||
});
|
||||
|
||||
if (createMatchingNoteOffs)
|
||||
sequence.updateMatchedPairs();
|
||||
|
||||
addTrack (sequence);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiFile::convertTimestampTicksToSeconds()
|
||||
{
|
||||
MidiMessageSequence tempoEvents;
|
||||
findAllTempoEvents (tempoEvents);
|
||||
findAllTimeSigEvents (tempoEvents);
|
||||
|
||||
if (timeFormat != 0)
|
||||
{
|
||||
for (auto* ms : tracks)
|
||||
{
|
||||
for (int j = ms->getNumEvents(); --j >= 0;)
|
||||
{
|
||||
auto& m = ms->getEventPointer(j)->message;
|
||||
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MidiFile::writeTo (OutputStream& out, int midiFileType) const
|
||||
{
|
||||
jassert (midiFileType >= 0 && midiFileType <= 2);
|
||||
|
||||
if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false;
|
||||
if (! out.writeIntBigEndian (6)) return false;
|
||||
if (! out.writeShortBigEndian ((short) midiFileType)) return false;
|
||||
if (! out.writeShortBigEndian ((short) tracks.size())) return false;
|
||||
if (! out.writeShortBigEndian (timeFormat)) return false;
|
||||
|
||||
for (auto* ms : tracks)
|
||||
if (! writeTrack (out, *ms))
|
||||
return false;
|
||||
|
||||
out.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const
|
||||
{
|
||||
MemoryOutputStream out;
|
||||
|
||||
int lastTick = 0;
|
||||
uint8 lastStatusByte = 0;
|
||||
bool endOfTrackEventWritten = false;
|
||||
|
||||
for (int i = 0; i < ms.getNumEvents(); ++i)
|
||||
{
|
||||
auto& mm = ms.getEventPointer(i)->message;
|
||||
|
||||
if (mm.isEndOfTrackMetaEvent())
|
||||
endOfTrackEventWritten = true;
|
||||
|
||||
auto tick = roundToInt (mm.getTimeStamp());
|
||||
auto delta = jmax (0, tick - lastTick);
|
||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
|
||||
lastTick = tick;
|
||||
|
||||
auto* data = mm.getRawData();
|
||||
auto dataSize = mm.getRawDataSize();
|
||||
auto statusByte = data[0];
|
||||
|
||||
if (statusByte == lastStatusByte
|
||||
&& (statusByte & 0xf0) != 0xf0
|
||||
&& dataSize > 1
|
||||
&& i > 0)
|
||||
{
|
||||
++data;
|
||||
--dataSize;
|
||||
}
|
||||
else if (statusByte == 0xf0) // Write sysex message with length bytes.
|
||||
{
|
||||
out.writeByte ((char) statusByte);
|
||||
|
||||
++data;
|
||||
--dataSize;
|
||||
|
||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize);
|
||||
}
|
||||
|
||||
out.write (data, (size_t) dataSize);
|
||||
lastStatusByte = statusByte;
|
||||
}
|
||||
|
||||
if (! endOfTrackEventWritten)
|
||||
{
|
||||
out.writeByte (0); // (tick delta)
|
||||
auto m = MidiMessage::endOfTrack();
|
||||
out.write (m.getRawData(), (size_t) m.getRawDataSize());
|
||||
}
|
||||
|
||||
if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false;
|
||||
if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false;
|
||||
|
||||
mainOut << out;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct MidiFileTest : public UnitTest
|
||||
{
|
||||
MidiFileTest()
|
||||
: UnitTest ("MidiFile", UnitTestCategories::midi)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("ReadTrack respects running status");
|
||||
{
|
||||
const auto sequence = parseSequence ([] (OutputStream& os)
|
||||
{
|
||||
MidiFileHelpers::writeVariableLengthInt (os, 100);
|
||||
writeBytes (os, { 0x90, 0x40, 0x40 });
|
||||
MidiFileHelpers::writeVariableLengthInt (os, 200);
|
||||
writeBytes (os, { 0x40, 0x40 });
|
||||
MidiFileHelpers::writeVariableLengthInt (os, 300);
|
||||
writeBytes (os, { 0xff, 0x2f, 0x00 });
|
||||
});
|
||||
|
||||
expectEquals (sequence.getNumEvents(), 3);
|
||||
expect (sequence.getEventPointer (0)->message.isNoteOn());
|
||||
expect (sequence.getEventPointer (1)->message.isNoteOn());
|
||||
expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent());
|
||||
}
|
||||
|
||||
beginTest ("ReadTrack returns available messages if input is truncated");
|
||||
{
|
||||
{
|
||||
const auto sequence = parseSequence ([] (OutputStream& os)
|
||||
{
|
||||
// Incomplete delta time
|
||||
writeBytes (os, { 0xff });
|
||||
});
|
||||
|
||||
expectEquals (sequence.getNumEvents(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
const auto sequence = parseSequence ([] (OutputStream& os)
|
||||
{
|
||||
// Complete delta with no following event
|
||||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff);
|
||||
});
|
||||
|
||||
expectEquals (sequence.getNumEvents(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
const auto sequence = parseSequence ([] (OutputStream& os)
|
||||
{
|
||||
// Complete delta with malformed following event
|
||||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff);
|
||||
writeBytes (os, { 0x90, 0x40 });
|
||||
});
|
||||
|
||||
expectEquals (sequence.getNumEvents(), 1);
|
||||
expect (sequence.getEventPointer (0)->message.isNoteOff());
|
||||
expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40);
|
||||
expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Header parsing works");
|
||||
{
|
||||
{
|
||||
// No data
|
||||
const auto header = parseHeader ([] (OutputStream&) {});
|
||||
expect (! header.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid initial byte
|
||||
const auto header = parseHeader ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 0xff });
|
||||
});
|
||||
|
||||
expect (! header.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Type block, but no header data
|
||||
const auto header = parseHeader ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd' });
|
||||
});
|
||||
|
||||
expect (! header.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// We (ll-formed header, but track type is 0 and channels != 1
|
||||
const auto header = parseHeader ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 });
|
||||
});
|
||||
|
||||
expect (! header.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Well-formed header, but track type is 5
|
||||
const auto header = parseHeader ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 });
|
||||
});
|
||||
|
||||
expect (! header.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Well-formed header
|
||||
const auto header = parseHeader ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 });
|
||||
});
|
||||
|
||||
expect (header.valid);
|
||||
|
||||
expectEquals (header.value.fileType, (short) 1);
|
||||
expectEquals (header.value.numberOfTracks, (short) 16);
|
||||
expectEquals (header.value.timeFormat, (short) 1);
|
||||
expectEquals ((int) header.value.bytesRead, 14);
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Read from stream");
|
||||
{
|
||||
{
|
||||
// Empty input
|
||||
const auto file = parseFile ([] (OutputStream&) {});
|
||||
expect (! file.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Malformed header
|
||||
const auto file = parseFile ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd' });
|
||||
});
|
||||
|
||||
expect (! file.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Header, no channels
|
||||
const auto file = parseFile ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 });
|
||||
});
|
||||
|
||||
expect (file.valid);
|
||||
expectEquals (file.value.getNumTracks(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Header, one malformed channel
|
||||
const auto file = parseFile ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
|
||||
writeBytes (os, { 'M', 'T', 'r', '?' });
|
||||
});
|
||||
|
||||
expect (! file.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Header, one channel with malformed message
|
||||
const auto file = parseFile ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
|
||||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff });
|
||||
});
|
||||
|
||||
expect (file.valid);
|
||||
expectEquals (file.value.getNumTracks(), 1);
|
||||
expectEquals (file.value.getTrack (0)->getNumEvents(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Header, one channel with incorrect length message
|
||||
const auto file = parseFile ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
|
||||
writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff });
|
||||
});
|
||||
|
||||
expect (! file.valid);
|
||||
}
|
||||
|
||||
{
|
||||
// Header, one channel, all well-formed
|
||||
const auto file = parseFile ([] (OutputStream& os)
|
||||
{
|
||||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 });
|
||||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 });
|
||||
|
||||
MidiFileHelpers::writeVariableLengthInt (os, 0x0f);
|
||||
writeBytes (os, { 0x80, 0x00, 0x00 });
|
||||
});
|
||||
|
||||
expect (file.valid);
|
||||
expectEquals (file.value.getNumTracks(), 1);
|
||||
|
||||
auto& track = *file.value.getTrack (0);
|
||||
expectEquals (track.getNumEvents(), 1);
|
||||
expect (track.getEventPointer (0)->message.isNoteOff());
|
||||
expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static MidiMessageSequence parseSequence (Fn&& fn)
|
||||
{
|
||||
MemoryOutputStream os;
|
||||
fn (os);
|
||||
|
||||
return MidiFileHelpers::readTrack (reinterpret_cast<const uint8*> (os.getData()),
|
||||
(int) os.getDataSize());
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static MidiFileHelpers::Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn)
|
||||
{
|
||||
MemoryOutputStream os;
|
||||
fn (os);
|
||||
|
||||
return MidiFileHelpers::parseMidiHeader (reinterpret_cast<const uint8*> (os.getData()),
|
||||
os.getDataSize());
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static MidiFileHelpers::Optional<MidiFile> parseFile (Fn&& fn)
|
||||
{
|
||||
MemoryOutputStream os;
|
||||
fn (os);
|
||||
|
||||
MemoryInputStream is (os.getData(), os.getDataSize(), false);
|
||||
MidiFile mf;
|
||||
|
||||
int fileType = 0;
|
||||
|
||||
if (mf.readFrom (is, true, &fileType))
|
||||
return mf;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static void writeBytes (OutputStream& os, const std::vector<uint8>& bytes)
|
||||
{
|
||||
for (const auto& byte : bytes)
|
||||
os.writeByte ((char) byte);
|
||||
}
|
||||
};
|
||||
|
||||
static MidiFileTest midiFileTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
201
deps/juce/modules/juce_audio_basics/midi/juce_MidiFile.h
vendored
Normal file
201
deps/juce/modules/juce_audio_basics/midi/juce_MidiFile.h
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Reads/writes standard midi format files.
|
||||
|
||||
To read a midi file, create a MidiFile object and call its readFrom() method. You
|
||||
can then get the individual midi tracks from it using the getTrack() method.
|
||||
|
||||
To write a file, create a MidiFile object, add some MidiMessageSequence objects
|
||||
to it using the addTrack() method, and then call its writeTo() method to stream
|
||||
it out.
|
||||
|
||||
@see MidiMessageSequence
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiFile
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty MidiFile object. */
|
||||
MidiFile();
|
||||
|
||||
/** Destructor. */
|
||||
~MidiFile();
|
||||
|
||||
/** Creates a copy of another MidiFile. */
|
||||
MidiFile (const MidiFile&);
|
||||
|
||||
/** Copies from another MidiFile object */
|
||||
MidiFile& operator= (const MidiFile&);
|
||||
|
||||
/** Creates a copy of another MidiFile. */
|
||||
MidiFile (MidiFile&&);
|
||||
|
||||
/** Copies from another MidiFile object */
|
||||
MidiFile& operator= (MidiFile&&);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of tracks in the file.
|
||||
@see getTrack, addTrack
|
||||
*/
|
||||
int getNumTracks() const noexcept;
|
||||
|
||||
/** Returns a pointer to one of the tracks in the file.
|
||||
@returns a pointer to the track, or nullptr if the index is out-of-range
|
||||
@see getNumTracks, addTrack
|
||||
*/
|
||||
const MidiMessageSequence* getTrack (int index) const noexcept;
|
||||
|
||||
/** Adds a midi track to the file.
|
||||
This will make its own internal copy of the sequence that is passed-in.
|
||||
@see getNumTracks, getTrack
|
||||
*/
|
||||
void addTrack (const MidiMessageSequence& trackSequence);
|
||||
|
||||
/** Removes all midi tracks from the file.
|
||||
@see getNumTracks
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/** Returns the raw time format code that will be written to a stream.
|
||||
|
||||
After reading a midi file, this method will return the time-format that
|
||||
was read from the file's header. It can be changed using the setTicksPerQuarterNote()
|
||||
or setSmpteTimeFormat() methods.
|
||||
|
||||
If the value returned is positive, it indicates the number of midi ticks
|
||||
per quarter-note - see setTicksPerQuarterNote().
|
||||
|
||||
It it's negative, the upper byte indicates the frames-per-second (but negative), and
|
||||
the lower byte is the number of ticks per frame - see setSmpteTimeFormat().
|
||||
*/
|
||||
short getTimeFormat() const noexcept;
|
||||
|
||||
/** Sets the time format to use when this file is written to a stream.
|
||||
|
||||
If this is called, the file will be written as bars/beats using the
|
||||
specified resolution, rather than SMPTE absolute times, as would be
|
||||
used if setSmpteTimeFormat() had been called instead.
|
||||
|
||||
@param ticksPerQuarterNote e.g. 96, 960
|
||||
@see setSmpteTimeFormat
|
||||
*/
|
||||
void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept;
|
||||
|
||||
/** Sets the time format to use when this file is written to a stream.
|
||||
|
||||
If this is called, the file will be written using absolute times, rather
|
||||
than bars/beats as would be the case if setTicksPerBeat() had been called
|
||||
instead.
|
||||
|
||||
@param framesPerSecond must be 24, 25, 29 or 30
|
||||
@param subframeResolution the sub-second resolution, e.g. 4 (midi time code),
|
||||
8, 10, 80 (SMPTE bit resolution), or 100. For millisecond
|
||||
timing, setSmpteTimeFormat (25, 40)
|
||||
@see setTicksPerBeat
|
||||
*/
|
||||
void setSmpteTimeFormat (int framesPerSecond,
|
||||
int subframeResolution) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Makes a list of all the tempo-change meta-events from all tracks in the midi file.
|
||||
Useful for finding the positions of all the tempo changes in a file.
|
||||
@param tempoChangeEvents a list to which all the events will be added
|
||||
*/
|
||||
void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const;
|
||||
|
||||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file.
|
||||
Useful for finding the positions of all the tempo changes in a file.
|
||||
@param timeSigEvents a list to which all the events will be added
|
||||
*/
|
||||
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const;
|
||||
|
||||
/** Makes a list of all the time-signature meta-events from all tracks in the midi file.
|
||||
@param keySigEvents a list to which all the events will be added
|
||||
*/
|
||||
void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const;
|
||||
|
||||
/** Returns the latest timestamp in any of the tracks.
|
||||
(Useful for finding the length of the file).
|
||||
*/
|
||||
double getLastTimestamp() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Reads a midi file format stream.
|
||||
|
||||
After calling this, you can get the tracks that were read from the file by using the
|
||||
getNumTracks() and getTrack() methods.
|
||||
|
||||
The timestamps of the midi events in the tracks will represent their positions in
|
||||
terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds()
|
||||
method.
|
||||
|
||||
@param sourceStream the source stream
|
||||
@param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will
|
||||
be automatically added at the end of the file by calling
|
||||
MidiMessageSequence::updateMatchedPairs on each track.
|
||||
@param midiFileType if not nullptr, the integer at this address will be set
|
||||
to 0, 1, or 2 depending on the type of the midi file
|
||||
|
||||
@returns true if the stream was read successfully
|
||||
*/
|
||||
bool readFrom (InputStream& sourceStream,
|
||||
bool createMatchingNoteOffs = true,
|
||||
int* midiFileType = nullptr);
|
||||
|
||||
/** Writes the midi tracks as a standard midi file.
|
||||
The midiFileType value is written as the file's format type, which can be 0, 1
|
||||
or 2 - see the midi file spec for more info about that.
|
||||
|
||||
@param destStream the destination stream
|
||||
@param midiFileType the type of midi file
|
||||
|
||||
@returns true if the operation succeeded.
|
||||
*/
|
||||
bool writeTo (OutputStream& destStream, int midiFileType = 1) const;
|
||||
|
||||
/** Converts the timestamp of all the midi events from midi ticks to seconds.
|
||||
|
||||
This will use the midi time format and tempo/time signature info in the
|
||||
tracks to convert all the timestamps to absolute values in seconds.
|
||||
*/
|
||||
void convertTimestampTicksToSeconds();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<MidiMessageSequence> tracks;
|
||||
short timeFormat;
|
||||
|
||||
void readNextTrack (const uint8*, int, bool);
|
||||
bool writeTrack (OutputStream&, const MidiMessageSequence&) const;
|
||||
|
||||
JUCE_LEAK_DETECTOR (MidiFile)
|
||||
};
|
||||
|
||||
} // namespace juce
|
177
deps/juce/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
vendored
Normal file
177
deps/juce/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
MidiKeyboardState::MidiKeyboardState()
|
||||
{
|
||||
zerostruct (noteStates);
|
||||
}
|
||||
|
||||
MidiKeyboardState::~MidiKeyboardState()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardState::reset()
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
zerostruct (noteStates);
|
||||
eventsToAdd.clear();
|
||||
}
|
||||
|
||||
bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept
|
||||
{
|
||||
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||
|
||||
return isPositiveAndBelow (n, 128)
|
||||
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0;
|
||||
}
|
||||
|
||||
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept
|
||||
{
|
||||
return isPositiveAndBelow (n, 128)
|
||||
&& (noteStates[n] & midiChannelMask) != 0;
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||
{
|
||||
jassert (midiChannel > 0 && midiChannel <= 16);
|
||||
jassert (isPositiveAndBelow (midiNoteNumber, 128));
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (isPositiveAndBelow (midiNoteNumber, 128))
|
||||
{
|
||||
const int timeNow = (int) Time::getMillisecondCounter();
|
||||
eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow);
|
||||
eventsToAdd.clear (0, timeNow - 500);
|
||||
|
||||
noteOnInternal (midiChannel, midiNoteNumber, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||
{
|
||||
if (isPositiveAndBelow (midiNoteNumber, 128))
|
||||
{
|
||||
noteStates[midiNoteNumber] = static_cast<uint16> (noteStates[midiNoteNumber] | (1 << (midiChannel - 1)));
|
||||
listeners.call ([&] (Listener& l) { l.handleNoteOn (this, midiChannel, midiNoteNumber, velocity); });
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||
{
|
||||
const int timeNow = (int) Time::getMillisecondCounter();
|
||||
eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow);
|
||||
eventsToAdd.clear (0, timeNow - 500);
|
||||
|
||||
noteOffInternal (midiChannel, midiNoteNumber, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber, const float velocity)
|
||||
{
|
||||
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||
{
|
||||
noteStates[midiNoteNumber] = static_cast<uint16> (noteStates[midiNoteNumber] & ~(1 << (midiChannel - 1)));
|
||||
listeners.call ([&] (Listener& l) { l.handleNoteOff (this, midiChannel, midiNoteNumber, velocity); });
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::allNotesOff (const int midiChannel)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (midiChannel <= 0)
|
||||
{
|
||||
for (int i = 1; i <= 16; ++i)
|
||||
allNotesOff (i);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 128; ++i)
|
||||
noteOff (midiChannel, i, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message)
|
||||
{
|
||||
if (message.isNoteOn())
|
||||
{
|
||||
noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
|
||||
}
|
||||
else if (message.isNoteOff())
|
||||
{
|
||||
noteOffInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity());
|
||||
}
|
||||
else if (message.isAllNotesOff())
|
||||
{
|
||||
for (int i = 0; i < 128; ++i)
|
||||
noteOffInternal (message.getChannel(), i, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer,
|
||||
const int startSample,
|
||||
const int numSamples,
|
||||
const bool injectIndirectEvents)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
for (const auto metadata : buffer)
|
||||
processNextMidiEvent (metadata.getMessage());
|
||||
|
||||
if (injectIndirectEvents)
|
||||
{
|
||||
const int firstEventToAdd = eventsToAdd.getFirstEventTime();
|
||||
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd);
|
||||
|
||||
for (const auto metadata : eventsToAdd)
|
||||
{
|
||||
const auto pos = jlimit (0, numSamples - 1, roundToInt ((metadata.samplePosition - firstEventToAdd) * scaleFactor));
|
||||
buffer.addEvent (metadata.getMessage(), startSample + pos);
|
||||
}
|
||||
}
|
||||
|
||||
eventsToAdd.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardState::addListener (Listener* listener)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void MidiKeyboardState::removeListener (Listener* listener)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
} // namespace juce
|
196
deps/juce/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h
vendored
Normal file
196
deps/juce/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h
vendored
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a piano keyboard, keeping track of which keys are currently pressed.
|
||||
|
||||
This object can parse a stream of midi events, using them to update its idea
|
||||
of which keys are pressed for each individual midi channel.
|
||||
|
||||
When keys go up or down, it can broadcast these events to listener objects.
|
||||
|
||||
It also allows key up/down events to be triggered with its noteOn() and noteOff()
|
||||
methods, and midi messages for these events will be merged into the
|
||||
midi stream that gets processed by processNextMidiBuffer().
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiKeyboardState
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MidiKeyboardState();
|
||||
~MidiKeyboardState();
|
||||
|
||||
//==============================================================================
|
||||
/** Resets the state of the object.
|
||||
|
||||
All internal data for all the channels is reset, but no events are sent as a
|
||||
result.
|
||||
|
||||
If you want to release any keys that are currently down, and to send out note-up
|
||||
midi messages for this, use the allNotesOff() method instead.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/** Returns true if the given midi key is currently held down for the given midi channel.
|
||||
|
||||
The channel number must be between 1 and 16. If you want to see if any notes are
|
||||
on for a range of channels, use the isNoteOnForChannels() method.
|
||||
*/
|
||||
bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept;
|
||||
|
||||
/** Returns true if the given midi key is currently held down on any of a set of midi channels.
|
||||
|
||||
The channel mask has a bit set for each midi channel you want to test for - bit
|
||||
0 = midi channel 1, bit 1 = midi channel 2, etc.
|
||||
|
||||
If a note is on for at least one of the specified channels, this returns true.
|
||||
*/
|
||||
bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept;
|
||||
|
||||
/** Turns a specified note on.
|
||||
|
||||
This will cause a suitable midi note-on event to be injected into the midi buffer during the
|
||||
next call to processNextMidiBuffer().
|
||||
|
||||
It will also trigger a synchronous callback to the listeners to tell them that the key has
|
||||
gone down.
|
||||
*/
|
||||
void noteOn (int midiChannel, int midiNoteNumber, float velocity);
|
||||
|
||||
/** Turns a specified note off.
|
||||
|
||||
This will cause a suitable midi note-off event to be injected into the midi buffer during the
|
||||
next call to processNextMidiBuffer().
|
||||
|
||||
It will also trigger a synchronous callback to the listeners to tell them that the key has
|
||||
gone up.
|
||||
|
||||
But if the note isn't actually down for the given channel, this method will in fact do nothing.
|
||||
*/
|
||||
void noteOff (int midiChannel, int midiNoteNumber, float velocity);
|
||||
|
||||
/** This will turn off any currently-down notes for the given midi channel.
|
||||
|
||||
If you pass 0 for the midi channel, it will in fact turn off all notes on all channels.
|
||||
|
||||
Calling this method will make calls to noteOff(), so can trigger synchronous callbacks
|
||||
and events being added to the midi stream.
|
||||
*/
|
||||
void allNotesOff (int midiChannel);
|
||||
|
||||
//==============================================================================
|
||||
/** Looks at a key-up/down event and uses it to update the state of this object.
|
||||
|
||||
To process a buffer full of midi messages, use the processNextMidiBuffer() method
|
||||
instead.
|
||||
*/
|
||||
void processNextMidiEvent (const MidiMessage& message);
|
||||
|
||||
/** Scans a midi stream for up/down events and adds its own events to it.
|
||||
|
||||
This will look for any up/down events and use them to update the internal state,
|
||||
synchronously making suitable callbacks to the listeners.
|
||||
|
||||
If injectIndirectEvents is true, then midi events to produce the recent noteOn()
|
||||
and noteOff() calls will be added into the buffer.
|
||||
|
||||
Only the section of the buffer whose timestamps are between startSample and
|
||||
(startSample + numSamples) will be affected, and any events added will be placed
|
||||
between these times.
|
||||
|
||||
If you're going to use this method, you'll need to keep calling it regularly for
|
||||
it to work satisfactorily.
|
||||
|
||||
To process a single midi event at a time, use the processNextMidiEvent() method
|
||||
instead.
|
||||
*/
|
||||
void processNextMidiBuffer (MidiBuffer& buffer,
|
||||
int startSample,
|
||||
int numSamples,
|
||||
bool injectIndirectEvents);
|
||||
|
||||
//==============================================================================
|
||||
/** Receives events from a MidiKeyboardState object. */
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
virtual ~Listener() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Called when one of the MidiKeyboardState's keys is pressed.
|
||||
|
||||
This will be called synchronously when the state is either processing a
|
||||
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
|
||||
when a note is being played with its MidiKeyboardState::noteOn() method.
|
||||
|
||||
Note that this callback could happen from an audio callback thread, so be
|
||||
careful not to block, and avoid any UI activity in the callback.
|
||||
*/
|
||||
virtual void handleNoteOn (MidiKeyboardState* source,
|
||||
int midiChannel, int midiNoteNumber, float velocity) = 0;
|
||||
|
||||
/** Called when one of the MidiKeyboardState's keys is released.
|
||||
|
||||
This will be called synchronously when the state is either processing a
|
||||
buffer in its MidiKeyboardState::processNextMidiBuffer() method, or
|
||||
when a note is being played with its MidiKeyboardState::noteOff() method.
|
||||
|
||||
Note that this callback could happen from an audio callback thread, so be
|
||||
careful not to block, and avoid any UI activity in the callback.
|
||||
*/
|
||||
virtual void handleNoteOff (MidiKeyboardState* source,
|
||||
int midiChannel, int midiNoteNumber, float velocity) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener for callbacks when keys go up or down.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
CriticalSection lock;
|
||||
std::atomic<uint16> noteStates[128];
|
||||
MidiBuffer eventsToAdd;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity);
|
||||
void noteOffInternal (int midiChannel, int midiNoteNumber, float velocity);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState)
|
||||
};
|
||||
|
||||
using MidiKeyboardStateListener = MidiKeyboardState::Listener;
|
||||
|
||||
} // namespace juce
|
1331
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessage.cpp
vendored
Normal file
1331
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessage.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
986
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessage.h
vendored
Normal file
986
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessage.h
vendored
Normal file
@ -0,0 +1,986 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Encapsulates a MIDI message.
|
||||
|
||||
@see MidiMessageSequence, MidiOutput, MidiInput
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiMessage
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a 3-byte short midi message.
|
||||
|
||||
@param byte1 message byte 1
|
||||
@param byte2 message byte 2
|
||||
@param byte3 message byte 3
|
||||
@param timeStamp the time to give the midi message - this value doesn't
|
||||
use any particular units, so will be application-specific
|
||||
*/
|
||||
MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept;
|
||||
|
||||
/** Creates a 2-byte short midi message.
|
||||
|
||||
@param byte1 message byte 1
|
||||
@param byte2 message byte 2
|
||||
@param timeStamp the time to give the midi message - this value doesn't
|
||||
use any particular units, so will be application-specific
|
||||
*/
|
||||
MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept;
|
||||
|
||||
/** Creates a 1-byte short midi message.
|
||||
|
||||
@param byte1 message byte 1
|
||||
@param timeStamp the time to give the midi message - this value doesn't
|
||||
use any particular units, so will be application-specific
|
||||
*/
|
||||
MidiMessage (int byte1, double timeStamp = 0) noexcept;
|
||||
|
||||
/** Creates a midi message from a list of bytes. */
|
||||
template <typename... Data>
|
||||
MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes))
|
||||
{
|
||||
// this checks that the length matches the data..
|
||||
jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size);
|
||||
|
||||
const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast<uint8> (otherBytes)... };
|
||||
memcpy (allocateSpace (size), data, (size_t) size);
|
||||
}
|
||||
|
||||
|
||||
/** Creates a midi message from a block of data. */
|
||||
MidiMessage (const void* data, int numBytes, double timeStamp = 0);
|
||||
|
||||
/** Reads the next midi message from some data.
|
||||
|
||||
This will read as many bytes from a data stream as it needs to make a
|
||||
complete message, and will return the number of bytes it used. This lets
|
||||
you read a sequence of midi messages from a file or stream.
|
||||
|
||||
@param data the data to read from
|
||||
@param maxBytesToUse the maximum number of bytes it's allowed to read
|
||||
@param numBytesUsed returns the number of bytes that were actually needed
|
||||
@param lastStatusByte in a sequence of midi messages, the initial byte
|
||||
can be dropped from a message if it's the same as the
|
||||
first byte of the previous message, so this lets you
|
||||
supply the byte to use if the first byte of the message
|
||||
has in fact been dropped.
|
||||
@param timeStamp the time to give the midi message - this value doesn't
|
||||
use any particular units, so will be application-specific
|
||||
@param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether
|
||||
to expect the data to begin with a variable-length
|
||||
field indicating its size
|
||||
*/
|
||||
MidiMessage (const void* data, int maxBytesToUse,
|
||||
int& numBytesUsed, uint8 lastStatusByte,
|
||||
double timeStamp = 0,
|
||||
bool sysexHasEmbeddedLength = true);
|
||||
|
||||
/** Creates an empty sysex message.
|
||||
|
||||
Since the MidiMessage has to contain a valid message, this default constructor
|
||||
just initialises it with an empty sysex message.
|
||||
*/
|
||||
MidiMessage() noexcept;
|
||||
|
||||
/** Creates a copy of another midi message. */
|
||||
MidiMessage (const MidiMessage&);
|
||||
|
||||
/** Creates a copy of another midi message, with a different timestamp. */
|
||||
MidiMessage (const MidiMessage&, double newTimeStamp);
|
||||
|
||||
/** Destructor. */
|
||||
~MidiMessage() noexcept;
|
||||
|
||||
/** Copies this message from another one. */
|
||||
MidiMessage& operator= (const MidiMessage& other);
|
||||
|
||||
/** Move constructor */
|
||||
MidiMessage (MidiMessage&&) noexcept;
|
||||
|
||||
/** Move assignment operator */
|
||||
MidiMessage& operator= (MidiMessage&&) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a pointer to the raw midi data.
|
||||
@see getRawDataSize
|
||||
*/
|
||||
const uint8* getRawData() const noexcept { return getData(); }
|
||||
|
||||
/** Returns the number of bytes of data in the message.
|
||||
@see getRawData
|
||||
*/
|
||||
int getRawDataSize() const noexcept { return size; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a human-readable description of the midi message as a string,
|
||||
for example "Note On C#3 Velocity 120 Channel 1".
|
||||
*/
|
||||
String getDescription() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the timestamp associated with this message.
|
||||
|
||||
The exact meaning of this time and its units will vary, as messages are used in
|
||||
a variety of different contexts.
|
||||
|
||||
If you're getting the message from a midi file, this could be a time in seconds, or
|
||||
a number of ticks - see MidiFile::convertTimestampTicksToSeconds().
|
||||
|
||||
If the message is being used in a MidiBuffer, it might indicate the number of
|
||||
audio samples from the start of the buffer.
|
||||
|
||||
If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage()
|
||||
for details of the way that it initialises this value.
|
||||
|
||||
@see setTimeStamp, addToTimeStamp
|
||||
*/
|
||||
double getTimeStamp() const noexcept { return timeStamp; }
|
||||
|
||||
/** Changes the message's associated timestamp.
|
||||
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
|
||||
@see addToTimeStamp, getTimeStamp
|
||||
*/
|
||||
void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; }
|
||||
|
||||
/** Adds a value to the message's timestamp.
|
||||
The units for the timestamp will be application-specific.
|
||||
*/
|
||||
void addToTimeStamp (double delta) noexcept { timeStamp += delta; }
|
||||
|
||||
/** Return a copy of this message with a new timestamp.
|
||||
The units for the timestamp will be application-specific - see the notes for getTimeStamp().
|
||||
*/
|
||||
MidiMessage withTimeStamp (double newTimestamp) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the midi channel associated with the message.
|
||||
|
||||
@returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g.
|
||||
if it's a sysex)
|
||||
@see isForChannel, setChannel
|
||||
*/
|
||||
int getChannel() const noexcept;
|
||||
|
||||
/** Returns true if the message applies to the given midi channel.
|
||||
|
||||
@param channelNumber the channel number to look for, in the range 1 to 16
|
||||
@see getChannel, setChannel
|
||||
*/
|
||||
bool isForChannel (int channelNumber) const noexcept;
|
||||
|
||||
/** Changes the message's midi channel.
|
||||
This won't do anything for non-channel messages like sysexes.
|
||||
@param newChannelNumber the channel number to change it to, in the range 1 to 16
|
||||
*/
|
||||
void setChannel (int newChannelNumber) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a system-exclusive message.
|
||||
*/
|
||||
bool isSysEx() const noexcept;
|
||||
|
||||
/** Returns a pointer to the sysex data inside the message.
|
||||
If this event isn't a sysex event, it'll return 0.
|
||||
@see getSysExDataSize
|
||||
*/
|
||||
const uint8* getSysExData() const noexcept;
|
||||
|
||||
/** Returns the size of the sysex data.
|
||||
This value excludes the 0xf0 header byte and the 0xf7 at the end.
|
||||
@see getSysExData
|
||||
*/
|
||||
int getSysExDataSize() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this message is a 'key-down' event.
|
||||
|
||||
@param returnTrueForVelocity0 if true, then if this event is a note-on with
|
||||
velocity 0, it will still be considered to be a note-on and the
|
||||
method will return true. If returnTrueForVelocity0 is false, then
|
||||
if this is a note-on event with velocity 0, it'll be regarded as
|
||||
a note-off, and the method will return false
|
||||
|
||||
@see isNoteOff, getNoteNumber, getVelocity, noteOn
|
||||
*/
|
||||
bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept;
|
||||
|
||||
/** Creates a key-down message (using a floating-point velocity).
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param noteNumber the key number, 0 to 127
|
||||
@param velocity in the range 0 to 1.0
|
||||
@see isNoteOn
|
||||
*/
|
||||
static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept;
|
||||
|
||||
/** Creates a key-down message (using an integer velocity).
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param noteNumber the key number, 0 to 127
|
||||
@param velocity in the range 0 to 127
|
||||
@see isNoteOn
|
||||
*/
|
||||
static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept;
|
||||
|
||||
/** Returns true if this message is a 'key-up' event.
|
||||
|
||||
If returnTrueForNoteOnVelocity0 is true, then his will also return true
|
||||
for a note-on event with a velocity of 0.
|
||||
|
||||
@see isNoteOn, getNoteNumber, getVelocity, noteOff
|
||||
*/
|
||||
bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept;
|
||||
|
||||
/** Creates a key-up message.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param noteNumber the key number, 0 to 127
|
||||
@param velocity in the range 0 to 1.0
|
||||
@see isNoteOff
|
||||
*/
|
||||
static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept;
|
||||
|
||||
/** Creates a key-up message.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param noteNumber the key number, 0 to 127
|
||||
@param velocity in the range 0 to 127
|
||||
@see isNoteOff
|
||||
*/
|
||||
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept;
|
||||
|
||||
/** Creates a key-up message.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param noteNumber the key number, 0 to 127
|
||||
@see isNoteOff
|
||||
*/
|
||||
static MidiMessage noteOff (int channel, int noteNumber) noexcept;
|
||||
|
||||
/** Returns true if this message is a 'key-down' or 'key-up' event.
|
||||
|
||||
@see isNoteOn, isNoteOff
|
||||
*/
|
||||
bool isNoteOnOrOff() const noexcept;
|
||||
|
||||
/** Returns the midi note number for note-on and note-off messages.
|
||||
If the message isn't a note-on or off, the value returned is undefined.
|
||||
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber
|
||||
*/
|
||||
int getNoteNumber() const noexcept;
|
||||
|
||||
/** Changes the midi note number of a note-on or note-off message.
|
||||
If the message isn't a note on or off, this will do nothing.
|
||||
*/
|
||||
void setNoteNumber (int newNoteNumber) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the velocity of a note-on or note-off message.
|
||||
|
||||
The value returned will be in the range 0 to 127.
|
||||
If the message isn't a note-on or off event, it will return 0.
|
||||
|
||||
@see getFloatVelocity
|
||||
*/
|
||||
uint8 getVelocity() const noexcept;
|
||||
|
||||
/** Returns the velocity of a note-on or note-off message.
|
||||
|
||||
The value returned will be in the range 0 to 1.0
|
||||
If the message isn't a note-on or off event, it will return 0.
|
||||
|
||||
@see getVelocity, setVelocity
|
||||
*/
|
||||
float getFloatVelocity() const noexcept;
|
||||
|
||||
/** Changes the velocity of a note-on or note-off message.
|
||||
|
||||
If the message isn't a note on or off, this will do nothing.
|
||||
|
||||
@param newVelocity the new velocity, in the range 0 to 1.0
|
||||
@see getFloatVelocity, multiplyVelocity
|
||||
*/
|
||||
void setVelocity (float newVelocity) noexcept;
|
||||
|
||||
/** Multiplies the velocity of a note-on or note-off message by a given amount.
|
||||
|
||||
If the message isn't a note on or off, this will do nothing.
|
||||
|
||||
@param scaleFactor the value by which to multiply the velocity
|
||||
@see setVelocity
|
||||
*/
|
||||
void multiplyVelocity (float scaleFactor) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this message is a 'sustain pedal down' controller message. */
|
||||
bool isSustainPedalOn() const noexcept;
|
||||
/** Returns true if this message is a 'sustain pedal up' controller message. */
|
||||
bool isSustainPedalOff() const noexcept;
|
||||
|
||||
/** Returns true if this message is a 'sostenuto pedal down' controller message. */
|
||||
bool isSostenutoPedalOn() const noexcept;
|
||||
/** Returns true if this message is a 'sostenuto pedal up' controller message. */
|
||||
bool isSostenutoPedalOff() const noexcept;
|
||||
|
||||
/** Returns true if this message is a 'soft pedal down' controller message. */
|
||||
bool isSoftPedalOn() const noexcept;
|
||||
/** Returns true if this message is a 'soft pedal up' controller message. */
|
||||
bool isSoftPedalOff() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the message is a program (patch) change message.
|
||||
@see getProgramChangeNumber, getGMInstrumentName
|
||||
*/
|
||||
bool isProgramChange() const noexcept;
|
||||
|
||||
/** Returns the new program number of a program change message.
|
||||
If the message isn't a program change, the value returned is undefined.
|
||||
@see isProgramChange, getGMInstrumentName
|
||||
*/
|
||||
int getProgramChangeNumber() const noexcept;
|
||||
|
||||
/** Creates a program-change message.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param programNumber the midi program number, 0 to 127
|
||||
@see isProgramChange, getGMInstrumentName
|
||||
*/
|
||||
static MidiMessage programChange (int channel, int programNumber) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the message is a pitch-wheel move.
|
||||
@see getPitchWheelValue, pitchWheel
|
||||
*/
|
||||
bool isPitchWheel() const noexcept;
|
||||
|
||||
/** Returns the pitch wheel position from a pitch-wheel move message.
|
||||
|
||||
The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position.
|
||||
If called for messages which aren't pitch wheel events, the number returned will be
|
||||
nonsense.
|
||||
|
||||
@see isPitchWheel
|
||||
*/
|
||||
int getPitchWheelValue() const noexcept;
|
||||
|
||||
/** Creates a pitch-wheel move message.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param position the wheel position, in the range 0 to 16383
|
||||
@see isPitchWheel
|
||||
*/
|
||||
static MidiMessage pitchWheel (int channel, int position) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the message is an aftertouch event.
|
||||
|
||||
For aftertouch events, use the getNoteNumber() method to find out the key
|
||||
that it applies to, and getAfterTouchValue() to find out the amount. Use
|
||||
getChannel() to find out the channel.
|
||||
|
||||
@see getAftertouchValue, getNoteNumber
|
||||
*/
|
||||
bool isAftertouch() const noexcept;
|
||||
|
||||
/** Returns the amount of aftertouch from an aftertouch messages.
|
||||
|
||||
The value returned is in the range 0 to 127, and will be nonsense for messages
|
||||
other than aftertouch messages.
|
||||
|
||||
@see isAftertouch
|
||||
*/
|
||||
int getAfterTouchValue() const noexcept;
|
||||
|
||||
/** Creates an aftertouch message.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param noteNumber the key number, 0 to 127
|
||||
@param aftertouchAmount the amount of aftertouch, 0 to 127
|
||||
@see isAftertouch
|
||||
*/
|
||||
static MidiMessage aftertouchChange (int channel,
|
||||
int noteNumber,
|
||||
int aftertouchAmount) noexcept;
|
||||
|
||||
/** Returns true if the message is a channel-pressure change event.
|
||||
|
||||
This is like aftertouch, but common to the whole channel rather than a specific
|
||||
note. Use getChannelPressureValue() to find out the pressure, and getChannel()
|
||||
to find out the channel.
|
||||
|
||||
@see channelPressureChange
|
||||
*/
|
||||
bool isChannelPressure() const noexcept;
|
||||
|
||||
/** Returns the pressure from a channel pressure change message.
|
||||
|
||||
@returns the pressure, in the range 0 to 127
|
||||
@see isChannelPressure, channelPressureChange
|
||||
*/
|
||||
int getChannelPressureValue() const noexcept;
|
||||
|
||||
/** Creates a channel-pressure change event.
|
||||
|
||||
@param channel the midi channel: 1 to 16
|
||||
@param pressure the pressure, 0 to 127
|
||||
@see isChannelPressure
|
||||
*/
|
||||
static MidiMessage channelPressureChange (int channel, int pressure) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a midi controller message.
|
||||
|
||||
@see getControllerNumber, getControllerValue, controllerEvent
|
||||
*/
|
||||
bool isController() const noexcept;
|
||||
|
||||
/** Returns the controller number of a controller message.
|
||||
|
||||
The name of the controller can be looked up using the getControllerName() method.
|
||||
Note that the value returned is invalid for messages that aren't controller changes.
|
||||
|
||||
@see isController, getControllerName, getControllerValue
|
||||
*/
|
||||
int getControllerNumber() const noexcept;
|
||||
|
||||
/** Returns the controller value from a controller message.
|
||||
|
||||
A value 0 to 127 is returned to indicate the new controller position.
|
||||
Note that the value returned is invalid for messages that aren't controller changes.
|
||||
|
||||
@see isController, getControllerNumber
|
||||
*/
|
||||
int getControllerValue() const noexcept;
|
||||
|
||||
/** Returns true if this message is a controller message and if it has the specified
|
||||
controller type.
|
||||
*/
|
||||
bool isControllerOfType (int controllerType) const noexcept;
|
||||
|
||||
/** Creates a controller message.
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@param controllerType the type of controller
|
||||
@param value the controller value
|
||||
@see isController
|
||||
*/
|
||||
static MidiMessage controllerEvent (int channel,
|
||||
int controllerType,
|
||||
int value) noexcept;
|
||||
|
||||
/** Checks whether this message is an all-notes-off message.
|
||||
@see allNotesOff
|
||||
*/
|
||||
bool isAllNotesOff() const noexcept;
|
||||
|
||||
/** Checks whether this message is an all-sound-off message.
|
||||
@see allSoundOff
|
||||
*/
|
||||
bool isAllSoundOff() const noexcept;
|
||||
|
||||
/** Checks whether this message is a reset all controllers message.
|
||||
@see allControllerOff
|
||||
*/
|
||||
bool isResetAllControllers() const noexcept;
|
||||
|
||||
/** Creates an all-notes-off message.
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@see isAllNotesOff
|
||||
*/
|
||||
static MidiMessage allNotesOff (int channel) noexcept;
|
||||
|
||||
/** Creates an all-sound-off message.
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@see isAllSoundOff
|
||||
*/
|
||||
static MidiMessage allSoundOff (int channel) noexcept;
|
||||
|
||||
/** Creates an all-controllers-off message.
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
*/
|
||||
static MidiMessage allControllersOff (int channel) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this event is a meta-event.
|
||||
|
||||
Meta-events are things like tempo changes, track names, etc.
|
||||
|
||||
@see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent,
|
||||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
|
||||
isKeySignatureMetaEvent, isMidiChannelMetaEvent
|
||||
*/
|
||||
bool isMetaEvent() const noexcept;
|
||||
|
||||
/** Returns a meta-event's type number.
|
||||
|
||||
If the message isn't a meta-event, this will return -1.
|
||||
|
||||
@see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent,
|
||||
isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent,
|
||||
isKeySignatureMetaEvent, isMidiChannelMetaEvent
|
||||
*/
|
||||
int getMetaEventType() const noexcept;
|
||||
|
||||
/** Returns a pointer to the data in a meta-event.
|
||||
@see isMetaEvent, getMetaEventLength
|
||||
*/
|
||||
const uint8* getMetaEventData() const noexcept;
|
||||
|
||||
/** Returns the length of the data for a meta-event.
|
||||
@see isMetaEvent, getMetaEventData
|
||||
*/
|
||||
int getMetaEventLength() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a 'track' meta-event. */
|
||||
bool isTrackMetaEvent() const noexcept;
|
||||
|
||||
/** Returns true if this is an 'end-of-track' meta-event. */
|
||||
bool isEndOfTrackMetaEvent() const noexcept;
|
||||
|
||||
/** Creates an end-of-track meta-event.
|
||||
@see isEndOfTrackMetaEvent
|
||||
*/
|
||||
static MidiMessage endOfTrack() noexcept;
|
||||
|
||||
/** Returns true if this is an 'track name' meta-event.
|
||||
You can use the getTextFromTextMetaEvent() method to get the track's name.
|
||||
*/
|
||||
bool isTrackNameEvent() const noexcept;
|
||||
|
||||
/** Returns true if this is a 'text' meta-event.
|
||||
@see getTextFromTextMetaEvent
|
||||
*/
|
||||
bool isTextMetaEvent() const noexcept;
|
||||
|
||||
/** Returns the text from a text meta-event.
|
||||
@see isTextMetaEvent
|
||||
*/
|
||||
String getTextFromTextMetaEvent() const;
|
||||
|
||||
/** Creates a text meta-event. */
|
||||
static MidiMessage textMetaEvent (int type, StringRef text);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a 'tempo' meta-event.
|
||||
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote
|
||||
*/
|
||||
bool isTempoMetaEvent() const noexcept;
|
||||
|
||||
/** Returns the tick length from a tempo meta-event.
|
||||
|
||||
@param timeFormat the 16-bit time format value from the midi file's header.
|
||||
@returns the tick length (in seconds).
|
||||
@see isTempoMetaEvent
|
||||
*/
|
||||
double getTempoMetaEventTickLength (short timeFormat) const noexcept;
|
||||
|
||||
/** Calculates the seconds-per-quarter-note from a tempo meta-event.
|
||||
@see isTempoMetaEvent, getTempoMetaEventTickLength
|
||||
*/
|
||||
double getTempoSecondsPerQuarterNote() const noexcept;
|
||||
|
||||
/** Creates a tempo meta-event.
|
||||
@see isTempoMetaEvent
|
||||
*/
|
||||
static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a 'time-signature' meta-event.
|
||||
@see getTimeSignatureInfo
|
||||
*/
|
||||
bool isTimeSignatureMetaEvent() const noexcept;
|
||||
|
||||
/** Returns the time-signature values from a time-signature meta-event.
|
||||
@see isTimeSignatureMetaEvent
|
||||
*/
|
||||
void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept;
|
||||
|
||||
/** Creates a time-signature meta-event.
|
||||
@see isTimeSignatureMetaEvent
|
||||
*/
|
||||
static MidiMessage timeSignatureMetaEvent (int numerator, int denominator);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a 'key-signature' meta-event.
|
||||
@see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey
|
||||
*/
|
||||
bool isKeySignatureMetaEvent() const noexcept;
|
||||
|
||||
/** Returns the key from a key-signature meta-event.
|
||||
This method must only be called if isKeySignatureMetaEvent() is true.
|
||||
A positive number here indicates the number of sharps in the key signature,
|
||||
and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#,
|
||||
-2 = Bb + Eb
|
||||
@see isKeySignatureMetaEvent, isKeySignatureMajorKey
|
||||
*/
|
||||
int getKeySignatureNumberOfSharpsOrFlats() const noexcept;
|
||||
|
||||
/** Returns true if this key-signature event is major, or false if it's minor.
|
||||
This method must only be called if isKeySignatureMetaEvent() is true.
|
||||
*/
|
||||
bool isKeySignatureMajorKey() const noexcept;
|
||||
|
||||
/** Creates a key-signature meta-event.
|
||||
@param numberOfSharpsOrFlats if positive, this indicates the number of sharps
|
||||
in the key; if negative, the number of flats
|
||||
@param isMinorKey if true, the key is minor; if false, it is major
|
||||
@see isKeySignatureMetaEvent
|
||||
*/
|
||||
static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a 'channel' meta-event.
|
||||
|
||||
A channel meta-event specifies the midi channel that should be used
|
||||
for subsequent meta-events.
|
||||
|
||||
@see getMidiChannelMetaEventChannel
|
||||
*/
|
||||
bool isMidiChannelMetaEvent() const noexcept;
|
||||
|
||||
/** Returns the channel number from a channel meta-event.
|
||||
|
||||
@returns the channel, in the range 1 to 16.
|
||||
@see isMidiChannelMetaEvent
|
||||
*/
|
||||
int getMidiChannelMetaEventChannel() const noexcept;
|
||||
|
||||
/** Creates a midi channel meta-event.
|
||||
|
||||
@param channel the midi channel, in the range 1 to 16
|
||||
@see isMidiChannelMetaEvent
|
||||
*/
|
||||
static MidiMessage midiChannelMetaEvent (int channel) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is an active-sense message. */
|
||||
bool isActiveSense() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a midi start event.
|
||||
@see midiStart
|
||||
*/
|
||||
bool isMidiStart() const noexcept;
|
||||
|
||||
/** Creates a midi start event. */
|
||||
static MidiMessage midiStart() noexcept;
|
||||
|
||||
/** Returns true if this is a midi continue event.
|
||||
@see midiContinue
|
||||
*/
|
||||
bool isMidiContinue() const noexcept;
|
||||
|
||||
/** Creates a midi continue event. */
|
||||
static MidiMessage midiContinue() noexcept;
|
||||
|
||||
/** Returns true if this is a midi stop event.
|
||||
@see midiStop
|
||||
*/
|
||||
bool isMidiStop() const noexcept;
|
||||
|
||||
/** Creates a midi stop event. */
|
||||
static MidiMessage midiStop() noexcept;
|
||||
|
||||
/** Returns true if this is a midi clock event.
|
||||
@see midiClock, songPositionPointer
|
||||
*/
|
||||
bool isMidiClock() const noexcept;
|
||||
|
||||
/** Creates a midi clock event. */
|
||||
static MidiMessage midiClock() noexcept;
|
||||
|
||||
/** Returns true if this is a song-position-pointer message.
|
||||
@see getSongPositionPointerMidiBeat, songPositionPointer
|
||||
*/
|
||||
bool isSongPositionPointer() const noexcept;
|
||||
|
||||
/** Returns the midi beat-number of a song-position-pointer message.
|
||||
@see isSongPositionPointer, songPositionPointer
|
||||
*/
|
||||
int getSongPositionPointerMidiBeat() const noexcept;
|
||||
|
||||
/** Creates a song-position-pointer message.
|
||||
|
||||
The position is a number of midi beats from the start of the song, where 1 midi
|
||||
beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there
|
||||
are 4 midi beats in a quarter-note.
|
||||
|
||||
@see isSongPositionPointer, getSongPositionPointerMidiBeat
|
||||
*/
|
||||
static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a quarter-frame midi timecode message.
|
||||
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue
|
||||
*/
|
||||
bool isQuarterFrame() const noexcept;
|
||||
|
||||
/** Returns the sequence number of a quarter-frame midi timecode message.
|
||||
This will be a value between 0 and 7.
|
||||
@see isQuarterFrame, getQuarterFrameValue, quarterFrame
|
||||
*/
|
||||
int getQuarterFrameSequenceNumber() const noexcept;
|
||||
|
||||
/** Returns the value from a quarter-frame message.
|
||||
This will be the lower nybble of the message's data-byte, a value between 0 and 15
|
||||
*/
|
||||
int getQuarterFrameValue() const noexcept;
|
||||
|
||||
/** Creates a quarter-frame MTC message.
|
||||
|
||||
@param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte
|
||||
@param value a value 0 to 15 for the lower nybble of the message's data byte
|
||||
*/
|
||||
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept;
|
||||
|
||||
/** SMPTE timecode types.
|
||||
Used by the getFullFrameParameters() and fullFrame() methods.
|
||||
*/
|
||||
enum SmpteTimecodeType
|
||||
{
|
||||
fps24 = 0,
|
||||
fps25 = 1,
|
||||
fps30drop = 2,
|
||||
fps30 = 3
|
||||
};
|
||||
|
||||
/** Returns true if this is a full-frame midi timecode message. */
|
||||
bool isFullFrame() const noexcept;
|
||||
|
||||
/** Extracts the timecode information from a full-frame midi timecode message.
|
||||
|
||||
You should only call this on messages where you've used isFullFrame() to
|
||||
check that they're the right kind.
|
||||
*/
|
||||
void getFullFrameParameters (int& hours,
|
||||
int& minutes,
|
||||
int& seconds,
|
||||
int& frames,
|
||||
SmpteTimecodeType& timecodeType) const noexcept;
|
||||
|
||||
/** Creates a full-frame MTC message. */
|
||||
static MidiMessage fullFrame (int hours,
|
||||
int minutes,
|
||||
int seconds,
|
||||
int frames,
|
||||
SmpteTimecodeType timecodeType);
|
||||
|
||||
//==============================================================================
|
||||
/** Types of MMC command.
|
||||
|
||||
@see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand
|
||||
*/
|
||||
enum MidiMachineControlCommand
|
||||
{
|
||||
mmc_stop = 1,
|
||||
mmc_play = 2,
|
||||
mmc_deferredplay = 3,
|
||||
mmc_fastforward = 4,
|
||||
mmc_rewind = 5,
|
||||
mmc_recordStart = 6,
|
||||
mmc_recordStop = 7,
|
||||
mmc_pause = 9
|
||||
};
|
||||
|
||||
/** Checks whether this is an MMC message.
|
||||
If it is, you can use the getMidiMachineControlCommand() to find out its type.
|
||||
*/
|
||||
bool isMidiMachineControlMessage() const noexcept;
|
||||
|
||||
/** For an MMC message, this returns its type.
|
||||
|
||||
Make sure it's actually an MMC message with isMidiMachineControlMessage() before
|
||||
calling this method.
|
||||
*/
|
||||
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept;
|
||||
|
||||
/** Creates an MMC message. */
|
||||
static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command);
|
||||
|
||||
/** Checks whether this is an MMC "goto" message.
|
||||
If it is, the parameters passed-in are set to the time that the message contains.
|
||||
@see midiMachineControlGoto
|
||||
*/
|
||||
bool isMidiMachineControlGoto (int& hours,
|
||||
int& minutes,
|
||||
int& seconds,
|
||||
int& frames) const noexcept;
|
||||
|
||||
/** Creates an MMC "goto" message.
|
||||
This messages tells the device to go to a specific frame.
|
||||
@see isMidiMachineControlGoto
|
||||
*/
|
||||
static MidiMessage midiMachineControlGoto (int hours,
|
||||
int minutes,
|
||||
int seconds,
|
||||
int frames);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a master-volume change message.
|
||||
@param volume the volume, 0 to 1.0
|
||||
*/
|
||||
static MidiMessage masterVolume (float volume);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a system-exclusive message.
|
||||
The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7.
|
||||
*/
|
||||
static MidiMessage createSysExMessage (const void* sysexData,
|
||||
int dataSize);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** Reads a midi variable-length integer.
|
||||
|
||||
The `data` argument indicates the data to read the number from,
|
||||
and `numBytesUsed` is used as an out-parameter to indicate the number
|
||||
of bytes that were read.
|
||||
*/
|
||||
[[deprecated ("This signature has been deprecated in favour of the safer readVariableLengthValue.")]]
|
||||
static int readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept;
|
||||
#endif
|
||||
|
||||
/** Holds information about a variable-length value which was parsed
|
||||
from a stream of bytes.
|
||||
|
||||
A valid value requires that `bytesUsed` is greater than 0.
|
||||
*/
|
||||
struct VariableLengthValue
|
||||
{
|
||||
VariableLengthValue() = default;
|
||||
|
||||
VariableLengthValue (int valueIn, int bytesUsedIn)
|
||||
: value (valueIn), bytesUsed (bytesUsedIn) {}
|
||||
|
||||
bool isValid() const noexcept { return bytesUsed > 0; }
|
||||
|
||||
int value = 0;
|
||||
int bytesUsed = 0;
|
||||
};
|
||||
|
||||
/** Reads a midi variable-length integer, with protection against buffer overflow.
|
||||
|
||||
@param data the data to read the number from
|
||||
@param maxBytesToUse the number of bytes in the region following `data`
|
||||
@returns a struct containing the parsed value, and the number
|
||||
of bytes that were read. If parsing fails, both the
|
||||
`value` and `bytesUsed` fields will be set to 0 and
|
||||
`isValid()` will return false
|
||||
*/
|
||||
static VariableLengthValue readVariableLengthValue (const uint8* data,
|
||||
int maxBytesToUse) noexcept;
|
||||
|
||||
/** Based on the first byte of a short midi message, this uses a lookup table
|
||||
to return the message length (either 1, 2, or 3 bytes).
|
||||
|
||||
The value passed in must be 0x80 or higher.
|
||||
*/
|
||||
static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the name of a midi note number.
|
||||
|
||||
E.g "C", "D#", etc.
|
||||
|
||||
@param noteNumber the midi note number, 0 to 127
|
||||
@param useSharps if true, sharpened notes are used, e.g. "C#", otherwise
|
||||
they'll be flattened, e.g. "Db"
|
||||
@param includeOctaveNumber if true, the octave number will be appended to the string,
|
||||
e.g. "C#4"
|
||||
@param octaveNumForMiddleC if an octave number is being appended, this indicates the
|
||||
number that will be used for middle C's octave
|
||||
|
||||
@see getMidiNoteInHertz
|
||||
*/
|
||||
static String getMidiNoteName (int noteNumber,
|
||||
bool useSharps,
|
||||
bool includeOctaveNumber,
|
||||
int octaveNumForMiddleC);
|
||||
|
||||
/** Returns the frequency of a midi note number.
|
||||
|
||||
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch.
|
||||
@see getMidiNoteName
|
||||
*/
|
||||
static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept;
|
||||
|
||||
/** Returns true if the given midi note number is a black key. */
|
||||
static bool isMidiNoteBlack (int noteNumber) noexcept;
|
||||
|
||||
/** Returns the standard name of a GM instrument, or nullptr if unknown for this index.
|
||||
|
||||
@param midiInstrumentNumber the program number 0 to 127
|
||||
@see getProgramChangeNumber
|
||||
*/
|
||||
static const char* getGMInstrumentName (int midiInstrumentNumber);
|
||||
|
||||
/** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number.
|
||||
@param midiBankNumber the bank, 0 to 15
|
||||
*/
|
||||
static const char* getGMInstrumentBankName (int midiBankNumber);
|
||||
|
||||
/** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number.
|
||||
@param midiNoteNumber the key number, 35 to 81
|
||||
*/
|
||||
static const char* getRhythmInstrumentName (int midiNoteNumber);
|
||||
|
||||
/** Returns the name of a controller type number, or nullptr if unknown for this controller number.
|
||||
@see getControllerNumber
|
||||
*/
|
||||
static const char* getControllerName (int controllerNumber);
|
||||
|
||||
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */
|
||||
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept;
|
||||
|
||||
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */
|
||||
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones,
|
||||
float pitchbendRangeInSemitones) noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
union PackedData
|
||||
{
|
||||
uint8* allocatedData;
|
||||
uint8 asBytes[sizeof (uint8*)];
|
||||
};
|
||||
|
||||
PackedData packedData;
|
||||
double timeStamp = 0;
|
||||
int size;
|
||||
#endif
|
||||
|
||||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); }
|
||||
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; }
|
||||
uint8* allocateSpace (int);
|
||||
};
|
||||
|
||||
} // namespace juce
|
883
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
vendored
Normal file
883
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp
vendored
Normal file
@ -0,0 +1,883 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {}
|
||||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {}
|
||||
MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {}
|
||||
|
||||
//==============================================================================
|
||||
MidiMessageSequence::MidiMessageSequence()
|
||||
{
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other)
|
||||
{
|
||||
list.addCopiesOf (other.list);
|
||||
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
auto noteOffIndex = other.getIndexOfMatchingKeyUp (i);
|
||||
|
||||
if (noteOffIndex >= 0)
|
||||
list.getUnchecked(i)->noteOffObject = list.getUnchecked (noteOffIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
|
||||
{
|
||||
MidiMessageSequence otherCopy (other);
|
||||
swapWith (otherCopy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept
|
||||
: list (std::move (other.list))
|
||||
{
|
||||
}
|
||||
|
||||
MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept
|
||||
{
|
||||
list = std::move (other.list);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MidiMessageSequence::~MidiMessageSequence()
|
||||
{
|
||||
}
|
||||
|
||||
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
|
||||
{
|
||||
list.swapWith (other.list);
|
||||
}
|
||||
|
||||
void MidiMessageSequence::clear()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getNumEvents() const noexcept
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); }
|
||||
MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); }
|
||||
MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); }
|
||||
MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); }
|
||||
|
||||
double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept
|
||||
{
|
||||
if (auto* meh = list[index])
|
||||
if (auto* noteOff = meh->noteOffObject)
|
||||
return noteOff->message.getTimeStamp();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept
|
||||
{
|
||||
if (auto* meh = list[index])
|
||||
{
|
||||
if (auto* noteOff = meh->noteOffObject)
|
||||
{
|
||||
for (int i = index; i < list.size(); ++i)
|
||||
if (list.getUnchecked(i) == noteOff)
|
||||
return i;
|
||||
|
||||
jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept
|
||||
{
|
||||
return list.indexOf (event);
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept
|
||||
{
|
||||
auto numEvents = list.size();
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numEvents; ++i)
|
||||
if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp)
|
||||
break;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
double MidiMessageSequence::getStartTime() const noexcept
|
||||
{
|
||||
return getEventTime (0);
|
||||
}
|
||||
|
||||
double MidiMessageSequence::getEndTime() const noexcept
|
||||
{
|
||||
return getEventTime (list.size() - 1);
|
||||
}
|
||||
|
||||
double MidiMessageSequence::getEventTime (const int index) const noexcept
|
||||
{
|
||||
if (auto* meh = list[index])
|
||||
return meh->message.getTimeStamp();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment)
|
||||
{
|
||||
newEvent->message.addToTimeStamp (timeAdjustment);
|
||||
auto time = newEvent->message.getTimeStamp();
|
||||
int i;
|
||||
|
||||
for (i = list.size(); --i >= 0;)
|
||||
if (list.getUnchecked(i)->message.getTimeStamp() <= time)
|
||||
break;
|
||||
|
||||
list.insert (i + 1, newEvent);
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment)
|
||||
{
|
||||
return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment)
|
||||
{
|
||||
return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment);
|
||||
}
|
||||
|
||||
void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp)
|
||||
{
|
||||
if (isPositiveAndBelow (index, list.size()))
|
||||
{
|
||||
if (deleteMatchingNoteUp)
|
||||
deleteEvent (getIndexOfMatchingKeyUp (index), false);
|
||||
|
||||
list.remove (index);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment)
|
||||
{
|
||||
for (auto* m : other)
|
||||
{
|
||||
auto newOne = new MidiEventHolder (m->message);
|
||||
newOne->message.addToTimeStamp (timeAdjustment);
|
||||
list.add (newOne);
|
||||
}
|
||||
|
||||
sort();
|
||||
}
|
||||
|
||||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other,
|
||||
double timeAdjustment,
|
||||
double firstAllowableTime,
|
||||
double endOfAllowableDestTimes)
|
||||
{
|
||||
for (auto* m : other)
|
||||
{
|
||||
auto t = m->message.getTimeStamp() + timeAdjustment;
|
||||
|
||||
if (t >= firstAllowableTime && t < endOfAllowableDestTimes)
|
||||
{
|
||||
auto newOne = new MidiEventHolder (m->message);
|
||||
newOne->message.setTimeStamp (t);
|
||||
list.add (newOne);
|
||||
}
|
||||
}
|
||||
|
||||
sort();
|
||||
}
|
||||
|
||||
void MidiMessageSequence::sort() noexcept
|
||||
{
|
||||
std::stable_sort (list.begin(), list.end(),
|
||||
[] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); });
|
||||
}
|
||||
|
||||
void MidiMessageSequence::updateMatchedPairs() noexcept
|
||||
{
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
auto* meh = list.getUnchecked(i);
|
||||
auto& m1 = meh->message;
|
||||
|
||||
if (m1.isNoteOn())
|
||||
{
|
||||
meh->noteOffObject = nullptr;
|
||||
auto note = m1.getNoteNumber();
|
||||
auto chan = m1.getChannel();
|
||||
auto len = list.size();
|
||||
|
||||
for (int j = i + 1; j < len; ++j)
|
||||
{
|
||||
auto* meh2 = list.getUnchecked(j);
|
||||
auto& m = meh2->message;
|
||||
|
||||
if (m.getNoteNumber() == note && m.getChannel() == chan)
|
||||
{
|
||||
if (m.isNoteOff())
|
||||
{
|
||||
meh->noteOffObject = meh2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m.isNoteOn())
|
||||
{
|
||||
auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note));
|
||||
list.insert (j, newEvent);
|
||||
newEvent->message.setTimeStamp (m.getTimeStamp());
|
||||
meh->noteOffObject = newEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiMessageSequence::addTimeToMessages (double delta) noexcept
|
||||
{
|
||||
if (delta != 0)
|
||||
for (auto* m : list)
|
||||
m->message.addToTimeStamp (delta);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
|
||||
MidiMessageSequence& destSequence,
|
||||
const bool alsoIncludeMetaEvents) const
|
||||
{
|
||||
for (auto* meh : list)
|
||||
if (meh->message.isForChannel (channelNumberToExtract)
|
||||
|| (alsoIncludeMetaEvents && meh->message.isMetaEvent()))
|
||||
destSequence.addEvent (meh->message);
|
||||
}
|
||||
|
||||
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
|
||||
{
|
||||
for (auto* meh : list)
|
||||
if (meh->message.isSysEx())
|
||||
destSequence.addEvent (meh->message);
|
||||
}
|
||||
|
||||
void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove)
|
||||
{
|
||||
for (int i = list.size(); --i >= 0;)
|
||||
if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove))
|
||||
list.remove(i);
|
||||
}
|
||||
|
||||
void MidiMessageSequence::deleteSysExMessages()
|
||||
{
|
||||
for (int i = list.size(); --i >= 0;)
|
||||
if (list.getUnchecked(i)->message.isSysEx())
|
||||
list.remove(i);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class OptionalPitchWheel
|
||||
{
|
||||
int value = 0;
|
||||
bool valid = false;
|
||||
|
||||
public:
|
||||
void emit (int channel, Array<MidiMessage>& out) const
|
||||
{
|
||||
if (valid)
|
||||
out.add (MidiMessage::pitchWheel (channel, value));
|
||||
}
|
||||
|
||||
void set (int v)
|
||||
{
|
||||
value = v;
|
||||
valid = true;
|
||||
}
|
||||
};
|
||||
|
||||
class OptionalControllerValues
|
||||
{
|
||||
int values[128];
|
||||
|
||||
public:
|
||||
OptionalControllerValues()
|
||||
{
|
||||
std::fill (std::begin (values), std::end (values), -1);
|
||||
}
|
||||
|
||||
void emit (int channel, Array<MidiMessage>& out) const
|
||||
{
|
||||
for (auto it = std::begin (values); it != std::end (values); ++it)
|
||||
if (*it != -1)
|
||||
out.add (MidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), *it));
|
||||
}
|
||||
|
||||
void set (int controller, int value)
|
||||
{
|
||||
values[controller] = value;
|
||||
}
|
||||
};
|
||||
|
||||
class OptionalProgramChange
|
||||
{
|
||||
int value = -1, bankLSB = -1, bankMSB = -1;
|
||||
|
||||
public:
|
||||
void emit (int channel, double time, Array<MidiMessage>& out) const
|
||||
{
|
||||
if (value == -1)
|
||||
return;
|
||||
|
||||
if (bankLSB != -1 && bankMSB != -1)
|
||||
{
|
||||
out.add (MidiMessage::controllerEvent (channel, 0x00, bankMSB).withTimeStamp (time));
|
||||
out.add (MidiMessage::controllerEvent (channel, 0x20, bankLSB).withTimeStamp (time));
|
||||
}
|
||||
|
||||
out.add (MidiMessage::programChange (channel, value).withTimeStamp (time));
|
||||
}
|
||||
|
||||
// Returns true if this is a bank number change, and false otherwise.
|
||||
bool trySetBank (int controller, int v)
|
||||
{
|
||||
switch (controller)
|
||||
{
|
||||
case 0x00: bankMSB = v; return true;
|
||||
case 0x20: bankLSB = v; return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void setProgram (int v) { value = v; }
|
||||
};
|
||||
|
||||
class ParameterNumberState
|
||||
{
|
||||
enum class Kind { rpn, nrpn };
|
||||
|
||||
int newestRpnLsb = -1, newestRpnMsb = -1, newestNrpnLsb = -1, newestNrpnMsb = -1;
|
||||
int lastSentLsb = -1, lastSentMsb = -1;
|
||||
Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn;
|
||||
|
||||
public:
|
||||
// If the effective parameter number has changed since the last time this function was called,
|
||||
// this will emit the current parameter in full (MSB and LSB).
|
||||
// This should be called before each data message (entry, increment, decrement: 0x06, 0x26, 0x60, 0x61)
|
||||
// to ensure that the data message operates on the correct parameter number.
|
||||
void sendIfNecessary (int channel, double time, Array<MidiMessage>& out)
|
||||
{
|
||||
const auto newestMsb = newestKind == Kind::rpn ? newestRpnMsb : newestNrpnMsb;
|
||||
const auto newestLsb = newestKind == Kind::rpn ? newestRpnLsb : newestNrpnLsb;
|
||||
|
||||
auto lastSent = std::tie (lastSentKind, lastSentMsb, lastSentLsb);
|
||||
const auto newest = std::tie (newestKind, newestMsb, newestLsb);
|
||||
|
||||
if (lastSent == newest || newestMsb == -1 || newestLsb == -1)
|
||||
return;
|
||||
|
||||
out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, newestMsb).withTimeStamp (time));
|
||||
out.add (MidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x64 : 0x62, newestLsb).withTimeStamp (time));
|
||||
|
||||
lastSent = newest;
|
||||
}
|
||||
|
||||
// Returns true if this is a parameter number change, and false otherwise.
|
||||
bool trySetProgramNumber (int controller, int value)
|
||||
{
|
||||
switch (controller)
|
||||
{
|
||||
case 0x65: newestRpnMsb = value; newestKind = Kind::rpn; return true;
|
||||
case 0x64: newestRpnLsb = value; newestKind = Kind::rpn; return true;
|
||||
case 0x63: newestNrpnMsb = value; newestKind = Kind::nrpn; return true;
|
||||
case 0x62: newestNrpnLsb = value; newestKind = Kind::nrpn; return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void MidiMessageSequence::createControllerUpdatesForTime (int channel, double time, Array<MidiMessage>& dest)
|
||||
{
|
||||
OptionalProgramChange programChange;
|
||||
OptionalControllerValues controllers;
|
||||
OptionalPitchWheel pitchWheel;
|
||||
ParameterNumberState parameterNumberState;
|
||||
|
||||
for (const auto& item : list)
|
||||
{
|
||||
const auto& mm = item->message;
|
||||
|
||||
if (! (mm.isForChannel (channel) && mm.getTimeStamp() <= time))
|
||||
continue;
|
||||
|
||||
if (mm.isController())
|
||||
{
|
||||
const auto num = mm.getControllerNumber();
|
||||
|
||||
if (parameterNumberState.trySetProgramNumber (num, mm.getControllerValue()))
|
||||
continue;
|
||||
|
||||
if (programChange.trySetBank (num, mm.getControllerValue()))
|
||||
continue;
|
||||
|
||||
constexpr int passthroughs[] { 0x06, 0x26, 0x60, 0x61 };
|
||||
|
||||
if (std::find (std::begin (passthroughs), std::end (passthroughs), num) != std::end (passthroughs))
|
||||
{
|
||||
parameterNumberState.sendIfNecessary (channel, mm.getTimeStamp(), dest);
|
||||
dest.add (mm);
|
||||
}
|
||||
else
|
||||
{
|
||||
controllers.set (num, mm.getControllerValue());
|
||||
}
|
||||
}
|
||||
else if (mm.isProgramChange())
|
||||
{
|
||||
programChange.setProgram (mm.getProgramChangeNumber());
|
||||
}
|
||||
else if (mm.isPitchWheel())
|
||||
{
|
||||
pitchWheel.set (mm.getPitchWheelValue());
|
||||
}
|
||||
}
|
||||
|
||||
pitchWheel.emit (channel, dest);
|
||||
controllers.emit (channel, dest);
|
||||
|
||||
// Also emits bank change messages if necessary.
|
||||
programChange.emit (channel, time, dest);
|
||||
|
||||
// Set the parameter number to its final state.
|
||||
parameterNumberState.sendIfNecessary (channel, time, dest);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct MidiMessageSequenceTest : public UnitTest
|
||||
{
|
||||
MidiMessageSequenceTest()
|
||||
: UnitTest ("MidiMessageSequence", UnitTestCategories::midi)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
MidiMessageSequence s;
|
||||
|
||||
s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0));
|
||||
s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0));
|
||||
s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0));
|
||||
s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0));
|
||||
|
||||
beginTest ("Start & end time");
|
||||
expectEquals (s.getStartTime(), 0.0);
|
||||
expectEquals (s.getEndTime(), 8.0);
|
||||
expectEquals (s.getEventTime (1), 2.0);
|
||||
|
||||
beginTest ("Matching note off & ons");
|
||||
s.updateMatchedPairs();
|
||||
expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0);
|
||||
expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0);
|
||||
expectEquals (s.getIndexOfMatchingKeyUp (0), 2);
|
||||
expectEquals (s.getIndexOfMatchingKeyUp (1), 3);
|
||||
|
||||
beginTest ("Time & indices");
|
||||
expectEquals (s.getNextIndexAtTime (0.5), 1);
|
||||
expectEquals (s.getNextIndexAtTime (2.5), 2);
|
||||
expectEquals (s.getNextIndexAtTime (9.0), 4);
|
||||
|
||||
beginTest ("Deleting events");
|
||||
s.deleteEvent (0, true);
|
||||
expectEquals (s.getNumEvents(), 2);
|
||||
|
||||
beginTest ("Merging sequences");
|
||||
MidiMessageSequence s2;
|
||||
s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0));
|
||||
s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0));
|
||||
s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0));
|
||||
s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0));
|
||||
s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0));
|
||||
s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0));
|
||||
|
||||
s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off
|
||||
s.updateMatchedPairs();
|
||||
|
||||
expectEquals (s.getNumEvents(), 7);
|
||||
expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off
|
||||
expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0);
|
||||
|
||||
struct ControlValue { int control, value; };
|
||||
|
||||
struct DataEntry
|
||||
{
|
||||
int controllerBase, channel, parameter, value;
|
||||
double time;
|
||||
|
||||
std::array<ControlValue, 4> getControlValues() const
|
||||
{
|
||||
return { { { controllerBase + 1, (parameter >> 7) & 0x7f },
|
||||
{ controllerBase + 0, (parameter >> 0) & 0x7f },
|
||||
{ 0x06, (value >> 7) & 0x7f },
|
||||
{ 0x26, (value >> 0) & 0x7f } } };
|
||||
}
|
||||
|
||||
void addToSequence (MidiMessageSequence& s) const
|
||||
{
|
||||
for (const auto& pair : getControlValues())
|
||||
s.addEvent (MidiMessage::controllerEvent (channel, pair.control, pair.value), time);
|
||||
}
|
||||
|
||||
bool matches (const MidiMessage* begin, const MidiMessage* end) const
|
||||
{
|
||||
const auto isEqual = [this] (const ControlValue& cv, const MidiMessage& msg)
|
||||
{
|
||||
return msg.getTimeStamp() == time
|
||||
&& msg.isController()
|
||||
&& msg.getChannel() == channel
|
||||
&& msg.getControllerNumber() == cv.control
|
||||
&& msg.getControllerValue() == cv.value;
|
||||
};
|
||||
|
||||
const auto pairs = getControlValues();
|
||||
return std::equal (pairs.begin(), pairs.end(), begin, end, isEqual);
|
||||
}
|
||||
};
|
||||
|
||||
const auto addNrpn = [&] (MidiMessageSequence& seq, int channel, int parameter, int value, double time = 0.0)
|
||||
{
|
||||
DataEntry { 0x62, channel, parameter, value, time }.addToSequence (seq);
|
||||
};
|
||||
|
||||
const auto addRpn = [&] (MidiMessageSequence& seq, int channel, int parameter, int value, double time = 0.0)
|
||||
{
|
||||
DataEntry { 0x64, channel, parameter, value, time }.addToSequence (seq);
|
||||
};
|
||||
|
||||
const auto checkNrpn = [&] (const MidiMessage* begin, const MidiMessage* end, int channel, int parameter, int value, double time = 0.0)
|
||||
{
|
||||
expect (DataEntry { 0x62, channel, parameter, value, time }.matches (begin, end));
|
||||
};
|
||||
|
||||
const auto checkRpn = [&] (const MidiMessage* begin, const MidiMessage* end, int channel, int parameter, int value, double time = 0.0)
|
||||
{
|
||||
expect (DataEntry { 0x64, channel, parameter, value, time }.matches (begin, end));
|
||||
};
|
||||
|
||||
beginTest ("createControllerUpdatesForTime should emit (N)RPN components in the correct order");
|
||||
{
|
||||
const auto channel = 1;
|
||||
const auto number = 200;
|
||||
const auto value = 300;
|
||||
|
||||
MidiMessageSequence sequence;
|
||||
addNrpn (sequence, channel, number, value);
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, 1.0, m);
|
||||
|
||||
checkNrpn (m.begin(), m.end(), channel, number, value);
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime ignores (N)RPNs after the final requested time");
|
||||
{
|
||||
const auto channel = 2;
|
||||
const auto number = 123;
|
||||
const auto value = 456;
|
||||
|
||||
MidiMessageSequence sequence;
|
||||
addRpn (sequence, channel, number, value, 0.5);
|
||||
addRpn (sequence, channel, 111, 222, 1.5);
|
||||
addRpn (sequence, channel, 333, 444, 2.5);
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, 1.0, m);
|
||||
|
||||
checkRpn (m.begin(), std::next (m.begin(), 4), channel, number, value, 0.5);
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime should emit separate (N)RPN messages when appropriate");
|
||||
{
|
||||
const auto channel = 2;
|
||||
const auto numberA = 1111;
|
||||
const auto valueA = 9999;
|
||||
|
||||
const auto numberB = 8888;
|
||||
const auto valueB = 2222;
|
||||
|
||||
const auto numberC = 7777;
|
||||
const auto valueC = 3333;
|
||||
|
||||
const auto numberD = 6666;
|
||||
const auto valueD = 4444;
|
||||
|
||||
const auto time = 0.5;
|
||||
|
||||
MidiMessageSequence sequence;
|
||||
addRpn (sequence, channel, numberA, valueA, time);
|
||||
addRpn (sequence, channel, numberB, valueB, time);
|
||||
addNrpn (sequence, channel, numberC, valueC, time);
|
||||
addNrpn (sequence, channel, numberD, valueD, time);
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, time * 2, m);
|
||||
|
||||
checkRpn (std::next (m.begin(), 0), std::next (m.begin(), 4), channel, numberA, valueA, time);
|
||||
checkRpn (std::next (m.begin(), 4), std::next (m.begin(), 8), channel, numberB, valueB, time);
|
||||
checkNrpn (std::next (m.begin(), 8), std::next (m.begin(), 12), channel, numberC, valueC, time);
|
||||
checkNrpn (std::next (m.begin(), 12), std::next (m.begin(), 16), channel, numberD, valueD, time);
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime correctly emits (N)RPN messages on multiple channels");
|
||||
{
|
||||
struct Info { int channel, number, value; };
|
||||
|
||||
const Info infos[] { { 2, 1111, 9999 },
|
||||
{ 8, 8888, 2222 },
|
||||
{ 5, 7777, 3333 },
|
||||
{ 1, 6666, 4444 } };
|
||||
|
||||
const auto time = 0.5;
|
||||
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
for (const auto& info : infos)
|
||||
addRpn (sequence, info.channel, info.number, info.value, time);
|
||||
|
||||
for (const auto& info : infos)
|
||||
{
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (info.channel, time * 2, m);
|
||||
checkRpn (std::next (m.begin(), 0), std::next (m.begin(), 4), info.channel, info.number, info.value, time);
|
||||
}
|
||||
}
|
||||
|
||||
const auto messagesAreEqual = [] (const MidiMessage& a, const MidiMessage& b)
|
||||
{
|
||||
return std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(),
|
||||
b.getRawData(), b.getRawData() + b.getRawDataSize());
|
||||
};
|
||||
|
||||
beginTest ("createControllerUpdatesForTime sends bank select messages when the next program is in a new bank");
|
||||
{
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
const auto time = 0.0;
|
||||
const auto channel = 1;
|
||||
|
||||
sequence.addEvent (MidiMessage::programChange (channel, 5), time);
|
||||
|
||||
sequence.addEvent (MidiMessage::controllerEvent (channel, 0x00, 128), time);
|
||||
sequence.addEvent (MidiMessage::controllerEvent (channel, 0x20, 64), time);
|
||||
sequence.addEvent (MidiMessage::programChange (channel, 63), time);
|
||||
|
||||
const Array<MidiMessage> finalEvents { MidiMessage::controllerEvent (channel, 0x00, 50),
|
||||
MidiMessage::controllerEvent (channel, 0x20, 40),
|
||||
MidiMessage::programChange (channel, 30) };
|
||||
|
||||
for (const auto& e : finalEvents)
|
||||
sequence.addEvent (e);
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, 1.0, m);
|
||||
|
||||
expect (std::equal (m.begin(), m.end(), finalEvents.begin(), finalEvents.end(), messagesAreEqual));
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime preserves all Data Increment and Data Decrement messages");
|
||||
{
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
const auto time = 0.0;
|
||||
const auto channel = 1;
|
||||
|
||||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x60, 0),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 100),
|
||||
MidiMessage::controllerEvent (channel, 0x26, 50),
|
||||
MidiMessage::controllerEvent (channel, 0x60, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x61, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 20),
|
||||
MidiMessage::controllerEvent (channel, 0x26, 30),
|
||||
MidiMessage::controllerEvent (channel, 0x61, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x61, 20) };
|
||||
|
||||
for (const auto& m : messages)
|
||||
sequence.addEvent (m, time);
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, 1.0, m);
|
||||
|
||||
expect (std::equal (m.begin(), m.end(), messages.begin(), messages.end(), messagesAreEqual));
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime does not emit redundant parameter number changes");
|
||||
{
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
const auto time = 0.0;
|
||||
const auto channel = 1;
|
||||
|
||||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 0),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 100),
|
||||
MidiMessage::controllerEvent (channel, 0x63, 50),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 10) };
|
||||
|
||||
for (const auto& m : messages)
|
||||
sequence.addEvent (m, time);
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, 1.0, m);
|
||||
|
||||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 50),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 10) };
|
||||
|
||||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual));
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime sets parameter number correctly at end of sequence");
|
||||
{
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
const auto time = 0.0;
|
||||
const auto channel = 1;
|
||||
|
||||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 0),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 100),
|
||||
MidiMessage::controllerEvent (channel, 0x63, 50),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 5) };
|
||||
|
||||
for (const auto& m : messages)
|
||||
sequence.addEvent (m, time);
|
||||
|
||||
const auto finalTime = 1.0;
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, finalTime, m);
|
||||
|
||||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 50),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 10),
|
||||
// Note: we should send both the MSB and LSB!
|
||||
MidiMessage::controllerEvent (channel, 0x65, 0).withTimeStamp (finalTime),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 5).withTimeStamp (finalTime) };
|
||||
|
||||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual));
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime does not emit duplicate parameter number change messages");
|
||||
{
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
const auto time = 0.0;
|
||||
const auto channel = 1;
|
||||
|
||||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x65, 1),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 2),
|
||||
MidiMessage::controllerEvent (channel, 0x63, 3),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 4),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 10),
|
||||
MidiMessage::controllerEvent (channel, 0x63, 30),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 40),
|
||||
MidiMessage::controllerEvent (channel, 0x63, 3),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 4),
|
||||
MidiMessage::controllerEvent (channel, 0x60, 5),
|
||||
MidiMessage::controllerEvent (channel, 0x65, 10) };
|
||||
|
||||
for (const auto& m : messages)
|
||||
sequence.addEvent (m, time);
|
||||
|
||||
const auto finalTime = 1.0;
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, finalTime, m);
|
||||
|
||||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x63, 3),
|
||||
MidiMessage::controllerEvent (channel, 0x62, 4),
|
||||
MidiMessage::controllerEvent (channel, 0x06, 10),
|
||||
// Parameter number is set to (30, 40) then back to (3, 4),
|
||||
// so there is no need to resend it
|
||||
MidiMessage::controllerEvent (channel, 0x60, 5),
|
||||
// Set parameter number to final value
|
||||
MidiMessage::controllerEvent (channel, 0x65, 10).withTimeStamp (finalTime),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 2) .withTimeStamp (finalTime) };
|
||||
|
||||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual));
|
||||
}
|
||||
|
||||
beginTest ("createControllerUpdatesForTime emits bank change messages immediately before program change");
|
||||
{
|
||||
MidiMessageSequence sequence;
|
||||
|
||||
const auto time = 0.0;
|
||||
const auto channel = 1;
|
||||
|
||||
const Array<MidiMessage> messages { MidiMessage::controllerEvent (channel, 0x00, 1),
|
||||
MidiMessage::controllerEvent (channel, 0x20, 2),
|
||||
MidiMessage::controllerEvent (channel, 0x65, 0),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 0),
|
||||
MidiMessage::programChange (channel, 5) };
|
||||
|
||||
for (const auto& m : messages)
|
||||
sequence.addEvent (m, time);
|
||||
|
||||
const auto finalTime = 1.0;
|
||||
|
||||
Array<MidiMessage> m;
|
||||
sequence.createControllerUpdatesForTime (channel, finalTime, m);
|
||||
|
||||
const Array<MidiMessage> expected { MidiMessage::controllerEvent (channel, 0x00, 1),
|
||||
MidiMessage::controllerEvent (channel, 0x20, 2),
|
||||
MidiMessage::programChange (channel, 5),
|
||||
MidiMessage::controllerEvent (channel, 0x65, 0).withTimeStamp (finalTime),
|
||||
MidiMessage::controllerEvent (channel, 0x64, 0).withTimeStamp (finalTime) };
|
||||
|
||||
|
||||
expect (std::equal (m.begin(), m.end(), expected.begin(), expected.end(), messagesAreEqual));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static MidiMessageSequenceTest midiMessageSequenceTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
321
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h
vendored
Normal file
321
deps/juce/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h
vendored
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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;
|
||||
|
||||
/** Destructor. */
|
||||
~MidiMessageSequence();
|
||||
|
||||
//==============================================================================
|
||||
/** 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:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
~MidiEventHolder();
|
||||
|
||||
/** 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
|
380
deps/juce/modules/juce_audio_basics/midi/juce_MidiRPN.cpp
vendored
Normal file
380
deps/juce/modules/juce_audio_basics/midi/juce_MidiRPN.cpp
vendored
Normal file
@ -0,0 +1,380 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiRPNDetector::ChannelState::ChannelState() noexcept
|
||||
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), 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
|
154
deps/juce/modules/juce_audio_basics/midi/juce_MidiRPN.h
vendored
Normal file
154
deps/juce/modules/juce_audio_basics/midi/juce_MidiRPN.h
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
||||
{
|
||||
ChannelState() noexcept;
|
||||
bool handleController (int channel, int controllerNumber,
|
||||
int value, MidiRPNMessage&) noexcept;
|
||||
void resetValue() noexcept;
|
||||
bool sendIfReady (int channel, MidiRPNMessage&) noexcept;
|
||||
|
||||
uint8 parameterMSB, parameterLSB, valueMSB, valueLSB;
|
||||
bool isNRPN;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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
|
43
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMP.h
vendored
Normal file
43
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMP.h
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace ump = universal_midi_packets;
|
||||
}
|
326
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h
vendored
Normal file
326
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h
vendored
Normal file
@ -0,0 +1,326 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
165
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h
vendored
Normal file
165
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
198
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h
vendored
Normal file
198
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
534
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h
vendored
Normal file
534
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h
vendored
Normal file
@ -0,0 +1,534 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
This struct holds functions that can be used to create different kinds
|
||||
of Universal MIDI Packet.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
struct Factory
|
||||
{
|
||||
/** @internal */
|
||||
struct Detail
|
||||
{
|
||||
static PacketX1 makeSystem() { return PacketX1{}.withMessageType (1); }
|
||||
static PacketX1 makeV1() { return PacketX1{}.withMessageType (2); }
|
||||
static PacketX2 makeV2() { return PacketX2{}.withMessageType (4); }
|
||||
|
||||
static PacketX2 makeSysEx (uint8_t group,
|
||||
uint8_t status,
|
||||
uint8_t numBytes,
|
||||
const uint8_t* data)
|
||||
{
|
||||
jassert (numBytes <= 6);
|
||||
|
||||
std::array<uint8_t, 8> bytes{{}};
|
||||
bytes[0] = (0x3 << 0x4) | group;
|
||||
bytes[1] = (uint8_t) (status << 0x4) | numBytes;
|
||||
|
||||
std::memcpy (bytes.data() + 2, data, numBytes);
|
||||
|
||||
std::array<uint32_t, 2> words;
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for (auto& word : words)
|
||||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++);
|
||||
|
||||
return PacketX2 { words };
|
||||
}
|
||||
|
||||
static PacketX4 makeSysEx8 (uint8_t group,
|
||||
uint8_t status,
|
||||
uint8_t numBytes,
|
||||
uint8_t dataStart,
|
||||
const uint8_t* data)
|
||||
{
|
||||
jassert (numBytes <= 16 - dataStart);
|
||||
|
||||
std::array<uint8_t, 16> bytes{{}};
|
||||
bytes[0] = (0x5 << 0x4) | group;
|
||||
bytes[1] = (uint8_t) (status << 0x4) | numBytes;
|
||||
|
||||
std::memcpy (bytes.data() + dataStart, data, numBytes);
|
||||
|
||||
std::array<uint32_t, 4> words;
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for (auto& word : words)
|
||||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++);
|
||||
|
||||
return PacketX4 { words };
|
||||
}
|
||||
};
|
||||
|
||||
static PacketX1 makeNoop (uint8_t group)
|
||||
{
|
||||
return PacketX1{}.withGroup (group);
|
||||
}
|
||||
|
||||
static PacketX1 makeJRClock (uint8_t group, uint16_t time)
|
||||
{
|
||||
return PacketX1 { time }.withStatus (1).withGroup (group);
|
||||
}
|
||||
|
||||
static PacketX1 makeJRTimestamp (uint8_t group, uint16_t time)
|
||||
{
|
||||
return PacketX1 { time }.withStatus (2).withGroup (group);
|
||||
}
|
||||
|
||||
static PacketX1 makeTimeCode (uint8_t group, uint8_t code)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xf1)
|
||||
.withU8<2> (code & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeSongPositionPointer (uint8_t group, uint16_t pos)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xf2)
|
||||
.withU8<2> (pos & 0x7f)
|
||||
.withU8<3> ((pos >> 7) & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeSongSelect (uint8_t group, uint8_t song)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xf3)
|
||||
.withU8<2> (song & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeTuneRequest (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xf6);
|
||||
}
|
||||
|
||||
static PacketX1 makeTimingClock (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xf8);
|
||||
}
|
||||
|
||||
static PacketX1 makeStart (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xfa);
|
||||
}
|
||||
|
||||
static PacketX1 makeContinue (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xfb);
|
||||
}
|
||||
|
||||
static PacketX1 makeStop (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xfc);
|
||||
}
|
||||
|
||||
static PacketX1 makeActiveSensing (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xfe);
|
||||
}
|
||||
|
||||
static PacketX1 makeReset (uint8_t group)
|
||||
{
|
||||
return Detail::makeSystem().withGroup (group)
|
||||
.withU8<1> (0xff);
|
||||
}
|
||||
|
||||
static PacketX1 makeNoteOffV1 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint8_t velocity)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0x8)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> (velocity & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeNoteOnV1 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint8_t velocity)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0x9)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> (velocity & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makePolyPressureV1 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint8_t pressure)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0xa)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> (pressure & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeControlChangeV1 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t controller,
|
||||
uint8_t value)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0xb)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (controller & 0x7f)
|
||||
.withU8<3> (value & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeProgramChangeV1 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t program)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0xc)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (program & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makeChannelPressureV1 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t pressure)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0xd)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (pressure & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX1 makePitchBend (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint16_t pitchbend)
|
||||
{
|
||||
return Detail::makeV1().withGroup (group)
|
||||
.withStatus (0xe)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (pitchbend & 0x7f)
|
||||
.withU8<3> ((pitchbend >> 7) & 0x7f);
|
||||
}
|
||||
|
||||
static PacketX2 makeSysExIn1Packet (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx (group, 0x0, numBytes, data);
|
||||
}
|
||||
|
||||
static PacketX2 makeSysExStart (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx (group, 0x1, numBytes, data);
|
||||
}
|
||||
|
||||
static PacketX2 makeSysExContinue (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx (group, 0x2, numBytes, data);
|
||||
}
|
||||
|
||||
static PacketX2 makeSysExEnd (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx (group, 0x3, numBytes, data);
|
||||
}
|
||||
|
||||
static PacketX2 makeRegisteredPerNoteControllerV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint8_t controller,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x0)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> (controller & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeAssignablePerNoteControllerV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint8_t controller,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x1)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> (controller & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeRegisteredControllerV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t bank,
|
||||
uint8_t index,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x2)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (bank & 0x7f)
|
||||
.withU8<3> (index & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeAssignableControllerV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t bank,
|
||||
uint8_t index,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x3)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (bank & 0x7f)
|
||||
.withU8<3> (index & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeRelativeRegisteredControllerV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t bank,
|
||||
uint8_t index,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x4)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (bank & 0x7f)
|
||||
.withU8<3> (index & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeRelativeAssignableControllerV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t bank,
|
||||
uint8_t index,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x5)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (bank & 0x7f)
|
||||
.withU8<3> (index & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makePerNotePitchBendV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x6)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
enum class NoteAttributeKind : uint8_t
|
||||
{
|
||||
none = 0x00,
|
||||
manufacturer = 0x01,
|
||||
profile = 0x02,
|
||||
pitch7_9 = 0x03
|
||||
};
|
||||
|
||||
static PacketX2 makeNoteOffV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
NoteAttributeKind attribute,
|
||||
uint16_t velocity,
|
||||
uint16_t attributeValue)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x8)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> ((uint8_t) attribute)
|
||||
.withU16<2> (velocity)
|
||||
.withU16<3> (attributeValue);
|
||||
}
|
||||
|
||||
static PacketX2 makeNoteOnV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
NoteAttributeKind attribute,
|
||||
uint16_t velocity,
|
||||
uint16_t attributeValue)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0x9)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU8<3> ((uint8_t) attribute)
|
||||
.withU16<2> (velocity)
|
||||
.withU16<3> (attributeValue);
|
||||
}
|
||||
|
||||
static PacketX2 makePolyPressureV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0xa)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeControlChangeV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t controller,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0xb)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (controller & 0x7f)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makeProgramChangeV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t optionFlags,
|
||||
uint8_t program,
|
||||
uint8_t bankMsb,
|
||||
uint8_t bankLsb)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0xc)
|
||||
.withChannel (channel)
|
||||
.withU8<3> (optionFlags)
|
||||
.withU8<4> (program)
|
||||
.withU8<6> (bankMsb)
|
||||
.withU8<7> (bankLsb);
|
||||
}
|
||||
|
||||
static PacketX2 makeChannelPressureV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0xd)
|
||||
.withChannel (channel)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makePitchBendV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint32_t data)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0xe)
|
||||
.withChannel (channel)
|
||||
.withU32<1> (data);
|
||||
}
|
||||
|
||||
static PacketX2 makePerNoteManagementV2 (uint8_t group,
|
||||
uint8_t channel,
|
||||
uint8_t note,
|
||||
uint8_t optionFlags)
|
||||
{
|
||||
return Detail::makeV2().withGroup (group)
|
||||
.withStatus (0xf)
|
||||
.withChannel (channel)
|
||||
.withU8<2> (note)
|
||||
.withU8<3> (optionFlags);
|
||||
}
|
||||
|
||||
|
||||
static PacketX4 makeSysEx8in1Packet (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
uint8_t streamId,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx8 (group, 0x0, numBytes, 3, data).withU8<2> (streamId);
|
||||
}
|
||||
|
||||
static PacketX4 makeSysEx8Start (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
uint8_t streamId,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx8 (group, 0x1, numBytes, 3, data).withU8<2> (streamId);
|
||||
}
|
||||
|
||||
static PacketX4 makeSysEx8Continue (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
uint8_t streamId,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx8 (group, 0x2, numBytes, 3, data).withU8<2> (streamId);
|
||||
}
|
||||
|
||||
static PacketX4 makeSysEx8End (uint8_t group,
|
||||
uint8_t numBytes,
|
||||
uint8_t streamId,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx8 (group, 0x3, numBytes, 3, data).withU8<2> (streamId);
|
||||
}
|
||||
|
||||
static PacketX4 makeMixedDataSetHeader (uint8_t group,
|
||||
uint8_t dataSetId,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx8 (group, 0x8, 14, 2, data).withChannel (dataSetId);
|
||||
}
|
||||
|
||||
static PacketX4 makeDataSetPayload (uint8_t group,
|
||||
uint8_t dataSetId,
|
||||
const uint8_t* data)
|
||||
{
|
||||
return Detail::makeSysEx8 (group, 0x9, 14, 2, data).withChannel (dataSetId);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
126
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPIterator.h
vendored
Normal file
126
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPIterator.h
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
}
|
213
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h
vendored
Normal file
213
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h
vendored
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
195
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp
vendored
Normal file
195
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
187
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h
vendored
Normal file
187
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
44
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPProtocols.h
vendored
Normal file
44
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPProtocols.h
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
42
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPReceiver.h
vendored
Normal file
42
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPReceiver.h
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
53
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp
vendored
Normal file
53
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
73
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h
vendored
Normal file
73
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
This struct acts as a single-file namespace for Univeral 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);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
1018
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPTests.cpp
vendored
Normal file
1018
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPTests.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
59
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPUtils.cpp
vendored
Normal file
59
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPUtils.cpp
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
113
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h
vendored
Normal file
113
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
35
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPView.cpp
vendored
Normal file
35
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPView.cpp
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace universal_midi_packets
|
||||
{
|
||||
|
||||
uint32_t View::size() const noexcept
|
||||
{
|
||||
jassert (ptr != nullptr);
|
||||
return Utils::getNumWordsForMessageType (*ptr);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
88
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPView.h
vendored
Normal file
88
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPView.h
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
189
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPacket.h
vendored
Normal file
189
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPacket.h
vendored
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
Holds a single Universal MIDI Packet.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
template <size_t numWords>
|
||||
class Packet
|
||||
{
|
||||
public:
|
||||
Packet() = default;
|
||||
|
||||
template <size_t w = numWords, typename std::enable_if<w == 1, int>::type = 0>
|
||||
Packet (uint32_t a)
|
||||
: contents { { a } }
|
||||
{
|
||||
jassert (Utils::getNumWordsForMessageType (a) == 1);
|
||||
}
|
||||
|
||||
template <size_t w = numWords, typename std::enable_if<w == 2, int>::type = 0>
|
||||
Packet (uint32_t a, uint32_t b)
|
||||
: contents { { a, b } }
|
||||
{
|
||||
jassert (Utils::getNumWordsForMessageType (a) == 2);
|
||||
}
|
||||
|
||||
template <size_t w = numWords, typename std::enable_if<w == 3, int>::type = 0>
|
||||
Packet (uint32_t a, uint32_t b, uint32_t c)
|
||||
: contents { { a, b, c } }
|
||||
{
|
||||
jassert (Utils::getNumWordsForMessageType (a) == 3);
|
||||
}
|
||||
|
||||
template <size_t w = numWords, typename std::enable_if<w == 4, int>::type = 0>
|
||||
Packet (uint32_t a, uint32_t b, uint32_t c, uint32_t d)
|
||||
: contents { { a, b, c, d } }
|
||||
{
|
||||
jassert (Utils::getNumWordsForMessageType (a) == 4);
|
||||
}
|
||||
|
||||
template <size_t w, typename std::enable_if<w == numWords, int>::type = 0>
|
||||
explicit Packet (const std::array<uint32_t, w>& fullPacket)
|
||||
: contents (fullPacket)
|
||||
{
|
||||
jassert (Utils::getNumWordsForMessageType (fullPacket.front()) == numWords);
|
||||
}
|
||||
|
||||
Packet withMessageType (uint8_t type) const noexcept
|
||||
{
|
||||
return withU4<0> (type);
|
||||
}
|
||||
|
||||
Packet withGroup (uint8_t group) const noexcept
|
||||
{
|
||||
return withU4<1> (group);
|
||||
}
|
||||
|
||||
Packet withStatus (uint8_t status) const noexcept
|
||||
{
|
||||
return withU4<2> (status);
|
||||
}
|
||||
|
||||
Packet withChannel (uint8_t channel) const noexcept
|
||||
{
|
||||
return withU4<3> (channel);
|
||||
}
|
||||
|
||||
uint8_t getMessageType() const noexcept { return getU4<0>(); }
|
||||
|
||||
uint8_t getGroup() const noexcept { return getU4<1>(); }
|
||||
|
||||
uint8_t getStatus() const noexcept { return getU4<2>(); }
|
||||
|
||||
uint8_t getChannel() const noexcept { return getU4<3>(); }
|
||||
|
||||
template <size_t index>
|
||||
Packet withU4 (uint8_t value) const noexcept
|
||||
{
|
||||
constexpr auto word = index / 8;
|
||||
auto copy = *this;
|
||||
std::get<word> (copy.contents) = Utils::U4<index % 8>::set (copy.template getU32<word>(), value);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
Packet withU8 (uint8_t value) const noexcept
|
||||
{
|
||||
constexpr auto word = index / 4;
|
||||
auto copy = *this;
|
||||
std::get<word> (copy.contents) = Utils::U8<index % 4>::set (copy.template getU32<word>(), value);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
Packet withU16 (uint16_t value) const noexcept
|
||||
{
|
||||
constexpr auto word = index / 2;
|
||||
auto copy = *this;
|
||||
std::get<word> (copy.contents) = Utils::U16<index % 2>::set (copy.template getU32<word>(), value);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
Packet withU32 (uint32_t value) const noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
std::get<index> (copy.contents) = value;
|
||||
return copy;
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
uint8_t getU4() const noexcept
|
||||
{
|
||||
return Utils::U4<index % 8>::get (this->template getU32<index / 8>());
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
uint8_t getU8() const noexcept
|
||||
{
|
||||
return Utils::U8<index % 4>::get (this->template getU32<index / 4>());
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
uint16_t getU16() const noexcept
|
||||
{
|
||||
return Utils::U16<index % 2>::get (this->template getU32<index / 2>());
|
||||
}
|
||||
|
||||
template <size_t index>
|
||||
uint32_t getU32() const noexcept
|
||||
{
|
||||
return std::get<index> (contents);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
using Contents = std::array<uint32_t, numWords>;
|
||||
|
||||
using const_iterator = typename Contents::const_iterator;
|
||||
|
||||
const_iterator begin() const noexcept { return contents.begin(); }
|
||||
const_iterator cbegin() const noexcept { return contents.begin(); }
|
||||
|
||||
const_iterator end() const noexcept { return contents.end(); }
|
||||
const_iterator cend() const noexcept { return contents.end(); }
|
||||
|
||||
const uint32_t* data() const noexcept { return contents.data(); }
|
||||
|
||||
const uint32_t& front() const noexcept { return contents.front(); }
|
||||
const uint32_t& back() const noexcept { return contents.back(); }
|
||||
|
||||
const uint32_t& operator[] (size_t index) const noexcept { return contents[index]; }
|
||||
|
||||
private:
|
||||
Contents contents { {} };
|
||||
};
|
||||
|
||||
using PacketX1 = Packet<1>;
|
||||
using PacketX2 = Packet<2>;
|
||||
using PacketX3 = Packet<3>;
|
||||
using PacketX4 = Packet<4>;
|
||||
|
||||
}
|
||||
}
|
92
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPackets.h
vendored
Normal file
92
deps/juce/modules/juce_audio_basics/midi/ump/juce_UMPackets.h
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
Holds a collection of Universal MIDI Packets.
|
||||
|
||||
Unlike MidiBuffer, this collection does not store any additional information
|
||||
(e.g. timestamps) alongside the raw messages.
|
||||
|
||||
If timestamps are required, these can be added to the container in UMP format,
|
||||
as Jitter Reduction Utility messages.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Packets
|
||||
{
|
||||
public:
|
||||
/** Adds a single packet to the collection.
|
||||
|
||||
The View must be valid for this to work. If the view
|
||||
points to a malformed message, or if the view points to a region
|
||||
too short for the contained message, this call will result in
|
||||
undefined behaviour.
|
||||
*/
|
||||
void add (const View& v) { storage.insert (storage.end(), v.cbegin(), v.cend()); }
|
||||
|
||||
void add (const PacketX1& p) { addImpl (p); }
|
||||
void add (const PacketX2& p) { addImpl (p); }
|
||||
void add (const PacketX3& p) { addImpl (p); }
|
||||
void add (const PacketX4& p) { addImpl (p); }
|
||||
|
||||
/** Pre-allocates space for at least `numWords` 32-bit words in this collection. */
|
||||
void reserve (size_t numWords) { storage.reserve (numWords); }
|
||||
|
||||
/** Removes all previously-added packets from this collection. */
|
||||
void clear() { storage.clear(); }
|
||||
|
||||
/** Gets an iterator pointing to the first packet in this collection. */
|
||||
Iterator cbegin() const noexcept { return Iterator (data(), size()); }
|
||||
Iterator begin() const noexcept { return cbegin(); }
|
||||
|
||||
/** Gets an iterator pointing one-past the last packet in this collection. */
|
||||
Iterator cend() const noexcept { return Iterator (data() + size(), 0); }
|
||||
Iterator end() const noexcept { return cend(); }
|
||||
|
||||
/** Gets a pointer to the contents of the collection as a range of raw 32-bit words. */
|
||||
const uint32_t* data() const noexcept { return storage.data(); }
|
||||
|
||||
/** Returns the number of uint32_t words in storage.
|
||||
|
||||
Note that this is likely to be larger than the number of packets
|
||||
currently being stored, as some packets span multiple words.
|
||||
*/
|
||||
size_t size() const noexcept { return storage.size(); }
|
||||
|
||||
private:
|
||||
template <size_t numWords>
|
||||
void addImpl (const Packet<numWords>& p)
|
||||
{
|
||||
jassert (Utils::getNumWordsForMessageType (p[0]) == numWords);
|
||||
add (View (p.data()));
|
||||
}
|
||||
|
||||
std::vector<uint32_t> storage;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user