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
23 namespace juce
24 {
25
MidiEventHolder(const MidiMessage & mm)26 MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {}
MidiEventHolder(MidiMessage && mm)27 MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {}
~MidiEventHolder()28 MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {}
29
30 //==============================================================================
MidiMessageSequence()31 MidiMessageSequence::MidiMessageSequence()
32 {
33 }
34
MidiMessageSequence(const MidiMessageSequence & other)35 MidiMessageSequence::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
operator =(const MidiMessageSequence & other)48 MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
49 {
50 MidiMessageSequence otherCopy (other);
51 swapWith (otherCopy);
52 return *this;
53 }
54
MidiMessageSequence(MidiMessageSequence && other)55 MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept
56 : list (std::move (other.list))
57 {
58 }
59
operator =(MidiMessageSequence && other)60 MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept
61 {
62 list = std::move (other.list);
63 return *this;
64 }
65
~MidiMessageSequence()66 MidiMessageSequence::~MidiMessageSequence()
67 {
68 }
69
swapWith(MidiMessageSequence & other)70 void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
71 {
72 list.swapWith (other.list);
73 }
74
clear()75 void MidiMessageSequence::clear()
76 {
77 list.clear();
78 }
79
getNumEvents() const80 int MidiMessageSequence::getNumEvents() const noexcept
81 {
82 return list.size();
83 }
84
getEventPointer(int index) const85 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept
86 {
87 return list[index];
88 }
89
begin()90 MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); }
begin() const91 MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); }
end()92 MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); }
end() const93 MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); }
94
getTimeOfMatchingKeyUp(int index) const95 double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept
96 {
97 if (auto* meh = list[index])
98 if (auto* noteOff = meh->noteOffObject)
99 return noteOff->message.getTimeStamp();
100
101 return 0;
102 }
103
getIndexOfMatchingKeyUp(int index) const104 int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept
105 {
106 if (auto* meh = list[index])
107 {
108 if (auto* noteOff = meh->noteOffObject)
109 {
110 for (int i = index; i < list.size(); ++i)
111 if (list.getUnchecked(i) == noteOff)
112 return i;
113
114 jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence
115 }
116 }
117
118 return -1;
119 }
120
getIndexOf(const MidiEventHolder * event) const121 int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept
122 {
123 return list.indexOf (event);
124 }
125
getNextIndexAtTime(double timeStamp) const126 int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept
127 {
128 auto numEvents = list.size();
129 int i;
130
131 for (i = 0; i < numEvents; ++i)
132 if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp)
133 break;
134
135 return i;
136 }
137
138 //==============================================================================
getStartTime() const139 double MidiMessageSequence::getStartTime() const noexcept
140 {
141 return getEventTime (0);
142 }
143
getEndTime() const144 double MidiMessageSequence::getEndTime() const noexcept
145 {
146 return getEventTime (list.size() - 1);
147 }
148
getEventTime(const int index) const149 double MidiMessageSequence::getEventTime (const int index) const noexcept
150 {
151 if (auto* meh = list[index])
152 return meh->message.getTimeStamp();
153
154 return 0;
155 }
156
157 //==============================================================================
addEvent(MidiEventHolder * newEvent,double timeAdjustment)158 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment)
159 {
160 newEvent->message.addToTimeStamp (timeAdjustment);
161 auto time = newEvent->message.getTimeStamp();
162 int i;
163
164 for (i = list.size(); --i >= 0;)
165 if (list.getUnchecked(i)->message.getTimeStamp() <= time)
166 break;
167
168 list.insert (i + 1, newEvent);
169 return newEvent;
170 }
171
addEvent(const MidiMessage & newMessage,double timeAdjustment)172 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment)
173 {
174 return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
175 }
176
addEvent(MidiMessage && newMessage,double timeAdjustment)177 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment)
178 {
179 return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment);
180 }
181
deleteEvent(int index,bool deleteMatchingNoteUp)182 void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp)
183 {
184 if (isPositiveAndBelow (index, list.size()))
185 {
186 if (deleteMatchingNoteUp)
187 deleteEvent (getIndexOfMatchingKeyUp (index), false);
188
189 list.remove (index);
190 }
191 }
192
addSequence(const MidiMessageSequence & other,double timeAdjustment)193 void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment)
194 {
195 for (auto* m : other)
196 {
197 auto newOne = new MidiEventHolder (m->message);
198 newOne->message.addToTimeStamp (timeAdjustment);
199 list.add (newOne);
200 }
201
202 sort();
203 }
204
addSequence(const MidiMessageSequence & other,double timeAdjustment,double firstAllowableTime,double endOfAllowableDestTimes)205 void MidiMessageSequence::addSequence (const MidiMessageSequence& other,
206 double timeAdjustment,
207 double firstAllowableTime,
208 double endOfAllowableDestTimes)
209 {
210 for (auto* m : other)
211 {
212 auto t = m->message.getTimeStamp() + timeAdjustment;
213
214 if (t >= firstAllowableTime && t < endOfAllowableDestTimes)
215 {
216 auto newOne = new MidiEventHolder (m->message);
217 newOne->message.setTimeStamp (t);
218 list.add (newOne);
219 }
220 }
221
222 sort();
223 }
224
sort()225 void MidiMessageSequence::sort() noexcept
226 {
227 std::stable_sort (list.begin(), list.end(),
228 [] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); });
229 }
230
updateMatchedPairs()231 void MidiMessageSequence::updateMatchedPairs() noexcept
232 {
233 for (int i = 0; i < list.size(); ++i)
234 {
235 auto* meh = list.getUnchecked(i);
236 auto& m1 = meh->message;
237
238 if (m1.isNoteOn())
239 {
240 meh->noteOffObject = nullptr;
241 auto note = m1.getNoteNumber();
242 auto chan = m1.getChannel();
243 auto len = list.size();
244
245 for (int j = i + 1; j < len; ++j)
246 {
247 auto* meh2 = list.getUnchecked(j);
248 auto& m = meh2->message;
249
250 if (m.getNoteNumber() == note && m.getChannel() == chan)
251 {
252 if (m.isNoteOff())
253 {
254 meh->noteOffObject = meh2;
255 break;
256 }
257
258 if (m.isNoteOn())
259 {
260 auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note));
261 list.insert (j, newEvent);
262 newEvent->message.setTimeStamp (m.getTimeStamp());
263 meh->noteOffObject = newEvent;
264 break;
265 }
266 }
267 }
268 }
269 }
270 }
271
addTimeToMessages(double delta)272 void MidiMessageSequence::addTimeToMessages (double delta) noexcept
273 {
274 if (delta != 0)
275 for (auto* m : list)
276 m->message.addToTimeStamp (delta);
277 }
278
279 //==============================================================================
extractMidiChannelMessages(const int channelNumberToExtract,MidiMessageSequence & destSequence,const bool alsoIncludeMetaEvents) const280 void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
281 MidiMessageSequence& destSequence,
282 const bool alsoIncludeMetaEvents) const
283 {
284 for (auto* meh : list)
285 if (meh->message.isForChannel (channelNumberToExtract)
286 || (alsoIncludeMetaEvents && meh->message.isMetaEvent()))
287 destSequence.addEvent (meh->message);
288 }
289
extractSysExMessages(MidiMessageSequence & destSequence) const290 void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
291 {
292 for (auto* meh : list)
293 if (meh->message.isSysEx())
294 destSequence.addEvent (meh->message);
295 }
296
deleteMidiChannelMessages(const int channelNumberToRemove)297 void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove)
298 {
299 for (int i = list.size(); --i >= 0;)
300 if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove))
301 list.remove(i);
302 }
303
deleteSysExMessages()304 void MidiMessageSequence::deleteSysExMessages()
305 {
306 for (int i = list.size(); --i >= 0;)
307 if (list.getUnchecked(i)->message.isSysEx())
308 list.remove(i);
309 }
310
311 //==============================================================================
createControllerUpdatesForTime(int channelNumber,double time,Array<MidiMessage> & dest)312 void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, double time, Array<MidiMessage>& dest)
313 {
314 bool doneProg = false;
315 bool donePitchWheel = false;
316 bool doneControllers[128] = {};
317
318 for (int i = list.size(); --i >= 0;)
319 {
320 auto& mm = list.getUnchecked(i)->message;
321
322 if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time)
323 {
324 if (mm.isProgramChange() && ! doneProg)
325 {
326 doneProg = true;
327 dest.add (MidiMessage (mm, 0.0));
328 }
329 else if (mm.isPitchWheel() && ! donePitchWheel)
330 {
331 donePitchWheel = true;
332 dest.add (MidiMessage (mm, 0.0));
333 }
334 else if (mm.isController())
335 {
336 auto controllerNumber = mm.getControllerNumber();
337 jassert (isPositiveAndBelow (controllerNumber, 128));
338
339 if (! doneControllers[controllerNumber])
340 {
341 doneControllers[controllerNumber] = true;
342 dest.add (MidiMessage (mm, 0.0));
343 }
344 }
345 }
346 }
347 }
348
349
350 //==============================================================================
351 //==============================================================================
352 #if JUCE_UNIT_TESTS
353
354 struct MidiMessageSequenceTest : public UnitTest
355 {
MidiMessageSequenceTestjuce::MidiMessageSequenceTest356 MidiMessageSequenceTest()
357 : UnitTest ("MidiMessageSequence", UnitTestCategories::midi)
358 {}
359
runTestjuce::MidiMessageSequenceTest360 void runTest() override
361 {
362 MidiMessageSequence s;
363
364 s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0));
365 s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0));
366 s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0));
367 s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0));
368
369 beginTest ("Start & end time");
370 expectEquals (s.getStartTime(), 0.0);
371 expectEquals (s.getEndTime(), 8.0);
372 expectEquals (s.getEventTime (1), 2.0);
373
374 beginTest ("Matching note off & ons");
375 s.updateMatchedPairs();
376 expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0);
377 expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0);
378 expectEquals (s.getIndexOfMatchingKeyUp (0), 2);
379 expectEquals (s.getIndexOfMatchingKeyUp (1), 3);
380
381 beginTest ("Time & indices");
382 expectEquals (s.getNextIndexAtTime (0.5), 1);
383 expectEquals (s.getNextIndexAtTime (2.5), 2);
384 expectEquals (s.getNextIndexAtTime (9.0), 4);
385
386 beginTest ("Deleting events");
387 s.deleteEvent (0, true);
388 expectEquals (s.getNumEvents(), 2);
389
390 beginTest ("Merging sequences");
391 MidiMessageSequence s2;
392 s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0));
393 s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0));
394 s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0));
395 s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0));
396 s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0));
397 s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0));
398
399 s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off
400 s.updateMatchedPairs();
401
402 expectEquals (s.getNumEvents(), 7);
403 expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off
404 expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0);
405 }
406 };
407
408 static MidiMessageSequenceTest midiMessageSequenceTests;
409
410 #endif
411
412 } // namespace juce
413