diff --git a/source/modules/water/midi/MidiFile.cpp b/source/modules/water/midi/MidiFile.cpp index b8a977f57..803335786 100644 --- a/source/modules/water/midi/MidiFile.cpp +++ b/source/modules/water/midi/MidiFile.cpp @@ -3,7 +3,7 @@ This file is part of the Water library. Copyright (c) 2016 ROLI Ltd. - Copyright (C) 2017 Filipe Coelho + Copyright (C) 2017-2022 Filipe Coelho Permission is granted to use this software under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license/ @@ -32,27 +32,6 @@ namespace water { 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); @@ -372,86 +351,4 @@ void MidiFile::convertTimestampTicksToSeconds() } } -//============================================================================== -bool MidiFile::writeTo (OutputStream& out, int midiFileType) -{ - wassert (midiFileType >= 0 && midiFileType <= 2); - - if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false; - if (! out.writeIntBigEndian (6)) return false; - if (! out.writeShortBigEndian ((short) midiFileType)) return false; - if (! out.writeShortBigEndian ((short) tracks.size())) return false; - if (! out.writeShortBigEndian (timeFormat)) return false; - - for (size_t i = 0; i < tracks.size(); ++i) - if (! writeTrack (out, i)) - return false; - - out.flush(); - return true; -} - -bool 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()); - } - - if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false; - if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false; - - mainOut << out; - - return true; -} - } diff --git a/source/modules/water/midi/MidiFile.h b/source/modules/water/midi/MidiFile.h index 3dd75c8f7..495670887 100644 --- a/source/modules/water/midi/MidiFile.h +++ b/source/modules/water/midi/MidiFile.h @@ -3,7 +3,7 @@ This file is part of the Water library. Copyright (c) 2016 ROLI Ltd. - Copyright (C) 2017 Filipe Coelho + Copyright (C) 2017-2022 Filipe Coelho Permission is granted to use this software under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license/ @@ -32,15 +32,11 @@ namespace water { //============================================================================== /** - Reads/writes standard midi format files. + Reads 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 MidiFile @@ -160,13 +156,6 @@ public: */ 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 @@ -181,7 +170,6 @@ private: short timeFormat; void readNextTrack (const uint8*, int size); - bool writeTrack (OutputStream&, int trackNum); }; } diff --git a/source/modules/water/midi/MidiMessage.cpp b/source/modules/water/midi/MidiMessage.cpp index a4354222e..25ff2a6c7 100644 --- a/source/modules/water/midi/MidiMessage.cpp +++ b/source/modules/water/midi/MidiMessage.cpp @@ -3,7 +3,7 @@ This file is part of the Water library. Copyright (c) 2016 ROLI Ltd. - Copyright (C) 2017 Filipe Coelho + Copyright (C) 2017-2022 Filipe Coelho Permission is granted to use this software under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license/ @@ -657,54 +657,6 @@ const uint8* MidiMessage::getMetaEventData() const noexcept return d + n; } -bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } -bool MidiMessage::isEndOfTrackMetaEvent() const noexcept { return getMetaEventType() == 47; } - -bool MidiMessage::isTextMetaEvent() const noexcept -{ - const int t = getMetaEventType(); - return t > 0 && t < 16; -} - -String MidiMessage::getTextFromTextMetaEvent() const -{ - const char* const textData = reinterpret_cast (getMetaEventData()); - return String (CharPointer_UTF8 (textData), - CharPointer_UTF8 (textData + getMetaEventLength())); -} - -MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) -{ - wassert (type > 0 && type < 16); - - MidiMessage result; - - const size_t textSize = text.text.sizeInBytes() - 1; - - uint8 header[8]; - size_t n = sizeof (header); - - header[--n] = (uint8) (textSize & 0x7f); - - for (size_t i = textSize; (i >>= 7) != 0;) - header[--n] = (uint8) ((i & 0x7f) | 0x80); - - header[--n] = (uint8) type; - header[--n] = 0xff; - - const size_t headerLen = sizeof (header) - n; - const int totalSize = (int) (headerLen + textSize); - - uint8* const dest = result.allocateSpace (totalSize); - result.size = totalSize; - - memcpy (dest, header + n, headerLen); - memcpy (dest + headerLen, text.text.getAddress(), textSize); - - return result; -} - -bool MidiMessage::isTrackNameEvent() const noexcept { const uint8* data = getData(); return (data[1] == 3) && (*data == 0xff); } bool MidiMessage::isTempoMetaEvent() const noexcept { const uint8* data = getData(); return (data[1] == 81) && (*data == 0xff); } bool MidiMessage::isMidiChannelMetaEvent() const noexcept { const uint8* data = getData(); return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } @@ -829,11 +781,6 @@ MidiMessage MidiMessage::keySignatureMetaEvent (int numberOfSharpsOrFlats, bool return MidiMessage (d, 5, 0.0); } -MidiMessage MidiMessage::endOfTrack() noexcept -{ - return MidiMessage (0xff, 0x2f, 0, 0.0); -} - //============================================================================== bool MidiMessage::isSongPositionPointer() const noexcept { return *getData() == 0xf2; } int MidiMessage::getSongPositionPointerMidiBeat() const noexcept { const uint8* data = getData(); return data[1] | (data[2] << 7); } @@ -963,25 +910,6 @@ MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int sec } //============================================================================== -String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC) -{ - static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; - static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; - - if (isPositiveAndBelow (note, (int) 128)) - { - String s (useSharps ? sharpNoteNames [note % 12] - : flatNoteNames [note % 12]); - - if (includeOctaveNumber) - s << (note / 12 + (octaveNumForMiddleC - 5)); - - return s; - } - - return String(); -} - double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept { return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); diff --git a/source/modules/water/midi/MidiMessage.h b/source/modules/water/midi/MidiMessage.h index 6b78d55f8..c091e92e4 100644 --- a/source/modules/water/midi/MidiMessage.h +++ b/source/modules/water/midi/MidiMessage.h @@ -3,7 +3,7 @@ This file is part of the Water library. Copyright (c) 2016 ROLI Ltd. - Copyright (C) 2017 Filipe Coelho + Copyright (C) 2017-2022 Filipe Coelho Permission is granted to use this software under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license/ @@ -505,8 +505,7 @@ public: Meta-events are things like tempo changes, track names, etc. - @see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, - isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, + @see getMetaEventType, isTempoMetaEvent, isTimeSignatureMetaEvent, isKeySignatureMetaEvent, isMidiChannelMetaEvent */ bool isMetaEvent() const noexcept; @@ -515,8 +514,7 @@ public: If the message isn't a meta-event, this will return -1. - @see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, - isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, + @see isMetaEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, isKeySignatureMetaEvent, isMidiChannelMetaEvent */ int getMetaEventType() const noexcept; @@ -531,36 +529,6 @@ public: */ 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 @@ -847,25 +815,6 @@ public: static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; //============================================================================== - /** Returns the name of a midi note number. - - E.g "C", "D#", etc. - - @param noteNumber the midi note number, 0 to 127 - @param useSharps if true, sharpened notes are used, e.g. "C#", otherwise - they'll be flattened, e.g. "Db" - @param includeOctaveNumber if true, the octave number will be appended to the string, - e.g. "C#4" - @param octaveNumForMiddleC if an octave number is being appended, this indicates the - number that will be used for middle C's octave - - @see getMidiNoteInHertz - */ - static String getMidiNoteName (int noteNumber, - bool useSharps, - bool includeOctaveNumber, - int octaveNumForMiddleC); - /** Returns the frequency of a midi note number. The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. diff --git a/source/modules/water/midi/MidiMessageSequence.cpp b/source/modules/water/midi/MidiMessageSequence.cpp index 634737c87..ecd0c8117 100644 --- a/source/modules/water/midi/MidiMessageSequence.cpp +++ b/source/modules/water/midi/MidiMessageSequence.cpp @@ -3,7 +3,7 @@ This file is part of the Water library. Copyright (c) 2016 ROLI Ltd. - Copyright (C) 2017-2019 Filipe Coelho + Copyright (C) 2017-2022 Filipe Coelho Permission is granted to use this software under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license/ @@ -68,40 +68,6 @@ MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (cons 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 (const 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 { @@ -139,18 +105,6 @@ MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiM return newOne; } -void MidiMessageSequence::deleteEvent (const int index, - const bool deleteMatchingNoteUp) -{ - if (isPositiveAndBelow (index, static_cast(list.size()))) - { - if (deleteMatchingNoteUp) - deleteEvent (getIndexOfMatchingKeyUp (index), false); - - list.remove (index); - } -} - struct MidiMessageSequenceSorter { static int compareElements (const MidiMessageSequence::MidiEventHolder* const first, @@ -175,28 +129,6 @@ void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double sort(); } -void MidiMessageSequence::addSequence (const MidiMessageSequence& other, - double timeAdjustment, - double firstAllowableTime, - double endOfAllowableDestTimes) -{ - for (int i = 0; i < static_cast(other.list.size()); ++i) - { - const MidiMessage& m = other.list.getUnchecked(i)->message; - const double t = m.getTimeStamp() + timeAdjustment; - - if (t >= firstAllowableTime && t < endOfAllowableDestTimes) - { - MidiEventHolder* const newOne = new MidiEventHolder (m); - newOne->message.setTimeStamp (t); - - list.add (newOne); - } - } - - sort(); -} - //============================================================================== void MidiMessageSequence::sort() noexcept { @@ -243,93 +175,6 @@ void MidiMessageSequence::updateMatchedPairs() noexcept } } -void MidiMessageSequence::addTimeToMessages (const double delta) noexcept -{ - for (int i = static_cast(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 < static_cast(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 < static_cast(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, Array& dest) -{ - bool doneProg = false; - bool donePitchWheel = false; - bool doneControllers[128] = { 0 }; - - for (int i = list.size(); --i >= 0;) - { - const MidiMessage& mm = list.getUnchecked(i)->message; - - if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) - { - if (mm.isProgramChange() && ! doneProg) - { - doneProg = true; - dest.add (MidiMessage (mm, 0.0)); - } - else if (mm.isPitchWheel() && ! donePitchWheel) - { - donePitchWheel = true; - dest.add (MidiMessage (mm, 0.0)); - } - else if (mm.isController()) - { - const int controllerNumber = mm.getControllerNumber(); - wassert (isPositiveAndBelow (controllerNumber, 128)); - - if (! doneControllers[controllerNumber]) - { - doneControllers[controllerNumber] = true; - dest.add (MidiMessage (mm, 0.0)); - } - } - } - } -} - - //============================================================================== MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm), noteOffObject (nullptr) diff --git a/source/modules/water/midi/MidiMessageSequence.h b/source/modules/water/midi/MidiMessageSequence.h index a8405074f..4954f893f 100644 --- a/source/modules/water/midi/MidiMessageSequence.h +++ b/source/modules/water/midi/MidiMessageSequence.h @@ -103,27 +103,6 @@ public: /** 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 (const MidiEventHolder* event) const noexcept; - - /** Returns the index of the first event on or after the given timestamp. - If the time is beyond the end of the sequence, this will return the - number of events. - */ - int getNextIndexAtTime (double timeStamp) const noexcept; - //============================================================================== /** Returns the timestamp of the first event in the sequence. @see getEndTime @@ -156,34 +135,6 @@ public: 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); - /** Merges another sequence into this one. Remember to call updateMatchedPairs() after using this method. @@ -209,62 +160,6 @@ public: */ 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, - Array& resultMessages); - //============================================================================== /** Swaps this sequence with another one. */ void swapWith (MidiMessageSequence&) noexcept; diff --git a/source/native-plugins/midi-file.cpp b/source/native-plugins/midi-file.cpp index 90b0a1d91..1fa5eb8cc 100644 --- a/source/native-plugins/midi-file.cpp +++ b/source/native-plugins/midi-file.cpp @@ -384,12 +384,12 @@ private: for (size_t i=0; igetNumEvents(); jgetEventPointer(j)); + const MidiMessageSequence::MidiEventHolder* const midiEventHolder = track->getEventPointer(j); CARLA_SAFE_ASSERT_CONTINUE(midiEventHolder != nullptr); const MidiMessage& midiMessage(midiEventHolder->message);