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