261 lines
9.9 KiB
C++
261 lines
9.9 KiB
C++
#pragma once
|
|
#include "CoreMinimal.h"
|
|
#include "MidiMessage.h"
|
|
|
|
//==============================================================================
|
|
/**
|
|
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.
|
|
*/
|
|
FMidiMessage getMessage() const { return FMidiMessage (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 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 FMidiBuffer
|
|
{
|
|
public:
|
|
//==============================================================================
|
|
/** Creates an empty MidiBuffer. */
|
|
FMidiBuffer() noexcept = default;
|
|
|
|
/** Creates a MidiBuffer containing a single midi message. */
|
|
explicit FMidiBuffer (const FMidiMessage& 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() 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 FMidiMessage& 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 FMidiBuffer& 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 (FMidiBuffer&) 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.GetData()); }
|
|
|
|
/** Get a read-only iterator pointing one past the end of this buffer. */
|
|
MidiBufferIterator cend() const noexcept { return MidiBufferIterator (data.GetData() + data.Num()); }
|
|
|
|
/** Get an iterator pointing to the first event with a timestamp greater-than or
|
|
equal-to `samplePosition`.
|
|
*/
|
|
MidiBufferIterator findNextSamplePosition (int samplePosition) const noexcept;
|
|
|
|
|
|
/** 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!
|
|
*/
|
|
TArray<uint8> data;
|
|
};
|