OpenShot Library | libopenshot-audio 0.2.0
juce_MidiMessageSequence.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
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
23namespace juce
24{
25
26MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {}
27MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {}
28MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {}
29
30//==============================================================================
31MidiMessageSequence::MidiMessageSequence()
32{
33}
34
35MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other)
36{
37 list.addCopiesOf (other.list);
38
39 for (int i = 0; i < list.size(); ++i)
40 {
41 auto noteOffIndex = other.getIndexOfMatchingKeyUp (i);
42
43 if (noteOffIndex >= 0)
44 list.getUnchecked(i)->noteOffObject = list.getUnchecked (noteOffIndex);
45 }
46}
47
48MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
49{
51 swapWith (otherCopy);
52 return *this;
53}
54
55MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept
56 : list (std::move (other.list))
57{
58}
59
60MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept
61{
62 list = std::move (other.list);
63 return *this;
64}
65
66MidiMessageSequence::~MidiMessageSequence()
67{
68}
69
70void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
71{
72 list.swapWith (other.list);
73}
74
75void MidiMessageSequence::clear()
76{
77 list.clear();
78}
79
80int MidiMessageSequence::getNumEvents() const noexcept
81{
82 return list.size();
83}
84
85MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept
86{
87 return list[index];
88}
89
90MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() const noexcept { return list.begin(); }
91MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() const noexcept { return list.end(); }
92
93double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept
94{
95 if (auto* meh = list[index])
96 if (auto* noteOff = meh->noteOffObject)
97 return noteOff->message.getTimeStamp();
98
99 return 0;
100}
101
102int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept
103{
104 if (auto* meh = list[index])
105 {
106 if (auto* noteOff = meh->noteOffObject)
107 {
108 for (int i = index; i < list.size(); ++i)
109 if (list.getUnchecked(i) == noteOff)
110 return i;
111
112 jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence
113 }
114 }
115
116 return -1;
117}
118
119int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept
120{
121 return list.indexOf (event);
122}
123
124int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept
125{
126 auto numEvents = list.size();
127 int i;
128
129 for (i = 0; i < numEvents; ++i)
130 if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp)
131 break;
132
133 return i;
134}
135
136//==============================================================================
137double MidiMessageSequence::getStartTime() const noexcept
138{
139 return getEventTime (0);
140}
141
142double MidiMessageSequence::getEndTime() const noexcept
143{
144 return getEventTime (list.size() - 1);
145}
146
147double MidiMessageSequence::getEventTime (const int index) const noexcept
148{
149 if (auto* meh = list[index])
150 return meh->message.getTimeStamp();
151
152 return 0;
153}
154
155//==============================================================================
156MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment)
157{
158 newEvent->message.addToTimeStamp (timeAdjustment);
159 auto time = newEvent->message.getTimeStamp();
160 int i;
161
162 for (i = list.size(); --i >= 0;)
163 if (list.getUnchecked(i)->message.getTimeStamp() <= time)
164 break;
165
166 list.insert (i + 1, newEvent);
167 return newEvent;
168}
169
170MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment)
171{
172 return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
173}
174
176{
177 return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment);
178}
179
180void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp)
181{
182 if (isPositiveAndBelow (index, list.size()))
183 {
185 deleteEvent (getIndexOfMatchingKeyUp (index), false);
186
187 list.remove (index);
188 }
189}
190
191void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment)
192{
193 for (auto* m : other)
194 {
195 auto newOne = new MidiEventHolder (m->message);
196 newOne->message.addToTimeStamp (timeAdjustment);
197 list.add (newOne);
198 }
199
200 sort();
201}
202
203void MidiMessageSequence::addSequence (const MidiMessageSequence& other,
204 double timeAdjustment,
205 double firstAllowableTime,
207{
208 for (auto* m : other)
209 {
210 auto t = m->message.getTimeStamp() + timeAdjustment;
211
213 {
214 auto newOne = new MidiEventHolder (m->message);
215 newOne->message.setTimeStamp (t);
216 list.add (newOne);
217 }
218 }
219
220 sort();
221}
222
223void MidiMessageSequence::sort() noexcept
224{
225 std::stable_sort (list.begin(), list.end(),
226 [] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); });
227}
228
229void MidiMessageSequence::updateMatchedPairs() noexcept
230{
231 for (int i = 0; i < list.size(); ++i)
232 {
233 auto* meh = list.getUnchecked(i);
234 auto& m1 = meh->message;
235
236 if (m1.isNoteOn())
237 {
238 meh->noteOffObject = nullptr;
239 auto note = m1.getNoteNumber();
240 auto chan = m1.getChannel();
241 auto len = list.size();
242
243 for (int j = i + 1; j < len; ++j)
244 {
245 auto* meh2 = list.getUnchecked(j);
246 auto& m = meh2->message;
247
248 if (m.getNoteNumber() == note && m.getChannel() == chan)
249 {
250 if (m.isNoteOff())
251 {
252 meh->noteOffObject = meh2;
253 break;
254 }
255
256 if (m.isNoteOn())
257 {
258 auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note));
259 list.insert (j, newEvent);
260 newEvent->message.setTimeStamp (m.getTimeStamp());
261 meh->noteOffObject = newEvent;
262 break;
263 }
264 }
265 }
266 }
267 }
268}
269
270void MidiMessageSequence::addTimeToMessages (double delta) noexcept
271{
272 if (delta != 0)
273 for (auto* m : list)
274 m->message.addToTimeStamp (delta);
275}
276
277//==============================================================================
278void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
280 const bool alsoIncludeMetaEvents) const
281{
282 for (auto* meh : list)
283 if (meh->message.isForChannel (channelNumberToExtract)
284 || (alsoIncludeMetaEvents && meh->message.isMetaEvent()))
285 destSequence.addEvent (meh->message);
286}
287
288void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
289{
290 for (auto* meh : list)
291 if (meh->message.isSysEx())
292 destSequence.addEvent (meh->message);
293}
294
295void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove)
296{
297 for (int i = list.size(); --i >= 0;)
298 if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove))
299 list.remove(i);
300}
301
302void MidiMessageSequence::deleteSysExMessages()
303{
304 for (int i = list.size(); --i >= 0;)
305 if (list.getUnchecked(i)->message.isSysEx())
306 list.remove(i);
307}
308
309//==============================================================================
310void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, double time, Array<MidiMessage>& dest)
311{
312 bool doneProg = false;
313 bool donePitchWheel = false;
314 bool doneControllers[128] = {};
315
316 for (int i = list.size(); --i >= 0;)
317 {
318 auto& mm = list.getUnchecked(i)->message;
319
320 if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time)
321 {
322 if (mm.isProgramChange() && ! doneProg)
323 {
324 doneProg = true;
325 dest.add (MidiMessage (mm, 0.0));
326 }
327 else if (mm.isPitchWheel() && ! donePitchWheel)
328 {
329 donePitchWheel = true;
330 dest.add (MidiMessage (mm, 0.0));
331 }
332 else if (mm.isController())
333 {
334 auto controllerNumber = mm.getControllerNumber();
335 jassert (isPositiveAndBelow (controllerNumber, 128));
336
338 {
340 dest.add (MidiMessage (mm, 0.0));
341 }
342 }
343 }
344 }
345}
346
347#if JUCE_UNIT_TESTS
348
350{
351 MidiMessageSequenceTest() : juce::UnitTest ("MidiMessageSequence") {}
352
353 void runTest() override
354 {
355 MidiMessageSequence s;
356
357 s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0));
358 s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0));
359 s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0));
360 s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0));
361
362 beginTest ("Start & end time");
363 expectEquals (s.getStartTime(), 0.0);
364 expectEquals (s.getEndTime(), 8.0);
365 expectEquals (s.getEventTime (1), 2.0);
366
367 beginTest ("Matching note off & ons");
368 s.updateMatchedPairs();
369 expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0);
370 expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0);
371 expectEquals (s.getIndexOfMatchingKeyUp (0), 2);
372 expectEquals (s.getIndexOfMatchingKeyUp (1), 3);
373
374 beginTest ("Time & indeces");
375 expectEquals (s.getNextIndexAtTime (0.5), 1);
376 expectEquals (s.getNextIndexAtTime (2.5), 2);
377 expectEquals (s.getNextIndexAtTime (9.0), 4);
378
379 beginTest ("Deleting events");
380 s.deleteEvent (0, true);
381 expectEquals (s.getNumEvents(), 2);
382
383 beginTest ("Merging sequences");
384 MidiMessageSequence s2;
385 s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0));
386 s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0));
387 s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0));
388 s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0));
389 s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0));
390 s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0));
391
392 s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off
393 s.updateMatchedPairs();
394
395 expectEquals (s.getNumEvents(), 7);
396 expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off
397 expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0);
398 }
399};
400
401static MidiMessageSequenceTest midiMessageSequenceTests;
402
403#endif
404
405} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:256
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:219
ElementType * begin() const noexcept
Returns a pointer to the first element in the array.
Definition juce_Array.h:309
void remove(int indexToRemove)
Removes an element from the array.
Definition juce_Array.h:724
void insert(int indexToInsertAt, ParameterType newElement)
Inserts a new element into the array at a given position.
Definition juce_Array.h:419
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:375
ElementType * end() const noexcept
Returns a pointer to the element which follows the last element in the array.
Definition juce_Array.h:317
Structure used to hold midi events in the sequence.
A sequence of timestamped midi messages.
Encapsulates a MIDI message.
This is a base class for classes that perform a unit test.