1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 The code included in this file is provided under the terms of the ISC license 11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission 12 To use, copy, modify, and/or distribute this software for any purpose with or 13 without fee is hereby granted provided that the above copyright notice and 14 this permission notice appear in all copies. 15 16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 18 DISCLAIMED. 19 20 ============================================================================== 21 */ 22 23 namespace juce 24 { 25 26 //============================================================================== 27 /** 28 A sequence of timestamped midi messages. 29 30 This allows the sequence to be manipulated, and also to be read from and 31 written to a standard midi file. 32 33 @see MidiMessage, MidiFile 34 35 @tags{Audio} 36 */ 37 class JUCE_API MidiMessageSequence 38 { 39 public: 40 //============================================================================== 41 /** Creates an empty midi sequence object. */ 42 MidiMessageSequence(); 43 44 /** Creates a copy of another sequence. */ 45 MidiMessageSequence (const MidiMessageSequence&); 46 47 /** Replaces this sequence with another one. */ 48 MidiMessageSequence& operator= (const MidiMessageSequence&); 49 50 /** Move constructor */ 51 MidiMessageSequence (MidiMessageSequence&&) noexcept; 52 53 /** Move assignment operator */ 54 MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; 55 56 /** Destructor. */ 57 ~MidiMessageSequence(); 58 59 //============================================================================== 60 /** Structure used to hold midi events in the sequence. 61 62 These structures act as 'handles' on the events as they are moved about in 63 the list, and make it quick to find the matching note-offs for note-on events. 64 65 @see MidiMessageSequence::getEventPointer 66 */ 67 class MidiEventHolder 68 { 69 public: 70 //============================================================================== 71 /** Destructor. */ 72 ~MidiEventHolder(); 73 74 /** The message itself, whose timestamp is used to specify the event's time. */ 75 MidiMessage message; 76 77 /** The matching note-off event (if this is a note-on event). 78 79 If this isn't a note-on, this pointer will be nullptr. 80 81 Use the MidiMessageSequence::updateMatchedPairs() method to keep these 82 note-offs up-to-date after events have been moved around in the sequence 83 or deleted. 84 */ 85 MidiEventHolder* noteOffObject = nullptr; 86 87 private: 88 //============================================================================== 89 friend class MidiMessageSequence; 90 MidiEventHolder (const MidiMessage&); 91 MidiEventHolder (MidiMessage&&); 92 JUCE_LEAK_DETECTOR (MidiEventHolder) 93 }; 94 95 //============================================================================== 96 /** Clears the sequence. */ 97 void clear(); 98 99 /** Returns the number of events in the sequence. */ 100 int getNumEvents() const noexcept; 101 102 /** Returns a pointer to one of the events. */ 103 MidiEventHolder* getEventPointer (int index) const noexcept; 104 105 /** Iterator for the list of MidiEventHolders */ 106 MidiEventHolder** begin() noexcept; 107 108 /** Iterator for the list of MidiEventHolders */ 109 MidiEventHolder* const* begin() const noexcept; 110 111 /** Iterator for the list of MidiEventHolders */ 112 MidiEventHolder** end() noexcept; 113 114 /** Iterator for the list of MidiEventHolders */ 115 MidiEventHolder* const* end() const noexcept; 116 117 /** Returns the time of the note-up that matches the note-on at this index. 118 If the event at this index isn't a note-on, it'll just return 0. 119 @see MidiMessageSequence::MidiEventHolder::noteOffObject 120 */ 121 double getTimeOfMatchingKeyUp (int index) const noexcept; 122 123 /** Returns the index of the note-up that matches the note-on at this index. 124 If the event at this index isn't a note-on, it'll just return -1. 125 @see MidiMessageSequence::MidiEventHolder::noteOffObject 126 */ 127 int getIndexOfMatchingKeyUp (int index) const noexcept; 128 129 /** Returns the index of an event. */ 130 int getIndexOf (const MidiEventHolder* event) const noexcept; 131 132 /** Returns the index of the first event on or after the given timestamp. 133 If the time is beyond the end of the sequence, this will return the 134 number of events. 135 */ 136 int getNextIndexAtTime (double timeStamp) const noexcept; 137 138 //============================================================================== 139 /** Returns the timestamp of the first event in the sequence. 140 @see getEndTime 141 */ 142 double getStartTime() const noexcept; 143 144 /** Returns the timestamp of the last event in the sequence. 145 @see getStartTime 146 */ 147 double getEndTime() const noexcept; 148 149 /** Returns the timestamp of the event at a given index. 150 If the index is out-of-range, this will return 0.0 151 */ 152 double getEventTime (int index) const noexcept; 153 154 //============================================================================== 155 /** Inserts a midi message into the sequence. 156 157 The index at which the new message gets inserted will depend on its timestamp, 158 because the sequence is kept sorted. 159 160 Remember to call updateMatchedPairs() after adding note-on events. 161 162 @param newMessage the new message to add (an internal copy will be made) 163 @param timeAdjustment an optional value to add to the timestamp of the message 164 that will be inserted 165 @see updateMatchedPairs 166 */ 167 MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0); 168 169 /** Inserts a midi message into the sequence. 170 171 The index at which the new message gets inserted will depend on its timestamp, 172 because the sequence is kept sorted. 173 174 Remember to call updateMatchedPairs() after adding note-on events. 175 176 @param newMessage the new message to add (an internal copy will be made) 177 @param timeAdjustment an optional value to add to the timestamp of the message 178 that will be inserted 179 @see updateMatchedPairs 180 */ 181 MidiEventHolder* addEvent (MidiMessage&& newMessage, double timeAdjustment = 0); 182 183 /** Deletes one of the events in the sequence. 184 185 Remember to call updateMatchedPairs() after removing events. 186 187 @param index the index of the event to delete 188 @param deleteMatchingNoteUp whether to also remove the matching note-off 189 if the event you're removing is a note-on 190 */ 191 void deleteEvent (int index, bool deleteMatchingNoteUp); 192 193 /** Merges another sequence into this one. 194 Remember to call updateMatchedPairs() after using this method. 195 196 @param other the sequence to add from 197 @param timeAdjustmentDelta an amount to add to the timestamps of the midi events 198 as they are read from the other sequence 199 @param firstAllowableDestTime events will not be added if their time is earlier 200 than this time. (This is after their time has been adjusted 201 by the timeAdjustmentDelta) 202 @param endOfAllowableDestTimes events will not be added if their time is equal to 203 or greater than this time. (This is after their time has 204 been adjusted by the timeAdjustmentDelta) 205 */ 206 void addSequence (const MidiMessageSequence& other, 207 double timeAdjustmentDelta, 208 double firstAllowableDestTime, 209 double endOfAllowableDestTimes); 210 211 /** Merges another sequence into this one. 212 Remember to call updateMatchedPairs() after using this method. 213 214 @param other the sequence to add from 215 @param timeAdjustmentDelta an amount to add to the timestamps of the midi events 216 as they are read from the other sequence 217 */ 218 void addSequence (const MidiMessageSequence& other, 219 double timeAdjustmentDelta); 220 221 //============================================================================== 222 /** Makes sure all the note-on and note-off pairs are up-to-date. 223 224 Call this after re-ordering messages or deleting/adding messages, and it 225 will scan the list and make sure all the note-offs in the MidiEventHolder 226 structures are pointing at the correct ones. 227 */ 228 void updateMatchedPairs() noexcept; 229 230 /** Forces a sort of the sequence. 231 You may need to call this if you've manually modified the timestamps of some 232 events such that the overall order now needs updating. 233 */ 234 void sort() noexcept; 235 236 //============================================================================== 237 /** Copies all the messages for a particular midi channel to another sequence. 238 239 @param channelNumberToExtract the midi channel to look for, in the range 1 to 16 240 @param destSequence the sequence that the chosen events should be copied to 241 @param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific 242 channel) will also be copied across. 243 @see extractSysExMessages 244 */ 245 void extractMidiChannelMessages (int channelNumberToExtract, 246 MidiMessageSequence& destSequence, 247 bool alsoIncludeMetaEvents) const; 248 249 /** Copies all midi sys-ex messages to another sequence. 250 @param destSequence this is the sequence to which any sys-exes in this sequence 251 will be added 252 @see extractMidiChannelMessages 253 */ 254 void extractSysExMessages (MidiMessageSequence& destSequence) const; 255 256 /** Removes any messages in this sequence that have a specific midi channel. 257 @param channelNumberToRemove the midi channel to look for, in the range 1 to 16 258 */ 259 void deleteMidiChannelMessages (int channelNumberToRemove); 260 261 /** Removes any sys-ex messages from this sequence. */ 262 void deleteSysExMessages(); 263 264 /** Adds an offset to the timestamps of all events in the sequence. 265 @param deltaTime the amount to add to each timestamp. 266 */ 267 void addTimeToMessages (double deltaTime) noexcept; 268 269 //============================================================================== 270 /** Scans through the sequence to determine the state of any midi controllers at 271 a given time. 272 273 This will create a sequence of midi controller changes that can be 274 used to set all midi controllers to the state they would be in at the 275 specified time within this sequence. 276 277 As well as controllers, it will also recreate the midi program number 278 and pitch bend position. 279 280 @param channelNumber the midi channel to look for, in the range 1 to 16. Controllers 281 for other channels will be ignored. 282 @param time the time at which you want to find out the state - there are 283 no explicit units for this time measurement, it's the same units 284 as used for the timestamps of the messages 285 @param resultMessages an array to which midi controller-change messages will be added. This 286 will be the minimum number of controller changes to recreate the 287 state at the required time. 288 */ 289 void createControllerUpdatesForTime (int channelNumber, double time, 290 Array<MidiMessage>& resultMessages); 291 292 //============================================================================== 293 /** Swaps this sequence with another one. */ 294 void swapWith (MidiMessageSequence&) noexcept; 295 296 private: 297 //============================================================================== 298 friend class MidiFile; 299 OwnedArray<MidiEventHolder> list; 300 301 MidiEventHolder* addEvent (MidiEventHolder*, double); 302 303 JUCE_LEAK_DETECTOR (MidiMessageSequence) 304 }; 305 306 } // namespace juce 307