mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-15 00:24:19 +00:00
Added Animated App template and examples
This commit is contained in:
parent
fefcf7aca6
commit
ff6520a89a
1141 changed files with 438491 additions and 94 deletions
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace MidiBufferHelpers
|
||||
{
|
||||
inline int getEventTime (const void* const d) noexcept
|
||||
{
|
||||
return *static_cast<const int32*> (d);
|
||||
}
|
||||
|
||||
inline uint16 getEventDataSize (const void* const d) noexcept
|
||||
{
|
||||
return *reinterpret_cast<const uint16*> (static_cast<const char*> (d) + sizeof (int32));
|
||||
}
|
||||
|
||||
inline uint16 getEventTotalSize (const void* const d) noexcept
|
||||
{
|
||||
return getEventDataSize (d) + sizeof (int32) + sizeof (uint16);
|
||||
}
|
||||
|
||||
static int findActualEventLength (const uint8* const data, const int maxBytes) noexcept
|
||||
{
|
||||
unsigned int byte = (unsigned int) *data;
|
||||
int size = 0;
|
||||
|
||||
if (byte == 0xf0 || byte == 0xf7)
|
||||
{
|
||||
const uint8* d = data + 1;
|
||||
|
||||
while (d < data + maxBytes)
|
||||
if (*d++ == 0xf7)
|
||||
break;
|
||||
|
||||
size = (int) (d - data);
|
||||
}
|
||||
else if (byte == 0xff)
|
||||
{
|
||||
int n;
|
||||
const int bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n);
|
||||
size = jmin (maxBytes, n + 2 + bytesLeft);
|
||||
}
|
||||
else if (byte >= 0x80)
|
||||
{
|
||||
size = jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static uint8* findEventAfter (uint8* d, uint8* endData, const int samplePosition) noexcept
|
||||
{
|
||||
while (d < endData && getEventTime (d) <= samplePosition)
|
||||
d += getEventTotalSize (d);
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiBuffer::MidiBuffer() noexcept {}
|
||||
MidiBuffer::~MidiBuffer() {}
|
||||
|
||||
MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {}
|
||||
|
||||
MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept
|
||||
{
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
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 (const int startSample, const int numSamples)
|
||||
{
|
||||
uint8* const start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1);
|
||||
uint8* const end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1);
|
||||
|
||||
data.removeRange ((int) (start - data.begin()), (int) (end - data.begin()));
|
||||
}
|
||||
|
||||
void MidiBuffer::addEvent (const MidiMessage& m, const int sampleNumber)
|
||||
{
|
||||
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
|
||||
}
|
||||
|
||||
void MidiBuffer::addEvent (const void* const newData, const int maxBytes, const int sampleNumber)
|
||||
{
|
||||
const int numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes);
|
||||
|
||||
if (numBytes > 0)
|
||||
{
|
||||
const size_t newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16);
|
||||
const int offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin());
|
||||
|
||||
data.insertMultiple (offset, 0, (int) newItemSize);
|
||||
|
||||
uint8* const d = data.begin() + offset;
|
||||
*reinterpret_cast<int32*> (d) = sampleNumber;
|
||||
*reinterpret_cast<uint16*> (d + 4) = (uint16) numBytes;
|
||||
memcpy (d + 6, newData, (size_t) numBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiBuffer::addEvents (const MidiBuffer& otherBuffer,
|
||||
const int startSample,
|
||||
const int numSamples,
|
||||
const int sampleDeltaToAdd)
|
||||
{
|
||||
Iterator i (otherBuffer);
|
||||
i.setNextSamplePosition (startSample);
|
||||
|
||||
const uint8* eventData;
|
||||
int eventSize, position;
|
||||
|
||||
while (i.getNextEvent (eventData, eventSize, position)
|
||||
&& (position < startSample + numSamples || numSamples < 0))
|
||||
{
|
||||
addEvent (eventData, eventSize, position + sampleDeltaToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
int MidiBuffer::getNumEvents() const noexcept
|
||||
{
|
||||
int n = 0;
|
||||
const uint8* const end = data.end();
|
||||
|
||||
for (const uint8* 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;
|
||||
|
||||
const uint8* const endData = data.end();
|
||||
|
||||
for (const uint8* d = data.begin();;)
|
||||
{
|
||||
const uint8* const nextOne = d + MidiBufferHelpers::getEventTotalSize (d);
|
||||
|
||||
if (nextOne >= endData)
|
||||
return MidiBufferHelpers::getEventTime (d);
|
||||
|
||||
d = nextOne;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept
|
||||
: buffer (b), data (b.data.begin())
|
||||
{
|
||||
}
|
||||
|
||||
MidiBuffer::Iterator::~Iterator() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
void MidiBuffer::Iterator::setNextSamplePosition (const int samplePosition) noexcept
|
||||
{
|
||||
data = buffer.data.begin();
|
||||
const uint8* const dataEnd = buffer.data.end();
|
||||
|
||||
while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition)
|
||||
data += MidiBufferHelpers::getEventTotalSize (data);
|
||||
}
|
||||
|
||||
bool MidiBuffer::Iterator::getNextEvent (const uint8* &midiData, int& numBytes, int& samplePosition) noexcept
|
||||
{
|
||||
if (data >= buffer.data.end())
|
||||
return false;
|
||||
|
||||
samplePosition = MidiBufferHelpers::getEventTime (data);
|
||||
const int itemSize = MidiBufferHelpers::getEventDataSize (data);
|
||||
numBytes = itemSize;
|
||||
midiData = data + sizeof (int32) + sizeof (uint16);
|
||||
data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept
|
||||
{
|
||||
if (data >= buffer.data.end())
|
||||
return false;
|
||||
|
||||
samplePosition = MidiBufferHelpers::getEventTime (data);
|
||||
const int itemSize = MidiBufferHelpers::getEventDataSize (data);
|
||||
result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition);
|
||||
data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MIDIBUFFER_H_INCLUDED
|
||||
#define JUCE_MIDIBUFFER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Holds a sequence of time-stamped midi events.
|
||||
|
||||
Analogous to the AudioSampleBuffer, 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
|
||||
*/
|
||||
class JUCE_API MidiBuffer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty MidiBuffer. */
|
||||
MidiBuffer() noexcept;
|
||||
|
||||
/** Creates a MidiBuffer containing a single midi message. */
|
||||
explicit MidiBuffer (const MidiMessage& message) noexcept;
|
||||
|
||||
/** Creates a copy of another MidiBuffer. */
|
||||
MidiBuffer (const MidiBuffer&) noexcept;
|
||||
|
||||
/** Makes a copy of another MidiBuffer. */
|
||||
MidiBuffer& operator= (const MidiBuffer&) noexcept;
|
||||
|
||||
/** Destructor */
|
||||
~MidiBuffer();
|
||||
|
||||
//==============================================================================
|
||||
/** 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 MidiBuffer::Iterator 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 MidiBuffer::Iterator object
|
||||
*/
|
||||
void 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 MidiBuffer::Iterator object
|
||||
*/
|
||||
void 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);
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Used to iterate through the events in a MidiBuffer.
|
||||
|
||||
Note that altering the buffer while an iterator is using it isn't a
|
||||
safe operation.
|
||||
|
||||
@see MidiBuffer
|
||||
*/
|
||||
class JUCE_API Iterator
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an Iterator for this MidiBuffer. */
|
||||
Iterator (const MidiBuffer&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~Iterator() 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;
|
||||
const uint8* data;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Iterator)
|
||||
};
|
||||
|
||||
/** 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)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_MIDIBUFFER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace MidiFileHelpers
|
||||
{
|
||||
static void writeVariableLengthInt (OutputStream& out, unsigned int v)
|
||||
{
|
||||
unsigned int 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;
|
||||
}
|
||||
}
|
||||
|
||||
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept
|
||||
{
|
||||
unsigned int ch = ByteOrder::bigEndianInt (data);
|
||||
data += 4;
|
||||
|
||||
if (ch != ByteOrder::bigEndianInt ("MThd"))
|
||||
{
|
||||
bool ok = false;
|
||||
|
||||
if (ch == ByteOrder::bigEndianInt ("RIFF"))
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
ch = ByteOrder::bigEndianInt (data);
|
||||
data += 4;
|
||||
|
||||
if (ch == ByteOrder::bigEndianInt ("MThd"))
|
||||
{
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! ok)
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int bytesRemaining = ByteOrder::bigEndianInt (data);
|
||||
data += 4;
|
||||
fileType = (short) ByteOrder::bigEndianShort (data);
|
||||
data += 2;
|
||||
numberOfTracks = (short) ByteOrder::bigEndianShort (data);
|
||||
data += 2;
|
||||
timeFormat = (short) ByteOrder::bigEndianShort (data);
|
||||
data += 2;
|
||||
bytesRemaining -= 6;
|
||||
data += bytesRemaining;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static double convertTicksToSeconds (const double time,
|
||||
const MidiMessageSequence& tempoEvents,
|
||||
const int timeFormat)
|
||||
{
|
||||
if (timeFormat < 0)
|
||||
return time / (-(timeFormat >> 8) * (timeFormat & 0xff));
|
||||
|
||||
double lastTime = 0.0, correctedTime = 0.0;
|
||||
const double tickLen = 1.0 / (timeFormat & 0x7fff);
|
||||
double secsPerTick = 0.5 * tickLen;
|
||||
const int numEvents = tempoEvents.getNumEvents();
|
||||
|
||||
for (int i = 0; i < numEvents; ++i)
|
||||
{
|
||||
const MidiMessage& m = tempoEvents.getEventPointer(i)->message;
|
||||
const double eventTime = m.getTimeStamp();
|
||||
|
||||
if (eventTime >= time)
|
||||
break;
|
||||
|
||||
correctedTime += (eventTime - lastTime) * secsPerTick;
|
||||
lastTime = eventTime;
|
||||
|
||||
if (m.isTempoMetaEvent())
|
||||
secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote();
|
||||
|
||||
while (i + 1 < numEvents)
|
||||
{
|
||||
const MidiMessage& m2 = tempoEvents.getEventPointer(i + 1)->message;
|
||||
|
||||
if (m2.getTimeStamp() != eventTime)
|
||||
break;
|
||||
|
||||
if (m2.isTempoMetaEvent())
|
||||
secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote();
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return correctedTime + (time - lastTime) * secsPerTick;
|
||||
}
|
||||
|
||||
// a comparator that puts all the note-offs before note-ons that have the same time
|
||||
struct Sorter
|
||||
{
|
||||
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first,
|
||||
const MidiMessageSequence::MidiEventHolder* const second) noexcept
|
||||
{
|
||||
const double diff = (first->message.getTimeStamp() - second->message.getTimeStamp());
|
||||
|
||||
if (diff > 0) return 1;
|
||||
if (diff < 0) return -1;
|
||||
if (first->message.isNoteOff() && second->message.isNoteOn()) return -1;
|
||||
if (first->message.isNoteOn() && second->message.isNoteOff()) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename MethodType>
|
||||
static void findAllMatchingEvents (const OwnedArray<MidiMessageSequence>& tracks,
|
||||
MidiMessageSequence& results,
|
||||
MethodType method)
|
||||
{
|
||||
for (int i = 0; i < tracks.size(); ++i)
|
||||
{
|
||||
const MidiMessageSequence& track = *tracks.getUnchecked(i);
|
||||
const int numEvents = track.getNumEvents();
|
||||
|
||||
for (int j = 0; j < numEvents; ++j)
|
||||
{
|
||||
const MidiMessage& m = track.getEventPointer(j)->message;
|
||||
|
||||
if ((m.*method)())
|
||||
results.addEvent (m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiFile::MidiFile()
|
||||
: timeFormat ((short) (unsigned short) 0xe728)
|
||||
{
|
||||
}
|
||||
|
||||
MidiFile::~MidiFile()
|
||||
{
|
||||
}
|
||||
|
||||
void MidiFile::clear()
|
||||
{
|
||||
tracks.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int MidiFile::getNumTracks() const noexcept
|
||||
{
|
||||
return tracks.size();
|
||||
}
|
||||
|
||||
const MidiMessageSequence* MidiFile::getTrack (const 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 (const int ticks) noexcept
|
||||
{
|
||||
timeFormat = (short) ticks;
|
||||
}
|
||||
|
||||
void MidiFile::setSmpteTimeFormat (const int framesPerSecond,
|
||||
const 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 (int i = tracks.size(); --i >= 0;)
|
||||
t = jmax (t, tracks.getUnchecked(i)->getEndTime());
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MidiFile::readFrom (InputStream& sourceStream)
|
||||
{
|
||||
clear();
|
||||
MemoryBlock data;
|
||||
|
||||
const int maxSensibleMidiFileSize = 2 * 1024 * 1024;
|
||||
|
||||
// (put a sanity-check on the file size, as midi files are generally small)
|
||||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize))
|
||||
{
|
||||
size_t size = data.getSize();
|
||||
const uint8* d = static_cast <const uint8*> (data.getData());
|
||||
short fileType, expectedTracks;
|
||||
|
||||
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks))
|
||||
{
|
||||
size -= (size_t) (d - static_cast <const uint8*> (data.getData()));
|
||||
|
||||
int track = 0;
|
||||
|
||||
while (size > 0 && track < expectedTracks)
|
||||
{
|
||||
const int chunkType = (int) ByteOrder::bigEndianInt (d);
|
||||
d += 4;
|
||||
const int chunkSize = (int) ByteOrder::bigEndianInt (d);
|
||||
d += 4;
|
||||
|
||||
if (chunkSize <= 0)
|
||||
break;
|
||||
|
||||
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk"))
|
||||
readNextTrack (d, chunkSize);
|
||||
|
||||
size -= (size_t) chunkSize + 8;
|
||||
d += chunkSize;
|
||||
++track;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MidiFile::readNextTrack (const uint8* data, int size)
|
||||
{
|
||||
double time = 0;
|
||||
uint8 lastStatusByte = 0;
|
||||
|
||||
MidiMessageSequence result;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
int bytesUsed;
|
||||
const int delay = MidiMessage::readVariableLengthVal (data, bytesUsed);
|
||||
data += bytesUsed;
|
||||
size -= bytesUsed;
|
||||
time += delay;
|
||||
|
||||
int messSize = 0;
|
||||
const MidiMessage mm (data, size, messSize, lastStatusByte, time);
|
||||
|
||||
if (messSize <= 0)
|
||||
break;
|
||||
|
||||
size -= messSize;
|
||||
data += messSize;
|
||||
|
||||
result.addEvent (mm);
|
||||
|
||||
const uint8 firstByte = *(mm.getRawData());
|
||||
if ((firstByte & 0xf0) != 0xf0)
|
||||
lastStatusByte = firstByte;
|
||||
}
|
||||
|
||||
// use a sort that puts all the note-offs before note-ons that have the same time
|
||||
MidiFileHelpers::Sorter sorter;
|
||||
result.list.sort (sorter, true);
|
||||
|
||||
addTrack (result);
|
||||
tracks.getLast()->updateMatchedPairs();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiFile::convertTimestampTicksToSeconds()
|
||||
{
|
||||
MidiMessageSequence tempoEvents;
|
||||
findAllTempoEvents (tempoEvents);
|
||||
findAllTimeSigEvents (tempoEvents);
|
||||
|
||||
if (timeFormat != 0)
|
||||
{
|
||||
for (int i = 0; i < tracks.size(); ++i)
|
||||
{
|
||||
const MidiMessageSequence& ms = *tracks.getUnchecked(i);
|
||||
|
||||
for (int j = ms.getNumEvents(); --j >= 0;)
|
||||
{
|
||||
MidiMessage& m = ms.getEventPointer(j)->message;
|
||||
m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MidiFile::writeTo (OutputStream& out, int midiFileType)
|
||||
{
|
||||
jassert (midiFileType >= 0 && midiFileType <= 2);
|
||||
|
||||
out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"));
|
||||
out.writeIntBigEndian (6);
|
||||
out.writeShortBigEndian ((short) midiFileType);
|
||||
out.writeShortBigEndian ((short) tracks.size());
|
||||
out.writeShortBigEndian (timeFormat);
|
||||
|
||||
for (int i = 0; i < tracks.size(); ++i)
|
||||
writeTrack (out, i);
|
||||
|
||||
out.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MidiFile::writeTrack (OutputStream& mainOut, const int trackNum)
|
||||
{
|
||||
MemoryOutputStream out;
|
||||
const MidiMessageSequence& ms = *tracks.getUnchecked (trackNum);
|
||||
|
||||
int lastTick = 0;
|
||||
uint8 lastStatusByte = 0;
|
||||
bool endOfTrackEventWritten = false;
|
||||
|
||||
for (int i = 0; i < ms.getNumEvents(); ++i)
|
||||
{
|
||||
const MidiMessage& mm = ms.getEventPointer(i)->message;
|
||||
|
||||
if (mm.isEndOfTrackMetaEvent())
|
||||
endOfTrackEventWritten = true;
|
||||
|
||||
const int tick = roundToInt (mm.getTimeStamp());
|
||||
const int delta = jmax (0, tick - lastTick);
|
||||
MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta);
|
||||
lastTick = tick;
|
||||
|
||||
const uint8* data = mm.getRawData();
|
||||
int dataSize = mm.getRawDataSize();
|
||||
|
||||
const uint8 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)
|
||||
const MidiMessage m (MidiMessage::endOfTrack());
|
||||
out.write (m.getRawData(), (size_t) m.getRawDataSize());
|
||||
}
|
||||
|
||||
mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"));
|
||||
mainOut.writeIntBigEndian ((int) out.getDataSize());
|
||||
mainOut << out;
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MIDIFILE_H_INCLUDED
|
||||
#define JUCE_MIDIFILE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API MidiFile
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty MidiFile object.
|
||||
*/
|
||||
MidiFile();
|
||||
|
||||
/** Destructor. */
|
||||
~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.
|
||||
|
||||
@returns true if the stream was read successfully
|
||||
*/
|
||||
bool readFrom (InputStream& sourceStream);
|
||||
|
||||
/** 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.
|
||||
@returns true if the operation succeeded.
|
||||
*/
|
||||
bool writeTo (OutputStream& destStream, int midiFileType = 1);
|
||||
|
||||
/** 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 size);
|
||||
void writeTrack (OutputStream&, int trackNum);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiFile)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_MIDIFILE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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, (int) 128)
|
||||
&& (noteStates[n] & (1 << (midiChannel - 1))) != 0;
|
||||
}
|
||||
|
||||
bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept
|
||||
{
|
||||
return isPositiveAndBelow (n, (int) 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, (int) 128));
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (isPositiveAndBelow (midiNoteNumber, (int) 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, (int) 128))
|
||||
{
|
||||
noteStates [midiNoteNumber] |= (1 << (midiChannel - 1));
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber)
|
||||
{
|
||||
if (isNoteOn (midiChannel, midiNoteNumber))
|
||||
{
|
||||
noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1));
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
else if (message.isAllNotesOff())
|
||||
{
|
||||
for (int i = 0; i < 128; ++i)
|
||||
noteOffInternal (message.getChannel(), i);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer,
|
||||
const int startSample,
|
||||
const int numSamples,
|
||||
const bool injectIndirectEvents)
|
||||
{
|
||||
MidiBuffer::Iterator i (buffer);
|
||||
MidiMessage message (0xf4, 0.0);
|
||||
int time;
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
while (i.getNextEvent (message, time))
|
||||
processNextMidiEvent (message);
|
||||
|
||||
if (injectIndirectEvents)
|
||||
{
|
||||
MidiBuffer::Iterator i2 (eventsToAdd);
|
||||
const int firstEventToAdd = eventsToAdd.getFirstEventTime();
|
||||
const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd);
|
||||
|
||||
while (i2.getNextEvent (message, time))
|
||||
{
|
||||
const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor));
|
||||
buffer.addEvent (message, startSample + pos);
|
||||
}
|
||||
}
|
||||
|
||||
eventsToAdd.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
listeners.addIfNotAlreadyThere (listener);
|
||||
}
|
||||
|
||||
void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
listeners.removeFirstMatchingValue (listener);
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MIDIKEYBOARDSTATE_H_INCLUDED
|
||||
#define JUCE_MIDIKEYBOARDSTATE_H_INCLUDED
|
||||
|
||||
class MidiKeyboardState;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives events from a MidiKeyboardState object.
|
||||
|
||||
@see MidiKeyboardState
|
||||
*/
|
||||
class JUCE_API MidiKeyboardStateListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MidiKeyboardStateListener() noexcept {}
|
||||
virtual ~MidiKeyboardStateListener() {}
|
||||
|
||||
//==============================================================================
|
||||
/** 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) = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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 individiual 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().
|
||||
*/
|
||||
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 acutally down for the given channel, this method will in fact do nothing.
|
||||
*/
|
||||
void noteOff (int midiChannel, int midiNoteNumber);
|
||||
|
||||
/** 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);
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a listener for callbacks when keys go up or down.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (MidiKeyboardStateListener* listener);
|
||||
|
||||
/** Deregisters a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (MidiKeyboardStateListener* listener);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
CriticalSection lock;
|
||||
uint16 noteStates [128];
|
||||
MidiBuffer eventsToAdd;
|
||||
Array <MidiKeyboardStateListener*> listeners;
|
||||
|
||||
void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity);
|
||||
void noteOffInternal (int midiChannel, int midiNoteNumber);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_MIDIKEYBOARDSTATE_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,903 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MIDIMESSAGE_H_INCLUDED
|
||||
#define JUCE_MIDIMESSAGE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Encapsulates a MIDI message.
|
||||
|
||||
@see MidiMessageSequence, MidiOutput, MidiInput
|
||||
*/
|
||||
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 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 active-sense 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();
|
||||
|
||||
/** Copies this message from another one. */
|
||||
MidiMessage& operator= (const MidiMessage& other);
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
MidiMessage (MidiMessage&&) noexcept;
|
||||
MidiMessage& operator= (MidiMessage&&) noexcept;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a pointer to the raw midi data.
|
||||
@see getRawDataSize
|
||||
*/
|
||||
const uint8* getRawData() const noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; }
|
||||
|
||||
/** Returns the number of bytes of data in the message.
|
||||
@see getRawData
|
||||
*/
|
||||
int getRawDataSize() const noexcept { return size; }
|
||||
|
||||
//==============================================================================
|
||||
/** 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; }
|
||||
|
||||
//==============================================================================
|
||||
/** 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 127
|
||||
@see isNoteOff
|
||||
*/
|
||||
static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity = 0) 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;
|
||||
|
||||
/** 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);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Reads a midi variable-length integer.
|
||||
|
||||
@param data the data to read the number from
|
||||
@param numBytesUsed on return, this will be set to the number of bytes that were read
|
||||
*/
|
||||
static int readVariableLengthVal (const uint8* data,
|
||||
int& numBytesUsed) 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 (const 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, const 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);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
double timeStamp;
|
||||
HeapBlock<uint8> allocatedData;
|
||||
int size;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
union
|
||||
{
|
||||
uint8 asBytes[4];
|
||||
uint32 asInt32;
|
||||
} preallocatedData;
|
||||
#endif
|
||||
|
||||
inline uint8* getData() noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; }
|
||||
uint8* allocateSpace (int);
|
||||
};
|
||||
|
||||
#endif // JUCE_MIDIMESSAGE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
MidiMessageSequence::MidiMessageSequence()
|
||||
{
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other)
|
||||
{
|
||||
list.addCopiesOf (other.list);
|
||||
updateMatchedPairs();
|
||||
}
|
||||
|
||||
MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
|
||||
{
|
||||
MidiMessageSequence otherCopy (other);
|
||||
swapWith (otherCopy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
|
||||
{
|
||||
list.swapWith (other.list);
|
||||
}
|
||||
|
||||
MidiMessageSequence::~MidiMessageSequence()
|
||||
{
|
||||
}
|
||||
|
||||
void MidiMessageSequence::clear()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getNumEvents() const noexcept
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const noexcept
|
||||
{
|
||||
return list [index];
|
||||
}
|
||||
|
||||
double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const noexcept
|
||||
{
|
||||
if (const MidiEventHolder* const meh = list [index])
|
||||
if (meh->noteOffObject != nullptr)
|
||||
return meh->noteOffObject->message.getTimeStamp();
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcept
|
||||
{
|
||||
if (const MidiEventHolder* const meh = list [index])
|
||||
return list.indexOf (meh->noteOffObject);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept
|
||||
{
|
||||
return list.indexOf (event);
|
||||
}
|
||||
|
||||
int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const noexcept
|
||||
{
|
||||
const int 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 (const MidiEventHolder* const meh = list [index])
|
||||
return meh->message.getTimeStamp();
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage,
|
||||
double timeAdjustment)
|
||||
{
|
||||
MidiEventHolder* const newOne = new MidiEventHolder (newMessage);
|
||||
|
||||
timeAdjustment += newMessage.getTimeStamp();
|
||||
newOne->message.setTimeStamp (timeAdjustment);
|
||||
|
||||
int i;
|
||||
for (i = list.size(); --i >= 0;)
|
||||
if (list.getUnchecked(i)->message.getTimeStamp() <= timeAdjustment)
|
||||
break;
|
||||
|
||||
list.insert (i + 1, newOne);
|
||||
return newOne;
|
||||
}
|
||||
|
||||
void MidiMessageSequence::deleteEvent (const int index,
|
||||
const bool deleteMatchingNoteUp)
|
||||
{
|
||||
if (isPositiveAndBelow (index, list.size()))
|
||||
{
|
||||
if (deleteMatchingNoteUp)
|
||||
deleteEvent (getIndexOfMatchingKeyUp (index), false);
|
||||
|
||||
list.remove (index);
|
||||
}
|
||||
}
|
||||
|
||||
struct MidiMessageSequenceSorter
|
||||
{
|
||||
static int compareElements (const MidiMessageSequence::MidiEventHolder* const first,
|
||||
const MidiMessageSequence::MidiEventHolder* const second) noexcept
|
||||
{
|
||||
const double diff = first->message.getTimeStamp() - second->message.getTimeStamp();
|
||||
return (diff > 0) - (diff < 0);
|
||||
}
|
||||
};
|
||||
|
||||
void MidiMessageSequence::addSequence (const MidiMessageSequence& other,
|
||||
double timeAdjustment,
|
||||
double firstAllowableTime,
|
||||
double endOfAllowableDestTimes)
|
||||
{
|
||||
firstAllowableTime -= timeAdjustment;
|
||||
endOfAllowableDestTimes -= timeAdjustment;
|
||||
|
||||
for (int i = 0; i < other.list.size(); ++i)
|
||||
{
|
||||
const MidiMessage& m = other.list.getUnchecked(i)->message;
|
||||
const double t = m.getTimeStamp();
|
||||
|
||||
if (t >= firstAllowableTime && t < endOfAllowableDestTimes)
|
||||
{
|
||||
MidiEventHolder* const newOne = new MidiEventHolder (m);
|
||||
newOne->message.setTimeStamp (timeAdjustment + t);
|
||||
|
||||
list.add (newOne);
|
||||
}
|
||||
}
|
||||
|
||||
sort();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiMessageSequence::sort() noexcept
|
||||
{
|
||||
MidiMessageSequenceSorter sorter;
|
||||
list.sort (sorter, true);
|
||||
}
|
||||
|
||||
void MidiMessageSequence::updateMatchedPairs() noexcept
|
||||
{
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
MidiEventHolder* const meh = list.getUnchecked(i);
|
||||
const MidiMessage& m1 = meh->message;
|
||||
|
||||
if (m1.isNoteOn())
|
||||
{
|
||||
meh->noteOffObject = nullptr;
|
||||
const int note = m1.getNoteNumber();
|
||||
const int chan = m1.getChannel();
|
||||
const int len = list.size();
|
||||
|
||||
for (int j = i + 1; j < len; ++j)
|
||||
{
|
||||
const MidiMessage& m = list.getUnchecked(j)->message;
|
||||
|
||||
if (m.getNoteNumber() == note && m.getChannel() == chan)
|
||||
{
|
||||
if (m.isNoteOff())
|
||||
{
|
||||
meh->noteOffObject = list[j];
|
||||
break;
|
||||
}
|
||||
else if (m.isNoteOn())
|
||||
{
|
||||
MidiEventHolder* const newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note));
|
||||
list.insert (j, newEvent);
|
||||
newEvent->message.setTimeStamp (m.getTimeStamp());
|
||||
meh->noteOffObject = newEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MidiMessageSequence::addTimeToMessages (const double delta) noexcept
|
||||
{
|
||||
for (int i = list.size(); --i >= 0;)
|
||||
{
|
||||
MidiMessage& mm = list.getUnchecked(i)->message;
|
||||
mm.setTimeStamp (mm.getTimeStamp() + delta);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
|
||||
MidiMessageSequence& destSequence,
|
||||
const bool alsoIncludeMetaEvents) const
|
||||
{
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
const MidiMessage& mm = list.getUnchecked(i)->message;
|
||||
|
||||
if (mm.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && mm.isMetaEvent()))
|
||||
destSequence.addEvent (mm);
|
||||
}
|
||||
}
|
||||
|
||||
void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
|
||||
{
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
{
|
||||
const MidiMessage& mm = list.getUnchecked(i)->message;
|
||||
|
||||
if (mm.isSysEx())
|
||||
destSequence.addEvent (mm);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiMessageSequence::createControllerUpdatesForTime (const int channelNumber,
|
||||
const double time,
|
||||
OwnedArray<MidiMessage>& dest)
|
||||
{
|
||||
bool doneProg = false;
|
||||
bool donePitchWheel = false;
|
||||
Array<int> doneControllers;
|
||||
doneControllers.ensureStorageAllocated (32);
|
||||
|
||||
for (int i = list.size(); --i >= 0;)
|
||||
{
|
||||
const MidiMessage& mm = list.getUnchecked(i)->message;
|
||||
|
||||
if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time)
|
||||
{
|
||||
if (mm.isProgramChange())
|
||||
{
|
||||
if (! doneProg)
|
||||
{
|
||||
dest.add (new MidiMessage (mm, 0.0));
|
||||
doneProg = true;
|
||||
}
|
||||
}
|
||||
else if (mm.isController())
|
||||
{
|
||||
if (! doneControllers.contains (mm.getControllerNumber()))
|
||||
{
|
||||
dest.add (new MidiMessage (mm, 0.0));
|
||||
doneControllers.add (mm.getControllerNumber());
|
||||
}
|
||||
}
|
||||
else if (mm.isPitchWheel())
|
||||
{
|
||||
if (! donePitchWheel)
|
||||
{
|
||||
dest.add (new MidiMessage (mm, 0.0));
|
||||
donePitchWheel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm)
|
||||
: message (mm), noteOffObject (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
MidiMessageSequence::MidiEventHolder::~MidiEventHolder()
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED
|
||||
#define JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
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&);
|
||||
|
||||
/** 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;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class MidiMessageSequence;
|
||||
MidiEventHolder (const 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;
|
||||
|
||||
/** 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 (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);
|
||||
|
||||
/** 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);
|
||||
|
||||
//==============================================================================
|
||||
/** 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.
|
||||
|
||||
@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,
|
||||
OwnedArray<MidiMessage>& resultMessages);
|
||||
|
||||
//==============================================================================
|
||||
/** Swaps this sequence with another one. */
|
||||
void swapWith (MidiMessageSequence&) noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class MidiFile;
|
||||
OwnedArray<MidiEventHolder> list;
|
||||
|
||||
JUCE_LEAK_DETECTOR (MidiMessageSequence)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_MIDIMESSAGESEQUENCE_H_INCLUDED
|
||||
Loading…
Add table
Add a link
Reference in a new issue