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 
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