OpenShot Library | OpenShotAudio  0.2.1
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)) {}
29 
30 //==============================================================================
32 {
33 }
34 
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 
49 {
50  MidiMessageSequence otherCopy (other);
51  swapWith (otherCopy);
52  return *this;
53 }
54 
56  : list (std::move (other.list))
57 {
58 }
59 
61 {
62  list = std::move (other.list);
63  return *this;
64 }
65 
67 {
68 }
69 
71 {
72  list.swapWith (other.list);
73 }
74 
76 {
77  list.clear();
78 }
79 
81 {
82  return list.size();
83 }
84 
86 {
87  return list[index];
88 }
89 
91 MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); }
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 //==============================================================================
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 
173 {
174  return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
175 }
176 
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 
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 
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 
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 
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 
305 {
306  for (int i = list.size(); --i >= 0;)
307  if (list.getUnchecked(i)->message.isSysEx())
308  list.remove(i);
309 }
310 
311 //==============================================================================
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  {
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");
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
void addTimeToMessages(double deltaTime) noexcept
Adds an offset to the timestamps of all events in the sequence.
void extractSysExMessages(MidiMessageSequence &destSequence) const
Copies all midi sys-ex messages to another sequence.
void updateMatchedPairs() noexcept
Makes sure all the note-on and note-off pairs are up-to-date.
void addSequence(const MidiMessageSequence &other, double timeAdjustmentDelta, double firstAllowableDestTime, double endOfAllowableDestTimes)
Merges another sequence into this one.
void setTimeStamp(double newTimestamp) noexcept
Changes the message&#39;s associated timestamp.
void deleteSysExMessages()
Removes any sys-ex messages from this sequence.
Encapsulates a MIDI message.
void sort() noexcept
Forces a sort of the sequence.
MidiEventHolder ** end() noexcept
Iterator for the list of MidiEventHolders.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:422
int getIndexOfMatchingKeyUp(int index) const noexcept
Returns the index of the note-up that matches the note-on at this index.
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
Inserts a midi message into the sequence.
void deleteEvent(int index, bool deleteMatchingNoteUp)
Deletes one of the events in the sequence.
STL namespace.
void swapWith(MidiMessageSequence &) noexcept
Swaps this sequence with another one.
MidiEventHolder ** begin() noexcept
Iterator for the list of MidiEventHolders.
void deleteMidiChannelMessages(int channelNumberToRemove)
Removes any messages in this sequence that have a specific midi channel.
MidiEventHolder * getEventPointer(int index) const noexcept
Returns a pointer to one of the events.
double getStartTime() const noexcept
Returns the timestamp of the first event in the sequence.
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:73
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
Creates a key-up message.
double getTimeOfMatchingKeyUp(int index) const noexcept
Returns the time of the note-up that matches the note-on at this index.
void addToTimeStamp(double delta) noexcept
Adds a value to the message&#39;s timestamp.
MidiMessageSequence & operator=(const MidiMessageSequence &)
Replaces this sequence with another one.
void createControllerUpdatesForTime(int channelNumber, double time, Array< MidiMessage > &resultMessages)
Scans through the sequence to determine the state of any midi controllers at a given time...
MidiMessage message
The message itself, whose timestamp is used to specify the event&#39;s time.
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:59
int getIndexOf(const MidiEventHolder *event) const noexcept
Returns the index of an event.
A sequence of timestamped midi messages.
Structure used to hold midi events in the sequence.
void extractMidiChannelMessages(int channelNumberToExtract, MidiMessageSequence &destSequence, bool alsoIncludeMetaEvents) const
Copies all the messages for a particular midi channel to another sequence.
double getTimeStamp() const noexcept
Returns the timestamp associated with this message.
int getNextIndexAtTime(double timeStamp) const noexcept
Returns the index of the first event on or after the given timestamp.
double getEndTime() const noexcept
Returns the timestamp of the last event in the sequence.
void clear()
Clears the sequence.
MidiMessageSequence()
Creates an empty midi sequence object.
int getNumEvents() const noexcept
Returns the number of events in the sequence.
double getEventTime(int index) const noexcept
Returns the timestamp of the event at a given index.
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
Creates a key-down message (using a floating-point velocity).