OpenShot Library | libopenshot-audio  0.1.9
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 
90 MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() const noexcept { return list.begin(); }
91 MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() const noexcept { return list.end(); }
92 
93 double 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 
102 int 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 
119 int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept
120 {
121  return list.indexOf (event);
122 }
123 
124 int 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 //==============================================================================
137 double MidiMessageSequence::getStartTime() const noexcept
138 {
139  return getEventTime (0);
140 }
141 
142 double MidiMessageSequence::getEndTime() const noexcept
143 {
144  return getEventTime (list.size() - 1);
145 }
146 
147 double 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 //==============================================================================
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 
171 {
172  return addEvent (new MidiEventHolder (newMessage), timeAdjustment);
173 }
174 
176 {
177  return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment);
178 }
179 
180 void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp)
181 {
182  if (isPositiveAndBelow (index, list.size()))
183  {
184  if (deleteMatchingNoteUp)
185  deleteEvent (getIndexOfMatchingKeyUp (index), false);
186 
187  list.remove (index);
188  }
189 }
190 
191 void 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 
204  double timeAdjustment,
205  double firstAllowableTime,
206  double endOfAllowableDestTimes)
207 {
208  for (auto* m : other)
209  {
210  auto t = m->message.getTimeStamp() + timeAdjustment;
211 
212  if (t >= firstAllowableTime && t < endOfAllowableDestTimes)
213  {
214  auto newOne = new MidiEventHolder (m->message);
215  newOne->message.setTimeStamp (t);
216  list.add (newOne);
217  }
218  }
219 
220  sort();
221 }
222 
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 
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 
270 void MidiMessageSequence::addTimeToMessages (double delta) noexcept
271 {
272  if (delta != 0)
273  for (auto* m : list)
274  m->message.addToTimeStamp (delta);
275 }
276 
277 //==============================================================================
278 void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract,
279  MidiMessageSequence& destSequence,
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 
289 {
290  for (auto* meh : list)
291  if (meh->message.isSysEx())
292  destSequence.addEvent (meh->message);
293 }
294 
295 void 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 
303 {
304  for (int i = list.size(); --i >= 0;)
305  if (list.getUnchecked(i)->message.isSysEx())
306  list.remove(i);
307 }
308 
309 //==============================================================================
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 
337  if (! doneControllers[controllerNumber])
338  {
339  doneControllers[controllerNumber] = true;
340  dest.add (MidiMessage (mm, 0.0));
341  }
342  }
343  }
344  }
345 }
346 
347 #if JUCE_UNIT_TESTS
348 
349 struct MidiMessageSequenceTest : public juce::UnitTest
350 {
351  MidiMessageSequenceTest() : juce::UnitTest ("MidiMessageSequence") {}
352 
353  void runTest() override
354  {
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");
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 
401 static MidiMessageSequenceTest midiMessageSequenceTests;
402 
403 #endif
404 
405 } // 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.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:375
MidiEventHolder ** begin() const noexcept
Iterator for the list of MidiEventHolders.
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.
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.
MidiEventHolder ** end() const noexcept
Iterator for the list of MidiEventHolders.
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).