OpenShot Library | OpenShotAudio  0.2.2
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 
23 namespace juce
24 {
25 
26 MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {}
27 MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {}
28 MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {}
29 
30 //==============================================================================
31 MidiMessageSequence::MidiMessageSequence()
32 {
33 }
34 
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 
48 MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other)
49 {
50  MidiMessageSequence otherCopy (other);
51  swapWith (otherCopy);
52  return *this;
53 }
54 
55 MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept
56  : list (std::move (other.list))
57 {
58 }
59 
60 MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept
61 {
62  list = std::move (other.list);
63  return *this;
64 }
65 
66 MidiMessageSequence::~MidiMessageSequence()
67 {
68 }
69 
70 void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept
71 {
72  list.swapWith (other.list);
73 }
74 
75 void MidiMessageSequence::clear()
76 {
77  list.clear();
78 }
79 
80 int MidiMessageSequence::getNumEvents() const noexcept
81 {
82  return list.size();
83 }
84 
85 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept
86 {
87  return list[index];
88 }
89 
90 MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); }
91 MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); }
92 MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); }
93 MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); }
94 
95 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 
104 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 
121 int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept
122 {
123  return list.indexOf (event);
124 }
125 
126 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 //==============================================================================
139 double MidiMessageSequence::getStartTime() const noexcept
140 {
141  return getEventTime (0);
142 }
143 
144 double MidiMessageSequence::getEndTime() const noexcept
145 {
146  return getEventTime (list.size() - 1);
147 }
148 
149 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 //==============================================================================
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 
172 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment)
173 {
174  return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
175 }
176 
177 MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment)
178 {
179  return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment);
180 }
181 
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 
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 
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 
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 
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 
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 //==============================================================================
280 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 
290 void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const
291 {
292  for (auto* meh : list)
293  if (meh->message.isSysEx())
294  destSequence.addEvent (meh->message);
295 }
296 
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 
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 //==============================================================================
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 {
356  MidiMessageSequenceTest()
357  : UnitTest ("MidiMessageSequence", UnitTestCategories::midi)
358  {}
359 
360  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
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:60
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:422
Structure used to hold midi events in the sequence.
MidiMessage message
The message itself, whose timestamp is used to specify the event's time.
A sequence of timestamped midi messages.
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Inserts a midi message into the sequence.
int getIndexOfMatchingKeyUp(int index) const noexcept
Returns the index of the note-up that matches the note-on at this index.
Encapsulates a MIDI message.
double getTimeStamp() const noexcept
Returns the timestamp associated with this message.
void addToTimeStamp(double delta) noexcept
Adds a value to the message's timestamp.
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:74
void expectEquals(ValueType actual, ValueType expected, String failureMessage=String())
Compares a value to an expected value.
void beginTest(const String &testName)
Tells the system that a new subsection of tests is beginning.
virtual void runTest()=0
Implement this method in your subclass to actually run your tests.