tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_MidiList.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14namespace
15{
16 void convertMidiPropertiesFromStrings (juce::ValueTree& midi)
17 {
18 if (midi.hasType (IDs::NOTE))
19 {
20 convertPropertyToType<int> (midi, IDs::p);
21 convertPropertyToType<double> (midi, IDs::b);
22 convertPropertyToType<double> (midi, IDs::l);
23 convertPropertyToType<int> (midi, IDs::v);
24 convertPropertyToType<int> (midi, IDs::c);
25 }
26 else if (midi.hasType (IDs::CONTROL))
27 {
28 convertPropertyToType<double> (midi, IDs::b);
29 convertPropertyToType<int> (midi, IDs::type);
30 convertPropertyToType<int> (midi, IDs::val);
31 convertPropertyToType<int> (midi, IDs::metadata);
32 }
33 else if (MidiExpression::isExpression (midi.getType()))
34 {
35 convertPropertyToType<double> (midi, IDs::b);
36 convertPropertyToType<double> (midi, IDs::v);
37 }
38
39 for (auto v : midi)
40 convertMidiPropertiesFromStrings (v);
41 }
42}
43
44template <typename Type>
45static void removeMidiEventFromSelection (Type* event)
46{
47 for (SelectionManager::Iterator sm; sm.next();)
48 if (auto sme = sm->getFirstItemOfType<SelectedMidiEvents>())
49 sme->removeSelectedEvent (event);
50}
51
52//==============================================================================
61{
62public:
63 MPEtoNoteExpression (MidiList& o, const TempoSequence* ts, juce::MPEZoneLayout layout, BeatPosition editBeatOfListTimeZero, juce::UndoManager* um)
64 : list (o), tempoSequence (ts), firstBeatNum (editBeatOfListTimeZero), undoManager (um)
65 {
66 mpeInstrument.setZoneLayout (layout);
67 mpeInstrument.addListener (this);
68 }
69
70 void processMidiMessage (const juce::MidiMessage& message)
71 {
72 currentEventBeat = tempoSequence != nullptr ? (tempoSequence->toBeats (TimePosition::fromSeconds (message.getTimeStamp())) - toDuration (firstBeatNum))
73 : BeatPosition::fromBeats (message.getTimeStamp());
74
75 mpeInstrument.processNextMidiEvent (message);
76 }
77
78private:
79 MidiList& list;
80 const TempoSequence* tempoSequence;
81 const BeatPosition firstBeatNum;
82 juce::UndoManager* undoManager;
83 juce::MPEInstrument mpeInstrument;
84
85 struct ActiveNote
86 {
87 ActiveNote (juce::MPENote n, BeatPosition beat)
88 : startNote (n), startBeat (beat)
89 {
90 modulations.ensureStorageAllocated (50);
91 }
92
93 enum ChangeType
94 {
95 pitchbend,
96 pressure,
97 timbre
98 };
99
101 {
102 BeatPosition beat;
103 float value;
104 ActiveNote::ChangeType type;
105 };
106
107 bool containsModulation (ActiveNote::ChangeType t)
108 {
109 for (const auto& mod : modulations)
110 if (mod.type == t)
111 return true;
112
113 return false;
114 }
115
116 juce::MPENote startNote;
117 BeatPosition startBeat;
118 juce::Array<Modulation> modulations;
119 };
120
122 BeatPosition currentEventBeat;
123
124 ActiveNote* getActiveNote (juce::MPENote note)
125 {
126 for (auto activeNote : activeNotes)
127 if (activeNote->startNote.noteID == note.noteID)
128 return activeNote;
129
130 return {};
131 }
132
133 void addNoteToList (juce::MPENote note, BeatPosition endBeat)
134 {
135 if (auto an = getActiveNote (note))
136 {
137 const auto startBeat = an->startBeat;
138 auto n = list.addNote (an->startNote.initialNote, startBeat, toDuration (endBeat - toDuration (startBeat)),
139 note.noteOnVelocity.as7BitInt(), 0, undoManager);
140 auto noteState = n->state;
141
142 auto lift = note.noteOffVelocity.as7BitInt();
143 auto timb = an->startNote.timbre.asUnsignedFloat();
144 auto pres = an->startNote.pressure.asUnsignedFloat();
145 auto bend = (float) an->startNote.totalPitchbendInSemitones;
146
147 if (lift != 0)
148 noteState.setProperty (IDs::lift, lift, undoManager);
149
150 if (timb != MidiList::defaultInitialTimbreValue)
151 noteState.setProperty (IDs::timb, timb, undoManager);
152
153 if (pres != MidiList::defaultInitialPressureValue)
154 noteState.setProperty (IDs::pres, pres, undoManager);
155
156 if (bend != MidiList::defaultInitialPitchBendValue)
157 noteState.setProperty (IDs::bend, bend, undoManager);
158
159 // MPE spec requires a timbre before a note-on (though we send all dimensions, just to be sure)
160 // and pressure & pitchbend immediately following.
161 MidiExpression::createAndAddExpressionToNote (noteState, IDs::PRESSURE, BeatPosition(), pres, undoManager);
162 MidiExpression::createAndAddExpressionToNote (noteState, IDs::PITCHBEND, BeatPosition(), bend, undoManager);
163
164 for (auto& mod : an->modulations)
165 {
166 auto getType = [] (ActiveNote::ChangeType type)
167 {
168 switch (type)
169 {
170 case ActiveNote::pitchbend: return IDs::PITCHBEND;
171 case ActiveNote::pressure: return IDs::PRESSURE;
172 case ActiveNote::timbre: return IDs::TIMBRE;
173 }
174
176 return juce::Identifier();
177 };
178
179 const auto relativeBeat = mod.beat - startBeat;
180
181 MidiExpression::createAndAddExpressionToNote (noteState, getType (mod.type), toPosition (relativeBeat), mod.value, undoManager);
182 }
183
184 activeNotes.removeObject (an);
185 }
186 }
187
188 void noteAdded (juce::MPENote note) override
189 {
190 activeNotes.add (new ActiveNote (note, currentEventBeat));
191 jassert (activeNotes.size() <= 16);
192 }
193
194 void notePressureChanged (juce::MPENote note) override
195 {
196 if (auto an = getActiveNote (note))
197 an->modulations.add (ActiveNote::Modulation { currentEventBeat, note.pressure.asUnsignedFloat(), ActiveNote::pressure });
198 }
199
200 void notePitchbendChanged (juce::MPENote note) override
201 {
202 if (auto an = getActiveNote (note))
203 an->modulations.add (ActiveNote::Modulation { currentEventBeat, float (note.totalPitchbendInSemitones), ActiveNote::pitchbend });
204 }
205
206 void noteTimbreChanged (juce::MPENote note) override
207 {
208 if (auto an = getActiveNote (note))
209 an->modulations.add (ActiveNote::Modulation { currentEventBeat, note.timbre.asUnsignedFloat(), ActiveNote::timbre });
210 }
211
212 void noteKeyStateChanged (juce::MPENote) override
213 {
214 }
215
216 void noteReleased (juce::MPENote note) override
217 {
218 addNoteToList (note, currentEventBeat);
219 }
220
222};
223
224//==============================================================================
225bool MidiExpression::listHasExpression (const MidiList& midiList) noexcept
226{
227 for (auto note : midiList.getNotes())
228 if (noteHasExpression (note->state))
229 return true;
230
231 return false;
232}
233
234namespace NoteHelpers
235{
236 inline juce::MidiMessage createNoteOn (int midiChannel, int noteNumber, float noteOnVelocity) noexcept
237 {
238 // a note cannot be started with a zero note-on velocity:
239 const float noteOnVelocityToUse = std::max (noteOnVelocity, 1.0f / 127.0f);
240 return juce::MidiMessage::noteOn (midiChannel, noteNumber, noteOnVelocityToUse);
241 }
242
243 inline juce::MidiMessage createPitchbend (int midiChannel, float pitchbend, float pitchbendRange) noexcept
244 {
245 const int midiPitchWheelPos = juce::MidiMessage::pitchbendToPitchwheelPos (pitchbend, pitchbendRange);
246 return juce::MidiMessage::pitchWheel (midiChannel, midiPitchWheelPos);
247 }
248
249 inline juce::MidiMessage createPressure (int midiChannel, float pressure) noexcept
250 {
251 auto midiPressure = juce::MidiMessage::floatValueToMidiByte (pressure);
252 return juce::MidiMessage::channelPressureChange (midiChannel, midiPressure);
253 }
254
255 inline juce::MidiMessage createTimbre (int midiChannel, float timbre) noexcept
256 {
257 auto midiCcValue = juce::MidiMessage::floatValueToMidiByte (timbre);
258 return juce::MidiMessage::controllerEvent (midiChannel, 74, midiCcValue);
259 }
260
261 inline juce::MidiMessage createNoteOff (int midiChannel, int noteNumber, float noteOffVelocity) noexcept
262 {
263 return juce::MidiMessage::noteOff (midiChannel, noteNumber, noteOffVelocity);
264 }
265}
266
267inline void addMidiNoteOnExpressionToSequence (juce::MidiMessageSequence& seq, const juce::ValueTree& state, int midiChannel, double noteOnTime) noexcept
268{
269 using namespace NoteHelpers;
270 jassert (state.hasType (IDs::NOTE));
271
272 const float pitchbend = state.getProperty (IDs::bend, MidiList::defaultInitialPitchBendValue);
273 const float pressure = state.getProperty (IDs::pres, MidiList::defaultInitialPressureValue);
274 const float timbre = state.getProperty (IDs::timb, MidiList::defaultInitialTimbreValue);
275
276 seq.addEvent (juce::MidiMessage (createPitchbend (midiChannel, pitchbend, 48.0f), noteOnTime));
277 seq.addEvent (juce::MidiMessage (createPressure (midiChannel, pressure), noteOnTime));
278 seq.addEvent (juce::MidiMessage (createTimbre (midiChannel, timbre), noteOnTime));
279}
280
281static void addMidiExpressionToSequence (juce::MidiMessageSequence& seq, const juce::ValueTree& state,
282 const MidiClip& clip, MidiList::TimeBase tb, int midiChannel,
283 BeatPosition notePlaybackBeat, BeatPosition notePlaybackEndBeat) noexcept
284{
285 using namespace NoteHelpers;
286
287 auto& ts = clip.edit.tempoSequence;
288 const auto beat = notePlaybackBeat + BeatDuration::fromBeats (static_cast<double> (state.getProperty (IDs::b)));
289
290 if (beat > notePlaybackEndBeat)
291 return;
292
293 const auto time = tb == MidiList::TimeBase::beats ? beat.inBeats()
294 : ts.toTime (beat).inSeconds();
295
296 if (state.hasType (IDs::PITCHBEND))
297 seq.addEvent (juce::MidiMessage (createPitchbend (midiChannel, state[IDs::v], 48.0f), time));
298 else if (state.hasType (IDs::PRESSURE))
299 seq.addEvent (juce::MidiMessage (createPressure (midiChannel, state[IDs::v]), time));
300 else if (state.hasType (IDs::TIMBRE))
301 seq.addEvent (juce::MidiMessage (createTimbre (midiChannel, state[IDs::v]), time));
302 else
304}
305
306static void addExpressiveNoteToSequence (juce::MidiMessageSequence& seq, const MidiClip& clip, MidiList::TimeBase tb, const MidiNote& note, int midiChannel, const GrooveTemplate* grooveTemplate)
307{
308 if (note.isMute() || note.getLengthBeats() <= BeatDuration::fromBeats (0.00001))
309 return;
310
311 const auto downTime = tb == MidiList::TimeBase::beats ? note.getPlaybackBeats (MidiNote::startEdge, clip, grooveTemplate).inBeats()
312 : note.getPlaybackTime (MidiNote::startEdge, clip, grooveTemplate).inSeconds();
313 const auto upTime = tb == MidiList::TimeBase::beats ? note.getPlaybackBeats (MidiNote::endEdge, clip, grooveTemplate).inBeats()
314 : note.getPlaybackTime (MidiNote::endEdge, clip, grooveTemplate).inSeconds();
315
316 if (upTime < downTime || upTime <= 0.0)
317 return;
318
319 auto state = note.state;
320 const auto noteStartBeat = tb == MidiList::TimeBase::beats ? BeatPosition::fromBeats (downTime)
321 : clip.edit.tempoSequence.toBeats (TimePosition::fromSeconds (downTime));
322 const auto noteEndBeat = tb == MidiList::TimeBase::beats ? BeatPosition::fromBeats (upTime)
323 : clip.edit.tempoSequence.toBeats (TimePosition::fromSeconds (upTime));
324
325 // First add expression with defaults if not present
326 addMidiNoteOnExpressionToSequence (seq, state, midiChannel, downTime);
327
328 // Then note-on
329 using namespace NoteHelpers;
330 const auto noteOnVelocity = juce::jlimit (0.0f, 1.0f, note.getVelocity() / 127.0f);
331 seq.addEvent (juce::MidiMessage (createNoteOn (midiChannel, note.getNoteNumber(), noteOnVelocity), downTime));
332
333 // Then modulating expression
334 for (auto v : state)
335 addMidiExpressionToSequence (seq, v, clip, tb, midiChannel, noteStartBeat, noteEndBeat);
336
337 // Finally note-off
338 const auto noteOffVelocity = state.hasProperty (IDs::lift) ? int (state[IDs::lift]) / 127.0f : 0.0f;
339 seq.addEvent (juce::MidiMessage (createNoteOff (midiChannel, note.getNoteNumber(), noteOffVelocity), upTime));
340}
341
342//==============================================================================
343static void addToSequence (juce::MidiMessageSequence& seq, const MidiClip& clip, MidiList::TimeBase tb,
344 const MidiNote& note, int channelNumber, bool addNoteUp,
345 const GrooveTemplate* grooveTemplate)
346{
347 jassert (channelNumber < 17); // SysEx?
348
349 if (note.isMute() || note.getLengthBeats() <= BeatDuration::fromBeats (0.00001))
350 return;
351
352 const auto downTime = [&]
353 {
354 switch (tb)
355 {
356 case MidiList::TimeBase::beatsRaw: return note.getStartBeat().inBeats();
357 case MidiList::TimeBase::beats: return note.getPlaybackBeats (MidiNote::startEdge, clip, grooveTemplate).inBeats();
358 case MidiList::TimeBase::seconds: [[ fallthrough ]];
359 default: return note.getPlaybackTime (MidiNote::startEdge, clip, grooveTemplate).inSeconds();
360 }
361 }();
362
363 auto velocity = (uint8_t) note.getVelocity();
364 int noteNumber = note.getNoteNumber();
365
366 if (addNoteUp)
367 {
368 // nudge the note-up backwards just a bit to make sure the ordering is correct
369 const auto upTime = [&]
370 {
371 switch (tb)
372 {
373 case MidiList::TimeBase::beatsRaw: return note.getEndBeat().inBeats();
374 case MidiList::TimeBase::beats: return note.getPlaybackBeats (MidiNote::endEdge, clip, grooveTemplate).inBeats();
375 case MidiList::TimeBase::seconds: [[ fallthrough ]];
376 default: return note.getPlaybackTime (MidiNote::endEdge, clip, grooveTemplate).inSeconds();
377 }
378 }();
379
380 if (upTime > downTime && upTime > 0.0)
381 {
382 seq.addEvent (juce::MidiMessage::noteOn (channelNumber, noteNumber, velocity), std::max (0.0, downTime));
383 seq.addEvent (juce::MidiMessage::noteOff (channelNumber, noteNumber), upTime);
384 }
385 }
386 else if (downTime >= 0.0)
387 {
388 seq.addEvent (juce::MidiMessage::noteOn (channelNumber, noteNumber, velocity), downTime);
389 }
390}
391
392static void addToSequence (juce::MidiMessageSequence& seq, const MidiClip& clip, MidiList::TimeBase tb,
393 const MidiControllerEvent& controller, int channelNumber)
394{
395 const auto time = [&]
396 {
397 switch (tb)
398 {
399 case MidiList::TimeBase::beatsRaw: return controller.getBeatPosition().inBeats();
400 case MidiList::TimeBase::beats: return std::max (0_bp, controller.getEditBeats (clip) - toDuration (clip.getStartBeat())).inBeats();
401 case MidiList::TimeBase::seconds: [[ fallthrough ]];
402 default: return std::max (0_tp, controller.getEditTime (clip) - toDuration (clip.getPosition().getStart())).inSeconds();
403 }
404 }();
405
406 const auto type = controller.getType();
407 const auto value = controller.getControllerValue();
408
409 if (juce::isPositiveAndBelow (type, 128))
410 {
411 seq.addEvent (juce::MidiMessage::controllerEvent (channelNumber, type, value >> 7), time);
412 return;
413 }
414
415 switch (type)
416 {
417 case MidiControllerEvent::programChangeType:
418 if (clip.isSendingBankChanges())
419 {
420 auto id = clip.getAudioTrack()->getIdForBank (controller.getMetadata());
421
422 seq.addEvent (juce::MidiMessage::controllerEvent (channelNumber, 0x00, MidiControllerEvent::bankIDToCoarse (id)), time);
423 seq.addEvent (juce::MidiMessage::controllerEvent (channelNumber, 0x20, MidiControllerEvent::bankIDToFine (id)), time);
424 }
425
426 seq.addEvent (juce::MidiMessage::programChange (channelNumber, value >> 7), time);
427 break;
428
429 case MidiControllerEvent::aftertouchType:
430 seq.addEvent (juce::MidiMessage::aftertouchChange (channelNumber, controller.getMetadata(), value >> 7), time);
431 break;
432
433 case MidiControllerEvent::pitchWheelType:
434 seq.addEvent (juce::MidiMessage::pitchWheel (channelNumber, value), time);
435 break;
436
437 case MidiControllerEvent::channelPressureType:
438 seq.addEvent (juce::MidiMessage::channelPressureChange (channelNumber, value >> 7), time);
439 break;
440
441 default:
443 break;
444 }
445}
446
447static void addToSequence (juce::MidiMessageSequence& seq, const MidiClip& clip, MidiList::TimeBase tb, const MidiSysexEvent& sysex)
448{
449 const auto time = [&]
450 {
451 switch (tb)
452 {
453 case MidiList::TimeBase::beatsRaw: return sysex.getBeatPosition().inBeats();
454 case MidiList::TimeBase::beats: return std::max (0_bp, sysex.getEditBeats (clip) - toDuration (clip.getStartBeat())).inBeats();
455 case MidiList::TimeBase::seconds: [[ fallthrough ]];
456 default: return std::max (0_tp, sysex.getEditTime (clip) - toDuration (clip.getPosition().getStart())).inSeconds();
457 }
458 }();
459 auto m = sysex.getMessage();
460 m.setTimeStamp (time);
461 seq.addEvent (m);
462}
463
464//==============================================================================
469{
470public:
471 //==========================================================================
474 : seq (s), clip (c), timeBase (tb), groove (g)
475 {
476 zoneLayout.setLowerZone (15);
477 const auto mpeZone = zoneLayout.getLowerZone();
478
479 midiChannelBegin = mpeZone.getFirstMemberChannel() - 1;
480 midiChannelEnd = mpeZone.getLastMemberChannel();
481 midiChannelLastAssigned = midiChannelEnd - 1;
482 }
483
484 void addNote (MidiNote& note)
485 {
486 if (timeBase == MidiList::TimeBase::beats)
487 clearNotesEndingBefore (note.getPlaybackBeats (MidiNote::startEdge, clip, groove));
488 else
489 clearNotesEndingBefore (note.getPlaybackTime (MidiNote::startEdge, clip, groove));
490
491 auto midiChannel = findMidiChannelForNewNote (note.getNoteNumber());
492 midiChannels[midiChannel].notes.add (&note);
493 midiChannels[midiChannel].lastNoteNumberPlayed = note.getNoteNumber();
494
495 addExpressiveNoteToSequence (seq, clip, timeBase, note, zoneLayout.getLowerZone().getMasterChannel() + midiChannel, groove);
496 }
497
498private:
499 //==========================================================================
500 struct MidiChannel
501 {
502 MidiNote* getNoteIfPresent (MidiNote& noteToLookFor) const noexcept
503 {
504 for (auto note : notes)
505 if (note == &noteToLookFor)
506 return note;
507
508 return {};
509 }
510
511 bool isFree() const noexcept
512 {
513 return notes.isEmpty();
514 }
515
517 int lastNoteNumberPlayed = -1;
518 };
519
520 int getNumMidiChannels() const noexcept
521 {
522 return midiChannelEnd - midiChannelBegin;
523 }
524
525 void clearNotesEndingBefore (TimePosition time)
526 {
527 // Iterate the notes in reverse as they will be removed when stopped
528 for (auto& midiChannel : midiChannels)
529 for (int i = midiChannel.notes.size(); --i >= 0;)
530 if (auto n = midiChannel.notes.getUnchecked (i))
531 if (n->getPlaybackTime (MidiNote::endEdge, clip, groove) < time)
532 stopNote (*n);
533 }
534
535 void clearNotesEndingBefore (BeatPosition time)
536 {
537 // Iterate the notes in reverse as they will be removed when stopped
538 for (auto& midiChannel : midiChannels)
539 for (int i = midiChannel.notes.size(); --i >= 0;)
540 if (auto n = midiChannel.notes.getUnchecked (i))
541 if (n->getPlaybackBeats (MidiNote::endEdge, clip, groove) < time)
542 stopNote (*n);
543 }
544
545 void stopNote (MidiNote& note)
546 {
547 for (int ch = midiChannelBegin; ch < midiChannelEnd; ++ch)
548 {
549 if (midiChannels[ch].notes.contains (&note))
550 {
551 midiChannels[ch].notes.removeFirstMatchingValue (&note);
552 return;
553 }
554 }
555
557 }
558
559 int findMidiChannelForNewNote (int noteNumber) noexcept
560 {
561 const int numMidiChannels = getNumMidiChannels();
562
563 jassert (numMidiChannels > 0);
564
565 if (numMidiChannels == 1)
566 return midiChannelBegin;
567
568 for (int ch = midiChannelBegin; ch != midiChannelEnd; ++ch)
569 if (midiChannels[ch].isFree() && midiChannels[ch].lastNoteNumberPlayed == noteNumber)
570 return ch;
571
572 // find next free channel in round robin assignment
573 for (int ch = midiChannelLastAssigned + 1; ; ++ch)
574 {
575 if (ch == midiChannelEnd) // loop wrap-around
576 ch = midiChannelBegin;
577
578 if (midiChannels[ch].isFree())
579 {
580 midiChannelLastAssigned = ch;
581 return ch;
582 }
583
584 if (ch == midiChannelLastAssigned)
585 break; // no free channels!
586 }
587
588 midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
589
590 return midiChannelLastAssigned;
591 }
592
593 int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
594 {
595 int channelWithClosestNote = midiChannelBegin;
596 int closestNoteDistance = 127;
597
598 for (int ch = midiChannelBegin; ch < midiChannelEnd; ++ch)
599 {
600 for (auto note : midiChannels[ch].notes)
601 {
602 const int noteDistance = std::abs (note->getNoteNumber() - noteNumber);
603
604 if (noteDistance > 0 && noteDistance < closestNoteDistance)
605 {
606 closestNoteDistance = noteDistance;
607 channelWithClosestNote = ch;
608 }
609 }
610 }
611
612 return channelWithClosestNote;
613 }
614
615 //==========================================================================
617 const MidiClip& clip;
618 const MidiList::TimeBase timeBase;
619 const GrooveTemplate* groove;
620 juce::MPEZoneLayout zoneLayout;
621 MidiChannel midiChannels[16];
622 int midiChannelBegin, midiChannelEnd, midiChannelLastAssigned;
623};
624
625//==============================================================================
626static juce::ValueTree createNoteValueTree (int pitch, BeatPosition beat, BeatDuration length, int vel, int col)
627{
628 return createValueTree (IDs::NOTE,
629 IDs::p, pitch,
630 IDs::b, beat.inBeats(),
631 IDs::l, std::max (0.0, length.inBeats()),
632 IDs::v, vel,
633 IDs::c, col);
634}
635
636juce::ValueTree MidiNote::createNote (const MidiNote& n, BeatPosition newStart, BeatDuration newLength)
637{
638 juce::ValueTree v (n.state.createCopy());
639 v.setProperty (IDs::b, newStart.inBeats(), nullptr);
640 v.setProperty (IDs::l, newLength.inBeats(), nullptr);
641
642 return v;
643}
644
645MidiNote::MidiNote (const juce::ValueTree& v)
646 : state (v)
647{
648 updatePropertiesFromState();
649}
650
651void MidiNote::updatePropertiesFromState()
652{
653 noteNumber = (uint8_t) juce::jlimit (0, 127, static_cast<int> (state.getProperty (IDs::p)));
654 startBeat = BeatPosition::fromBeats (static_cast<double> (state.getProperty (IDs::b)));
655 lengthInBeats = std::max (BeatDuration(), BeatDuration::fromBeats (static_cast<double> (state.getProperty (IDs::l))));
656 velocity = (uint8_t) juce::jlimit (0, 127, static_cast<int> (state.getProperty (IDs::v)));
657 colour = (uint8_t) juce::jlimit (0, 127, static_cast<int> (state.getProperty (IDs::c)));
658 mute = state.getProperty (IDs::m) ? 1 : 0;
659}
660
661template<>
662struct MidiList::EventDelegate<MidiNote>
663{
664 static bool isSuitableType (const juce::ValueTree& v)
665 {
666 return v.hasType (IDs::NOTE);
667 }
668
669 static bool updateObject (MidiNote& m, const juce::Identifier& i)
670 {
671 m.updatePropertiesFromState();
672 return i == IDs::b;
673 }
674
675 static void removeFromSelection (MidiNote* m)
676 {
677 removeMidiEventFromSelection (m);
678 }
679};
680
681//==============================================================================
683{
684 return c.getQuantisation().roundBeatToNearest (startBeat + toDuration (c.getContentStartBeat()))
685 - toDuration (c.getContentStartBeat());
686}
687
689{
690 if (c != nullptr)
691 return getQuantisedStartBeat (*c);
692
693 return startBeat;
694}
695
696BeatPosition MidiNote::getQuantisedEndBeat (const MidiClip& c) const
697{
698 return c.getQuantisation().roundBeatToNearest (startBeat + toDuration (c.getContentStartBeat()))
699 + lengthInBeats - toDuration (c.getContentStartBeat());
700}
701
702BeatPosition MidiNote::getQuantisedEndBeat (const MidiClip* const c) const
703{
704 if (c != nullptr)
705 return getQuantisedEndBeat (*c);
706
707 return startBeat + lengthInBeats;
708}
709
710BeatDuration MidiNote::getQuantisedLengthBeats (const MidiClip& c) const
711{
712 return getQuantisedEndBeat (c) - getQuantisedStartBeat (c);
713}
714
715BeatDuration MidiNote::getQuantisedLengthBeats (const MidiClip* const c) const
716{
717 return getQuantisedEndBeat (c) - getQuantisedStartBeat (c);
718}
719
720//==============================================================================
722{
723 const auto quantisedBeatInEdit = c.getQuantisation().roundBeatToNearest (startBeat + toDuration (c.getContentStartBeat()));
724
725 return c.edit.tempoSequence.toTime (quantisedBeatInEdit);
726}
727
728TimePosition MidiNote::getEditEndTime (const MidiClip& c) const
729{
730 const auto quantisedBeatInEdit = c.getQuantisation().roundBeatToNearest (startBeat + toDuration (c.getContentStartBeat()))
731 + lengthInBeats;
732
733 return c.edit.tempoSequence.toTime (quantisedBeatInEdit);
734}
735
736TimeRange MidiNote::getEditTimeRange (const MidiClip& c) const
737{
738 const auto quantisedStartBeat = c.getQuantisation().roundBeatToNearest (startBeat - toDuration (c.getLoopStartBeats()) + toDuration (c.getContentStartBeat()));
739
740 return { c.edit.tempoSequence.toTime (quantisedStartBeat),
741 c.edit.tempoSequence.toTime (quantisedStartBeat + lengthInBeats) };
742}
743
744TimeDuration MidiNote::getLengthSeconds (const MidiClip& c) const
745{
746 return getEditTimeRange (c).getLength();
747}
748
749//==============================================================================
750void MidiNote::setStartAndLength (BeatPosition newStartBeat, BeatDuration newLengthInBeats, juce::UndoManager* undoManager)
751{
752 newStartBeat = std::max (BeatPosition(), newStartBeat);
753
754 if (newLengthInBeats <= BeatDuration())
755 newLengthInBeats = BeatDuration::fromBeats (1.0 / Edit::ticksPerQuarterNote);
756
757 if (startBeat != newStartBeat)
758 {
759 state.setProperty (IDs::b, newStartBeat.inBeats(), undoManager);
760 startBeat = newStartBeat;
761 }
762
763 if (lengthInBeats != newLengthInBeats)
764 {
765 state.setProperty (IDs::l, newLengthInBeats.inBeats(), undoManager);
766 lengthInBeats = newLengthInBeats;
767 }
768}
769
770void MidiNote::setNoteNumber (int newNoteNumber, juce::UndoManager* undoManager)
771{
772 newNoteNumber = juce::jlimit (0, 127, newNoteNumber);
773
774 if (getNoteNumber() != newNoteNumber)
775 {
776 state.setProperty (IDs::p, newNoteNumber, undoManager);
777 noteNumber = (uint8_t) newNoteNumber;
778 }
779}
780
781void MidiNote::setVelocity (int newVelocity, juce::UndoManager* undoManager)
782{
783 newVelocity = juce::jlimit (0, 127, newVelocity);
784
785 if (getVelocity() != newVelocity)
786 {
787 state.setProperty (IDs::v, newVelocity, undoManager);
788 velocity = (uint8_t) newVelocity;
789 }
790}
791
792void MidiNote::setColour (int newColourIndex, juce::UndoManager* um)
793{
794 newColourIndex = juce::jlimit (0, 127, newColourIndex);
795
796 if (getColour() != newColourIndex)
797 {
798 state.setProperty (IDs::c, newColourIndex, um);
799 colour = (uint8_t) newColourIndex;
800 }
801}
802
803void MidiNote::setMute (bool shouldMute, juce::UndoManager* um)
804{
805 if (isMute() != shouldMute)
806 {
807 state.setProperty (IDs::m, shouldMute, um);
808 mute = shouldMute ? 1 : 0;
809 }
810}
811
812//==============================================================================
813TimePosition MidiNote::getPlaybackTime (NoteEdge edge, const MidiClip& clip, const GrooveTemplate* const grooveTemplate) const
814{
815 auto pos = clip.getPosition();
816
817 // nudge the note-up backwards just a bit to make sure the ordering is correct
818 auto time = edge == startEdge ? getEditStartTime (clip)
819 : std::min (getEditEndTime (clip), pos.getEnd()) - TimeDuration::fromSeconds (0.0001);
820
821 if (grooveTemplate != nullptr)
822 time = grooveTemplate->editTimeToGroovyTime (time, clip.getGrooveStrength(), clip.edit);
823
824 return time - toDuration (pos.getStart());
825}
826
827BeatPosition MidiNote::getPlaybackBeats (NoteEdge edge, const MidiClip& clip, const GrooveTemplate* const grooveTemplate) const
828{
829 auto pos = clip.getPosition();
830
831 // nudge the note-up backwards just a bit to make sure the ordering is correct
832 auto time = edge == startEdge ? clip.getQuantisation().roundBeatToNearest (startBeat + toDuration (clip.getContentStartBeat()))
833 : std::min (clip.getQuantisation().roundBeatToNearest (startBeat + toDuration (clip.getContentStartBeat()))
834 + lengthInBeats, clip.edit.tempoSequence.toBeats (pos.getEnd())) - 0.00001_bd;
835
836 if (grooveTemplate != nullptr)
837 time = grooveTemplate->beatsTimeToGroovyTime (time, clip.getGrooveStrength());
838
839 return time - toDuration (clip.edit.tempoSequence.toBeats (pos.getStart()));
840}
841
842//==============================================================================
843juce::ValueTree MidiControllerEvent::createControllerEvent (const MidiControllerEvent& e, BeatPosition beat)
844{
845 juce::ValueTree v (e.state.createCopy());
846 v.setProperty (IDs::b, beat.inBeats(), nullptr);
847 return v;
848}
849
850juce::ValueTree MidiControllerEvent::createControllerEvent (BeatPosition beat, int controllerType, int controllerValue)
851{
852 return createValueTree (IDs::CONTROL,
853 IDs::b, beat.inBeats(),
854 IDs::type, controllerType,
855 IDs::val, controllerValue);
856}
857
858juce::ValueTree MidiControllerEvent::createControllerEvent (BeatPosition beat, int controllerType, int controllerValue, int metadata)
859{
860 auto v = createControllerEvent (beat, controllerType, controllerValue);
861 v.setProperty (IDs::metadata, metadata, nullptr);
862 return v;
863}
864
865MidiControllerEvent::MidiControllerEvent (const juce::ValueTree& v)
866 : state (v),
867 beatNumber (BeatPosition::fromBeats (static_cast<double> (v.getProperty (IDs::b)))),
868 type (int (v.getProperty (IDs::type))),
869 value (v.getProperty (IDs::val))
870{
871 updatePropertiesFromState();
872}
873
874void MidiControllerEvent::updatePropertiesFromState() noexcept
875{
876 beatNumber = BeatPosition::fromBeats (static_cast<double> (state.getProperty (IDs::b)));
877 type = state.getProperty (IDs::type);
878 value = state.getProperty (IDs::val);
879 metadata = state.getProperty (IDs::metadata);
880}
881
882template<>
883struct MidiList::EventDelegate<MidiControllerEvent>
884{
885 static bool isSuitableType (const juce::ValueTree& v)
886 {
887 return v.hasType (IDs::CONTROL);
888 }
889
890 static bool updateObject (MidiControllerEvent& e, const juce::Identifier& i)
891 {
892 e.updatePropertiesFromState();
893 return i == IDs::b;
894 }
895
896 static void removeFromSelection (MidiControllerEvent* e)
897 {
898 removeMidiEventFromSelection (e);
899 }
900};
901
902//==============================================================================
903juce::String MidiControllerEvent::getControllerTypeName (int type) noexcept
904{
905 if (juce::isPositiveAndBelow (type, 128))
907
908 switch (type)
909 {
910 case programChangeType: return TRANS("Program Change");
911 case noteVelocities: return TRANS("Note Velocities");
912 case aftertouchType: return TRANS("Aftertouch");
913 case pitchWheelType: return TRANS("Pitch Wheel");
914 case sysExType: return TRANS("SysEx Events");
915 case channelPressureType: return TRANS("Channel Pressure");
916 default: break;
917 };
918
919 return "(" + TRANS("Unnamed") + ")";
920}
921
923{
924 return c.getQuantisation().roundBeatToNearest (beatNumber - toDuration (c.getLoopStartBeats()) + toDuration (c.getContentStartBeat()));
925}
926
927TimePosition MidiControllerEvent::getEditTime (const MidiClip& c) const
928{
929 return c.edit.tempoSequence.toTime (getEditBeats (c));
930}
931
932juce::String MidiControllerEvent::getLevelDescription (MidiClip* ownerClip) const
933{
934 const int coarseValue = value >> 7;
935
936 if (type == programChangeType)
937 {
938 juce::String bank;
939 auto program = juce::String (coarseValue + 1) + " - ";
940
941 if (ownerClip != nullptr && ownerClip->getTrack() != nullptr)
942 {
943 auto audioTrack = ownerClip->getAudioTrack();
944 bank = audioTrack->getNameForBank (metadata);
945 program += audioTrack->getNameForProgramNumber (coarseValue, metadata);
946 }
947 else
948 {
949 bank = TRANS("Bank") + " " + juce::String ((value & 0x0F) + 1);
950 program += TRANS(juce::MidiMessage::getGMInstrumentName (coarseValue));
951 }
952
953 return bank + ": " + program;
954 }
955
956 if (type == pitchWheelType)
957 {
958 juce::String s (TRANS("Pitch Wheel") + ": ");
959 const int percent = juce::jlimit (-100, 100, (int)((value - 0x2000) * (100.5 / 0x2000)));
960
961 if (percent > 0)
962 s += "+";
963
964 s << percent << "%";
965
966 return s;
967 }
968
969 if (type == channelPressureType)
970 return TRANS("Channel Pressure") + " - " + juce::String (coarseValue);
971
972 if (type == aftertouchType)
973 return TRANS("Aftertouch") + " - " + juce::String (coarseValue);
974
975 if (juce::isPositiveAndBelow (type, 128))
976 {
977 auto controllerName = TRANS(juce::MidiMessage::getControllerName (type));
978
979 if (controllerName.isEmpty())
980 controllerName = TRANS("Controller Number") + " " + juce::String (type);
981
982 return controllerName + " - " + juce::String (coarseValue);
983 }
984
985 return juce::String (coarseValue);
986}
987
988void MidiControllerEvent::setMetadata (int m, juce::UndoManager* um)
989{
990 if (metadata != m)
991 {
992 state.setProperty (IDs::metadata, m, um);
993 metadata = m;
994 }
995}
996
997void MidiControllerEvent::setBeatPosition (BeatPosition newBeatNumber, juce::UndoManager* um)
998{
999 newBeatNumber = std::max (BeatPosition(), newBeatNumber);
1000
1001 if (beatNumber != newBeatNumber)
1002 {
1003 state.setProperty (IDs::b, newBeatNumber.inBeats(), um);
1004 beatNumber = newBeatNumber;
1005 }
1006}
1007
1008void MidiControllerEvent::setType (int t, juce::UndoManager* um)
1009{
1010 if (t != type)
1011 {
1012 state.setProperty (IDs::type, t, um);
1013 type = t;
1014 }
1015}
1016
1017void MidiControllerEvent::setControllerValue (int newValue, juce::UndoManager* um)
1018{
1019 if (value != newValue)
1020 {
1021 state.setProperty (IDs::val, newValue, um);
1022 value = newValue;
1023 }
1024}
1025
1026int MidiControllerEvent::getControllerDefautValue (int type)
1027{
1028 switch (type)
1029 {
1030 case pitchWheelType: return 8192;
1031 case 7: return 99 << 7; // volume
1032 case 10: return 64 << 7; // pan
1033 default: return 0;
1034 }
1035}
1036
1037//==============================================================================
1038static juce::String midiToHex (const juce::MidiMessage& m)
1039{
1040 return juce::String::toHexString (m.getRawData(), m.getRawDataSize(), 0);
1041}
1042
1043juce::ValueTree MidiSysexEvent::createSysexEvent (const MidiSysexEvent& e, BeatPosition time)
1044{
1045 juce::ValueTree v (e.state.createCopy());
1046 v.setProperty (IDs::time, time.inBeats(), nullptr);
1047 return v;
1048}
1049
1050juce::ValueTree MidiSysexEvent::createSysexEvent (const juce::MidiMessage& m, BeatPosition time)
1051{
1052 return createValueTree (IDs::SYSEX,
1053 IDs::time, time.inBeats(),
1054 IDs::data, midiToHex (m));
1055}
1056
1057MidiSysexEvent::MidiSysexEvent (const juce::ValueTree& v) : state (v)
1058{
1059 updateMessage();
1060}
1061
1062void MidiSysexEvent::updateMessage()
1063{
1064 auto time = state.getProperty (IDs::time);
1065
1067 d.loadFromHexString (state.getProperty (IDs::data).toString());
1068
1069 if (d.getSize() > 0)
1070 message = juce::MidiMessage (d.getData(), (int) d.getSize(), time);
1071 else
1072 message.setTimeStamp (time);
1073}
1074
1075void MidiSysexEvent::updateTime()
1076{
1077 message.setTimeStamp (state.getProperty (IDs::time));
1078}
1079
1080template<>
1081struct MidiList::EventDelegate<MidiSysexEvent>
1082{
1083 static bool isSuitableType (const juce::ValueTree& v)
1084 {
1085 return v.hasType (IDs::SYSEX);
1086 }
1087
1088 static bool updateObject (MidiSysexEvent& m, const juce::Identifier& i)
1089 {
1090 if (i == IDs::time)
1091 {
1092 m.updateTime();
1093 return true;
1094 }
1095
1096 if (i == IDs::data)
1097 m.updateMessage();
1098
1099 return false;
1100 }
1101
1102 static void removeFromSelection (MidiSysexEvent* m)
1103 {
1104 removeMidiEventFromSelection (m);
1105 }
1106};
1107
1108TimePosition MidiSysexEvent::getEditTime (const MidiClip& c) const
1109{
1110 return c.edit.tempoSequence.toTime (getEditBeats (c));
1111}
1112
1113BeatPosition MidiSysexEvent::getEditBeats (const MidiClip& c) const
1114{
1115 return c.getQuantisation().roundBeatToNearest (getBeatPosition() - toDuration (c.getLoopStartBeats()) + toDuration (c.getContentStartBeat()));
1116}
1117
1118void MidiSysexEvent::setMessage (const juce::MidiMessage& m, juce::UndoManager* um)
1119{
1120 state.setProperty (IDs::data, midiToHex (m), um);
1121}
1122
1123void MidiSysexEvent::setBeatPosition (BeatPosition newBeatNumber, juce::UndoManager* um)
1124{
1125 state.setProperty (IDs::time, std::max (0.0, newBeatNumber.inBeats()), um);
1126}
1127
1128//==============================================================================
1129juce::ValueTree MidiList::createMidiList()
1130{
1131 return createValueTree (IDs::SEQUENCE,
1132 IDs::ver, 1,
1133 IDs::channelNumber, MidiChannel (1));
1134}
1135
1136MidiList::MidiList() : state (IDs::SEQUENCE)
1137{
1138 state.setProperty (IDs::ver, 1, nullptr);
1139 initialise (nullptr);
1140}
1141
1142MidiList::MidiList (const juce::ValueTree& v, juce::UndoManager* um)
1143 : state (v)
1144{
1145 jassert (state.hasType (IDs::SEQUENCE));
1146 state.setProperty (IDs::ver, 1, um);
1147 convertMidiPropertiesFromStrings (state);
1148
1149 initialise (um);
1150}
1151
1152MidiList::~MidiList()
1153{
1154}
1155
1156void MidiList::initialise (juce::UndoManager* um)
1157{
1159
1160 midiChannel.referTo (state, IDs::channelNumber, um);
1161 isComp.referTo (state, IDs::isComp, um, false);
1162
1163 noteList = std::make_unique<EventList<MidiNote>> (state);
1164 controllerList = std::make_unique<EventList<MidiControllerEvent>> (state);
1165 sysexList = std::make_unique<EventList<MidiSysexEvent>> (state);
1166}
1167
1168void MidiList::clear (juce::UndoManager* um)
1169{
1170 state.removeAllChildren (um);
1171 importedName = {};
1172}
1173
1175{
1176 if (this != &other)
1177 {
1178 clear (um);
1179 state.copyPropertiesFrom (other.state, um);
1180 addFrom (other, um);
1181 }
1182}
1183
1185{
1186 if (this != &other)
1187 for (int i = 0; i < other.state.getNumChildren(); ++i)
1188 state.addChild (other.state.getChild (i).createCopy(), -1, um);
1189}
1190
1192{
1193 midiChannel = newChannel;
1194}
1195
1196//==============================================================================
1197template<typename EventType>
1198const juce::Array<EventType*>& getEventsChecked (const juce::Array<EventType*>& events)
1199{
1200 #if JUCE_DEBUG
1201 BeatPosition lastBeat;
1202
1203 for (auto* e : events)
1204 {
1205 auto beat = e->getBeatPosition();
1206 jassert (lastBeat <= beat);
1207 lastBeat = beat;
1208 }
1209 #endif
1210
1211 return events;
1212}
1213
1214const juce::Array<MidiNote*>& MidiList::getNotes() const
1215{
1216 jassert (noteList != nullptr);
1217 return getEventsChecked (noteList->getSortedList());
1218}
1219
1220const juce::Array<MidiControllerEvent*>& MidiList::getControllerEvents() const
1221{
1222 jassert (controllerList != nullptr);
1223 return getEventsChecked (controllerList->getSortedList());
1224}
1225
1226const juce::Array<MidiSysexEvent*>& MidiList::getSysexEvents() const
1227{
1228 jassert (sysexList != nullptr);
1229 return getEventsChecked (sysexList->getSortedList());
1230}
1231
1232//==============================================================================
1233void MidiList::moveAllBeatPositions (BeatDuration delta, juce::UndoManager* um)
1234{
1235 if (delta != BeatDuration())
1236 {
1237 for (auto e : getNotes())
1238 e->setStartAndLength (e->getStartBeat() + delta, e->getLengthBeats(), um);
1239
1240 for (auto e : getControllerEvents())
1241 e->setBeatPosition (e->getBeatPosition() + delta, um);
1242
1243 for (auto e : getSysexEvents())
1244 e->setBeatPosition (e->getBeatPosition() + delta, um);
1245 }
1246}
1247
1248void MidiList::rescale (double factor, juce::UndoManager* um)
1249{
1250 if (factor != 1.0 && factor > 0.001 && factor < 1000.0)
1251 {
1252 for (auto e : getNotes())
1253 e->setStartAndLength (e->getStartBeat() * factor,
1254 e->getLengthBeats() * factor, um);
1255
1256 for (auto e : getControllerEvents())
1257 e->setBeatPosition (e->getBeatPosition() * factor, um);
1258
1259 for (auto e : getSysexEvents())
1260 e->setBeatPosition (e->getBeatPosition() * factor, um);
1261 }
1262}
1263
1264void MidiList::trimOutside (BeatPosition start, BeatPosition end, juce::UndoManager* um)
1265{
1266 juce::Array<juce::ValueTree> itemsToRemove;
1267
1268 for (auto n : getNotes())
1269 {
1270 if (n->getStartBeat() >= (end - BeatDuration::fromBeats (0.0001)) || n->getEndBeat() <= (start + BeatDuration::fromBeats (0.0001)))
1271 {
1272 itemsToRemove.add (n->state);
1273 }
1274 else if (n->getStartBeat() < start || n->getEndBeat() > end)
1275 {
1276 auto newStart = std::max (start, n->getStartBeat());
1277 auto newEnd = std::min (end, n->getEndBeat());
1278
1279 n->setStartAndLength (newStart, newEnd - newStart, um);
1280 }
1281 }
1282
1283 for (auto e : getControllerEvents())
1284 if (e->getBeatPosition() < start || e->getBeatPosition() >= end)
1285 itemsToRemove.add (e->state);
1286
1287 for (auto s : getSysexEvents())
1288 if (s->getBeatPosition() < start || s->getBeatPosition() >= end)
1289 itemsToRemove.add (s->state);
1290
1291 for (auto& v : itemsToRemove)
1292 state.removeChild (v, um);
1293}
1294
1295//==============================================================================
1297{
1298 auto t = BeatPosition::fromBeats (Edit::maximumLength);
1299
1300 if (auto first = getNotes().getFirst()) t = std::min (t, first->getStartBeat());
1301 if (auto first = getControllerEvents().getFirst()) t = std::min (t, first->getBeatPosition());
1302 if (auto first = getSysexEvents().getFirst()) t = std::min (t, first->getBeatPosition());
1303
1304 return t;
1305}
1306
1308{
1309 BeatPosition t;
1310
1311 if (auto last = getNotes().getLast()) t = std::max (t, last->getEndBeat());
1312 if (auto last = getControllerEvents().getLast()) t = std::max (t, last->getBeatPosition());
1313 if (auto last = getSysexEvents().getLast()) t = std::max (t, last->getBeatPosition());
1314
1315 return t;
1316}
1317
1318MidiNote* MidiList::getNoteFor (const juce::ValueTree& s)
1319{
1320 for (auto n : getNotes())
1321 if (n->state == s)
1322 return n;
1323
1324 return {};
1325}
1326
1327juce::Range<int> MidiList::getNoteNumberRange() const
1328{
1329 if (getNumNotes() == 0)
1330 return {};
1331
1332 int min = 127;
1333 int max = 0;
1334
1335 for (auto n : getNotes())
1336 {
1337 min = std::min (min, n->getNoteNumber());
1338 max = std::max (max, n->getNoteNumber());
1339 }
1340
1341 return { min, max };
1342}
1343
1344MidiNote* MidiList::addNote (const MidiNote& note, juce::UndoManager* um)
1345{
1346 auto v = note.state.createCopy();
1347 state.addChild (v, -1, um);
1348 return noteList->getEventFor (v);
1349}
1350
1351MidiNote* MidiList::addNote (int pitch, BeatPosition startBeat, BeatDuration lengthInBeats,
1352 int velocity, int colourIndex, juce::UndoManager* um)
1353{
1354 auto v = createNoteValueTree (pitch, startBeat, lengthInBeats, velocity, colourIndex);
1355 state.addChild (v, -1, um);
1356 return noteList->getEventFor (v);
1357}
1358
1359void MidiList::removeNote (MidiNote& note, juce::UndoManager* um)
1360{
1361 state.removeChild (note.state, um);
1362}
1363
1364void MidiList::removeAllNotes (juce::UndoManager* um)
1365{
1366 for (int i = state.getNumChildren(); --i >= 0;)
1367 if (state.getChild (i).hasType (IDs::NOTE))
1368 state.removeChild (i, um);
1369}
1370
1371//==============================================================================
1372MidiControllerEvent* MidiList::getControllerEventAt (BeatPosition beatNumber, int controllerType) const
1373{
1374 auto& controllerEvents = getControllerEvents();
1375
1376 for (int i = controllerEvents.size(); --i >= 0;)
1377 {
1378 auto e = controllerEvents.getUnchecked (i);
1379
1380 if (e->getBeatPosition() <= beatNumber && e->getType() == controllerType)
1381 return controllerEvents.getUnchecked (i);
1382 }
1383
1384 return {};
1385}
1386
1387bool MidiList::containsController (int controllerType) const
1388{
1389 for (auto e : getControllerEvents())
1390 if (e->getType() == controllerType)
1391 return true;
1392
1393 return false;
1394}
1395
1396MidiControllerEvent* MidiList::addControllerEvent (const MidiControllerEvent& event, juce::UndoManager* um)
1397{
1398 auto v = event.state.createCopy();
1399 state.addChild (v, -1, um);
1400 return controllerList->getEventFor (v);
1401}
1402
1403MidiControllerEvent* MidiList::addControllerEvent (BeatPosition beat, int controllerType, int controllerValue, juce::UndoManager* um)
1404{
1405 auto v = MidiControllerEvent::createControllerEvent (beat, controllerType, controllerValue);
1406 state.addChild (v, -1, um);
1407 return controllerList->getEventFor (v);
1408}
1409
1410MidiControllerEvent* MidiList::addControllerEvent (BeatPosition beat, int controllerType, int controllerValue, int metadata, juce::UndoManager* um)
1411{
1412 auto v = MidiControllerEvent::createControllerEvent (beat, controllerType, controllerValue, metadata);
1413 state.addChild (v, -1, um);
1414 return controllerList->getEventFor (v);
1415}
1416
1417void MidiList::removeControllerEvent (MidiControllerEvent& e, juce::UndoManager* um)
1418{
1419 state.removeChild (e.state, um);
1420}
1421
1422void MidiList::removeAllControllers (juce::UndoManager* um)
1423{
1424 for (int i = state.getNumChildren(); --i >= 0;)
1425 if (state.getChild (i).hasType (IDs::CONTROL))
1426 state.removeChild (i, um);
1427}
1428
1429void MidiList::setControllerValueAt (int controllerType, BeatPosition beatNumber, int newValue, juce::UndoManager* um)
1430{
1431 beatNumber = std::max (BeatPosition(), beatNumber);
1432 auto& controllerEvents = getControllerEvents();
1433
1434 for (int i = controllerEvents.size(); --i >= 0;) // N.B.: The order matters!
1435 {
1436 auto e = controllerEvents.getUnchecked (i);
1437
1438 if (e->getType() == controllerType
1439 && e->getBeatPosition() <= beatNumber) // N.B.: '<=' because we want the event the beat is within
1440 {
1441 e->setControllerValue (newValue, um);
1442 break;
1443 }
1444 }
1445}
1446
1447static int interpolate (int startValue, int rangeValues, BeatPosition startBeat, BeatPosition beat, BeatDuration rangeBeats) noexcept
1448{
1449 return juce::roundToInt (startValue + (rangeValues * (beat - startBeat).inBeats() / rangeBeats.inBeats()));
1450}
1451
1452void MidiList::insertRepeatedControllerValue (int type, int startVal, int endVal,
1453 BeatRange beats, BeatDuration intervalBeats, juce::UndoManager* um)
1454{
1455 auto rangeBeats = beats.getLength();
1456 auto rangeValues = endVal - startVal;
1457
1458 if (rangeBeats == BeatDuration())
1459 {
1460 addControllerEvent (beats.getStart(), type, startVal, um);
1461 addControllerEvent (beats.getEnd(), type, endVal, um);
1462 return;
1463 }
1464
1465 auto maxNumSteps = std::max (2, type == MidiControllerEvent::pitchWheelType ? std::abs (rangeValues)
1466 : std::abs (rangeValues) >> 7);
1467 auto numSteps = juce::roundToInt (rangeBeats.inBeats() / intervalBeats.inBeats());
1468 auto limitedNumSteps = juce::jlimit (2, maxNumSteps, numSteps);
1469
1470 if (numSteps != limitedNumSteps)
1471 {
1472 numSteps = limitedNumSteps;
1473 intervalBeats = BeatDuration::fromBeats (rangeBeats.inBeats() / numSteps);
1474 }
1475
1476 auto beat = beats.getStart();
1477
1478 for (int i = 0; i < numSteps; ++i)
1479 {
1480 const int value = interpolate (startVal, rangeValues, beats.getStart(), beat, rangeBeats);
1481 addControllerEvent (beat, type, value, um);
1482 beat = beat + intervalBeats;
1483 }
1484
1485 if (beat - intervalBeats < beats.getEnd())
1486 addControllerEvent (beats.getEnd(), type, endVal, um);
1487}
1488
1489void MidiList::removeControllersBetween (int controllerType, BeatPosition beatStart, BeatPosition beatEnd, juce::UndoManager* um)
1490{
1491 juce::Array<juce::ValueTree> itemsToRemove;
1492
1493 for (auto e : getControllerEvents())
1494 if (e->getType() == controllerType && e->getBeatPosition() >= beatStart && e->getBeatPosition() < beatEnd)
1495 itemsToRemove.add (e->state);
1496
1497 for (auto& v : itemsToRemove)
1498 state.removeChild (v, um);
1499}
1500
1501//==============================================================================
1502MidiSysexEvent* MidiList::getSysexEventFor (const juce::ValueTree& v) const
1503{
1504 for (auto n : getSysexEvents())
1505 if (n->state == v)
1506 return n;
1507
1508 return {};
1509}
1510
1511MidiSysexEvent& MidiList::addSysExEvent (const juce::MidiMessage& message, BeatPosition beat, juce::UndoManager* um)
1512{
1513 auto v = MidiSysexEvent::createSysexEvent (message, beat);
1514 state.addChild (v, -1, um);
1515 sysexList->triggerSort();
1516
1517 return *sysexList->getEventFor (v);
1518}
1519
1520void MidiList::removeSysExEvent (const MidiSysexEvent& event, juce::UndoManager* um)
1521{
1522 state.removeChild (event.state, um);
1523}
1524
1525void MidiList::removeAllSysexes (juce::UndoManager* um)
1526{
1527 for (int i = state.getNumChildren(); --i >= 0;)
1528 if (state.getChild (i).hasType (IDs::SYSEX))
1529 state.removeChild (i, um);
1530}
1531
1532//==============================================================================
1533bool MidiList::fileHasTempoChanges (const juce::File& f)
1534{
1535 juce::MidiFile midiFile;
1536
1537 {
1538 juce::FileInputStream in (f);
1539
1540 if (! in.openedOk() || ! midiFile.readFrom (in))
1541 return false;
1542 }
1543
1544 juce::MidiMessageSequence tempoEvents;
1545 midiFile.findAllTempoEvents (tempoEvents);
1546 midiFile.findAllTimeSigEvents (tempoEvents);
1547
1548 return tempoEvents.getNumEvents() > 0;
1549}
1550
1551bool MidiList::looksLikeMPEData (const juce::File& f)
1552{
1553 juce::MidiFile midiFile;
1554
1555 {
1556 juce::FileInputStream in (f);
1557
1558 if (! in.openedOk() || ! midiFile.readFrom (in))
1559 return false;
1560 }
1561
1562 juce::BigInteger noteChannelsUsed, controllerChannelsUsed;
1563
1564 for (int currentTrack = midiFile.getNumTracks(); --currentTrack >= 0;)
1565 {
1566 auto& trackSequence = *midiFile.getTrack (currentTrack);
1567
1568 for (int eventIndex = trackSequence.getNumEvents(); --eventIndex >= 0;)
1569 {
1570 if (auto* event = trackSequence.getEventPointer (eventIndex))
1571 {
1572 const auto& m = event->message;
1573
1574 if (m.isNoteOn())
1575 noteChannelsUsed.setBit (m.getChannel());
1576 else if (m.isController() || m.isPitchWheel() || m.isChannelPressure())
1577 controllerChannelsUsed.setBit (m.getChannel());
1578
1579 if (noteChannelsUsed.countNumberOfSetBits() > 1
1580 && controllerChannelsUsed.countNumberOfSetBits() > 1)
1581 return true;
1582 }
1583 }
1584 }
1585
1586 return false;
1587}
1588
1589bool MidiList::readSeparateTracksFromFile (const juce::File& f,
1591 juce::Array<BeatPosition>& tempoChangeBeatNumbers,
1592 juce::Array<double>& bpms,
1593 juce::Array<int>& numerators,
1594 juce::Array<int>& denominators,
1595 BeatDuration& songLength,
1596 bool importAsNoteExpression)
1597{
1598 songLength = BeatDuration();
1599 juce::MidiFile midiFile;
1600
1601 {
1602 juce::FileInputStream in (f);
1603
1604 if (! in.openedOk() || ! midiFile.readFrom (in))
1605 return false;
1606 }
1607
1608 juce::MidiMessageSequence tempoEvents;
1609 midiFile.findAllTempoEvents (tempoEvents);
1610 midiFile.findAllTimeSigEvents (tempoEvents);
1611
1612 tempoChangeBeatNumbers.clearQuick();
1613 bpms.clearQuick();
1614 numerators.clearQuick();
1615 denominators.clearQuick();
1616
1617 int numer = 4, denom = 4;
1618 double secsPerQuarterNote = 0.5;
1619
1620 auto timeFormat = midiFile.getTimeFormat();
1621 auto tickLen = 1.0 / (timeFormat > 0 ? timeFormat & 0x7fff
1622 : ((timeFormat & 0x7fff) >> 8) * (timeFormat & 0xff));
1623
1624 for (int i = 0; i < tempoEvents.getNumEvents(); ++i)
1625 {
1626 auto& msg = tempoEvents.getEventPointer (i)->message;
1627
1628 if (msg.isTempoMetaEvent())
1629 secsPerQuarterNote = msg.getTempoSecondsPerQuarterNote();
1630 else if (msg.isTimeSignatureMetaEvent())
1631 msg.getTimeSignatureInfo (numer, denom);
1632
1633 while ((i + 1) < tempoEvents.getNumEvents())
1634 {
1635 auto& msg2 = tempoEvents.getEventPointer (i + 1)->message;
1636
1637 if (msg2.getTimeStamp() == msg.getTimeStamp())
1638 {
1639 ++i;
1640
1641 if (msg2.isTempoMetaEvent())
1642 secsPerQuarterNote = msg2.getTempoSecondsPerQuarterNote();
1643 else if (msg2.isTimeSignatureMetaEvent())
1644 msg2.getTimeSignatureInfo (numer, denom);
1645 }
1646 else
1647 {
1648 break;
1649 }
1650 }
1651
1652 tempoChangeBeatNumbers.add (BeatPosition::fromBeats (tickLen * msg.getTimeStamp()));
1653 bpms.add (4.0 * 60.0 / (denom * secsPerQuarterNote));
1654 numerators.add (numer);
1655 denominators.add (denom);
1656 }
1657
1658 if (importAsNoteExpression)
1659 {
1660 juce::MidiMessageSequence destSequence;
1661
1662 for (int currentTrack = 0; currentTrack < midiFile.getNumTracks(); ++currentTrack)
1663 {
1664 auto& trackSequence = *midiFile.getTrack (currentTrack);
1665
1666 if (trackSequence.getNumEvents() > 0)
1667 {
1668 for (int i = trackSequence.getNumEvents(); --i >= 0;)
1669 {
1670 auto& msg = trackSequence.getEventPointer (i)->message;
1671 msg.setTimeStamp (tickLen * msg.getTimeStamp());
1672 }
1673
1674 for (int channel = 0; channel <= 16; ++channel)
1675 {
1676 if (channel == 0)
1677 trackSequence.extractSysExMessages (destSequence);
1678 else
1679 trackSequence.extractMidiChannelMessages (channel, destSequence, true);
1680 }
1681 }
1682 }
1683
1684 if (destSequence.getNumEvents() > 0)
1685 {
1686 destSequence.sort();
1687 destSequence.updateMatchedPairs();
1688
1689 songLength = std::max (songLength, BeatDuration::fromBeats (destSequence.getStartTime() + destSequence.getEndTime()));
1690
1691 std::unique_ptr<MidiList> midiList (new MidiList());
1692 midiList->importFromEditTimeSequenceWithNoteExpression (destSequence, nullptr, TimePosition(), nullptr);
1693
1694 if (! midiList->isEmpty())
1695 lists.add (midiList.release());
1696 }
1697 }
1698 else
1699 {
1700 for (int currentTrack = 0; currentTrack < midiFile.getNumTracks(); ++currentTrack)
1701 {
1702 auto& trackSequence = *midiFile.getTrack (currentTrack);
1703
1704 if (trackSequence.getNumEvents() > 0)
1705 {
1706 for (int i = trackSequence.getNumEvents(); --i >= 0;)
1707 {
1708 auto& msg = trackSequence.getEventPointer (i)->message;
1709 msg.setTimeStamp (tickLen * msg.getTimeStamp());
1710 }
1711
1712 for (int channel = 0; channel <= 16; ++channel)
1713 {
1714 juce::MidiMessageSequence channelSequence;
1715 MidiChannel midiChannel ((uint8_t) std::max (1, channel));
1716
1717 if (channel == 0)
1718 trackSequence.extractSysExMessages (channelSequence);
1719 else
1720 trackSequence.extractMidiChannelMessages (channel, channelSequence, true);
1721
1722 if (channelSequence.getNumEvents() > 0)
1723 {
1724 channelSequence.sort();
1725 channelSequence.updateMatchedPairs();
1726
1727 songLength = std::max (songLength, BeatDuration::fromBeats (channelSequence.getEndTime()));
1728
1729 std::unique_ptr<MidiList> midiList (new MidiList());
1730 midiList->setMidiChannel (midiChannel);
1731 midiList->setImportedFileName (f.getFileName());
1732 midiList->importMidiSequence (channelSequence, nullptr, TimePosition(), nullptr);
1733
1734 if (! midiList->isEmpty())
1735 lists.add (midiList.release());
1736 }
1737 }
1738 }
1739 }
1740 }
1741
1742 return true;
1743}
1744
1745//==============================================================================
1747 TimePosition editTimeOfListTimeZero, juce::UndoManager* um)
1748{
1749 auto ts = edit != nullptr ? &edit->tempoSequence : nullptr;
1750 auto firstBeatNum = ts != nullptr ? ts->toBeats (editTimeOfListTimeZero) : BeatPosition();
1751 const int channelNumber = getMidiChannel().getChannelNumber();
1752
1753 for (int i = 0; i < sequence.getNumEvents(); ++i)
1754 {
1755 auto& m = sequence.getEventPointer (i)->message;
1756
1757 auto beatTime = ts != nullptr ? ts->toBeats (TimePosition::fromSeconds (m.getTimeStamp())) - toDuration (firstBeatNum)
1758 : BeatPosition::fromBeats (m.getTimeStamp());
1759
1760 if (m.isSysEx())
1761 {
1762 importedName = TRANS("SysEx");
1763 addSysExEvent (m, beatTime, um);
1764 }
1765 else if (m.isTrackNameEvent())
1766 {
1767 importedName = m.getTextFromTextMetaEvent();
1768 }
1769 else if (m.isForChannel (channelNumber))
1770 {
1771 if (channelNumber == 10)
1772 importedName = TRANS("Drums");
1773
1774 if (m.isNoteOn())
1775 {
1776 const auto keyUpTime = TimePosition::fromSeconds (sequence.getTimeOfMatchingKeyUp (i));
1777
1778 addNote (m.getNoteNumber(),
1779 beatTime,
1780 ts != nullptr ? ((ts->toBeats (keyUpTime) - firstBeatNum) - toDuration (beatTime))
1781 : (BeatDuration::fromBeats (keyUpTime.inSeconds()) - toDuration (beatTime)),
1782 m.getVelocity(), edit != nullptr ? edit->engine.getEngineBehaviour().getDefaultNoteColour() : 0, um);
1783 }
1784 else if (m.isAftertouch())
1785 {
1786 addControllerEvent (beatTime, MidiControllerEvent::aftertouchType,
1787 m.getAfterTouchValue() << 7, m.getNoteNumber(), um);
1788 }
1789 else if (m.isPitchWheel())
1790 {
1791 addControllerEvent (beatTime, MidiControllerEvent::pitchWheelType, m.getPitchWheelValue(), um);
1792 }
1793 else if (m.isController())
1794 {
1795 addControllerEvent (beatTime, m.getControllerNumber(), m.getControllerValue() << 7, um);
1796 }
1797 else if (m.isProgramChange())
1798 {
1799 if (importedName.isEmpty())
1800 importedName = TRANS(juce::MidiMessage::getGMInstrumentName (m.getProgramChangeNumber()));
1801
1802 addControllerEvent (beatTime, MidiControllerEvent::programChangeType, m.getProgramChangeNumber() << 7, um);
1803 }
1804 else if (m.isChannelPressure())
1805 {
1806 addControllerEvent (beatTime, MidiControllerEvent::channelPressureType, m.getChannelPressureValue() << 7, um);
1807 }
1808 }
1809 }
1810
1811 if (importedName.isEmpty())
1812 importedName = TRANS("channel") + " " + juce::String (channelNumber);
1813}
1814
1816 TimePosition editTimeOfListTimeZero, juce::UndoManager* um)
1817{
1818 auto ts = edit != nullptr ? &edit->tempoSequence : nullptr;
1819 auto firstBeatNum = ts != nullptr ? ts->toBeats (editTimeOfListTimeZero) : BeatPosition();
1820 const int channelNumber = getMidiChannel().getChannelNumber();
1821
1822 juce::MPEZoneLayout layout;
1823 layout.setLowerZone (15);
1824 MPEtoNoteExpression noteQueue (*this, ts, layout, firstBeatNum, um);
1825
1826 for (int i = 0; i < sequence.getNumEvents(); ++i)
1827 {
1828 auto& m = sequence.getEventPointer (i)->message;
1829 const auto beatTime = ts != nullptr ? ts->toBeats (TimePosition::fromSeconds (m.getTimeStamp())) - toDuration (firstBeatNum)
1830 : BeatPosition::fromBeats (m.getTimeStamp());
1831
1832 if (m.isSysEx())
1833 {
1834 importedName = TRANS("SysEx");
1835 addSysExEvent (m, beatTime, um);
1836 }
1837 else if (m.isTrackNameEvent())
1838 {
1839 importedName = m.getTextFromTextMetaEvent();
1840 }
1841 else
1842 {
1843 if (channelNumber == 10)
1844 importedName = TRANS("Drums");
1845
1846 if (m.isNoteOn() || m.isNoteOff() || m.isPitchWheel() || m.isChannelPressure()
1847 || (m.isController() && m.getControllerNumber() == 74))
1848 {
1849 noteQueue.processMidiMessage (m);
1850 }
1851 else if (m.isAftertouch())
1852 {
1853 addControllerEvent (beatTime, MidiControllerEvent::aftertouchType, m.getAfterTouchValue() << 7, m.getNoteNumber(), um);
1854 }
1855 else if (m.isPitchWheel())
1856 {
1857 addControllerEvent (beatTime, MidiControllerEvent::pitchWheelType, m.getPitchWheelValue(), um);
1858 }
1859 else if (m.isController())
1860 {
1861 addControllerEvent (beatTime, m.getControllerNumber(), m.getControllerValue() << 7, um);
1862 }
1863 else if (m.isProgramChange())
1864 {
1865 if (importedName.isEmpty())
1866 importedName = TRANS(juce::MidiMessage::getGMInstrumentName (m.getProgramChangeNumber()));
1867
1868 addControllerEvent (beatTime, MidiControllerEvent::programChangeType, m.getProgramChangeNumber() << 7, um);
1869 }
1870 else if (m.isChannelPressure())
1871 {
1872 addControllerEvent (beatTime, MidiControllerEvent::channelPressureType, m.getChannelPressureValue() << 7, um);
1873 }
1874 }
1875 }
1876
1877 if (importedName.isEmpty())
1878 importedName = TRANS("channel") + " " + juce::String (channelNumber);
1879}
1880
1881//==============================================================================
1883{
1884 return clip.edit.engine.getEngineBehaviour().createPlaybackMidiSequence (*this, clip, tb, generateMPE);
1885}
1886
1888{
1889 juce::MidiMessageSequence destSequence;
1890
1891 auto& ts = clip.edit.tempoSequence;
1892 auto midiStartBeat = clip.getContentStartBeat();
1893 auto channelNumber = list.getMidiChannel().getChannelNumber();
1894
1895 // NB: allow extra space here in case the notes get quantised or nudged around later on..
1896 const auto overlapAllowance = 0.5_bd;
1897 auto firstNoteBeat = timeBase == TimeBase::beatsRaw ? list.getFirstBeatNumber() - overlapAllowance
1898 : toPosition (ts.toBeats (clip.getPosition().getStart()) - midiStartBeat - overlapAllowance);
1899 auto lastNoteBeat = timeBase == TimeBase::beatsRaw ? list.getLastBeatNumber() + overlapAllowance
1900 : toPosition (ts.toBeats (clip.getPosition().getEnd()) - midiStartBeat + overlapAllowance);
1901
1902 auto& notes = list.getNotes();
1903 const auto numNotes = notes.size();
1904 auto selectedEvents = clip.getSelectedEvents();
1905
1906 auto grooveTemplate = clip.edit.engine.getGrooveTemplateManager().getTemplateByName (clip.getGrooveTemplate());
1907
1908 if (grooveTemplate != nullptr && grooveTemplate->isEmpty())
1909 grooveTemplate = nullptr;
1910
1911 // Do controllers first in case they send and program or bank change messages
1912 auto& controllerEvents = list.getControllerEvents();
1913
1914 {
1915 // Add cumulative controller events that are off the start
1916 juce::Array<int> doneControllers;
1917
1918 for (auto e : controllerEvents)
1919 {
1920 auto beat = e->getBeatPosition();
1921
1922 if (beat < firstNoteBeat)
1923 {
1924 if (! doneControllers.contains (e->getType()))
1925 {
1926 addToSequence (destSequence, clip, timeBase, *e, channelNumber);
1927 doneControllers.add (e->getType());
1928 }
1929 }
1930 }
1931 }
1932
1933 // Add the real controller events:
1934 for (auto e : controllerEvents)
1935 {
1936 auto beat = e->getBeatPosition();
1937
1938 if (beat >= firstNoteBeat && beat < lastNoteBeat)
1939 addToSequence (destSequence, clip, timeBase, *e, channelNumber);
1940 }
1941
1942 // Then the note events
1943 if (! generateMPE)
1944 {
1945 for (int i = 0; i < numNotes; ++i)
1946 {
1947 auto& note = *notes.getUnchecked (i);
1948
1949 if (selectedEvents != nullptr && ! selectedEvents->isSelected (&note))
1950 continue;
1951
1952 // check for subsequent overlaps
1953 auto thisNoteStart = note.getStartBeat();
1954
1955 if (thisNoteStart >= lastNoteBeat)
1956 break;
1957
1958 auto thisNoteEnd = note.getEndBeat();
1959 auto noteNum = note.getNoteNumber();
1960 bool useNoteUp = true;
1961
1962 for (int j = i + 1; j < numNotes; ++j)
1963 {
1964 const auto& note2 = *notes.getUnchecked (j);
1965 const auto s = note2.getStartBeat();
1966
1967 if (s >= lastNoteBeat || s >= thisNoteEnd)
1968 break;
1969
1970 if (note2.getNoteNumber() == noteNum)
1971 {
1972 useNoteUp = false;
1973 break;
1974 }
1975 }
1976
1977 if (thisNoteEnd > firstNoteBeat)
1978 addToSequence (destSequence, clip, timeBase, note, channelNumber, useNoteUp, grooveTemplate);
1979 }
1980 }
1981 else
1982 {
1983 MPEChannelAssigner assigner (destSequence, clip, timeBase, grooveTemplate);
1984
1985 for (auto note : notes)
1986 {
1987 if (selectedEvents != nullptr && ! selectedEvents->isSelected (note))
1988 continue;
1989
1990 assigner.addNote (*note);
1991 }
1992 }
1993
1994 // Add the SysEx events:
1995 for (auto e : list.getSysexEvents())
1996 {
1997 auto beat = e->getBeatPosition();
1998
1999 if (beat >= firstNoteBeat && beat < lastNoteBeat)
2000 addToSequence (destSequence, clip, timeBase, *e);
2001 }
2002
2003 return destSequence;
2004}
2005
2006}} // namespace tracktion { inline namespace engine
void clearQuick()
void add(const ElementType &newElement)
bool contains(ParameterType elementToLookFor) const
int countNumberOfSetBits() const noexcept
BigInteger & setBit(int bitNumber)
void referTo(ValueTree &tree, const Identifier &property, UndoManager *um)
String getFileName() const
void setZoneLayout(MPEZoneLayout newLayout)
virtual void processNextMidiEvent(const MidiMessage &message)
void addListener(Listener *listenerToAdd)
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
MPEZone getLowerZone() const noexcept
void * getData() noexcept
size_t getSize() const noexcept
void loadFromHexString(StringRef sourceHexString)
int getNumTracks() const noexcept
short getTimeFormat() const noexcept
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true, int *midiFileType=nullptr)
void findAllTimeSigEvents(MidiMessageSequence &timeSigEvents) const
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
const MidiMessageSequence * getTrack(int index) const noexcept
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
void updateMatchedPairs() noexcept
double getStartTime() const noexcept
double getEndTime() const noexcept
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
static MidiMessage aftertouchChange(int channel, int noteNumber, int aftertouchAmount) noexcept
static MidiMessage pitchWheel(int channel, int position) noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
static uint16 pitchbendToPitchwheelPos(float pitchbendInSemitones, float pitchbendRangeInSemitones) noexcept
double getTimeStamp() const noexcept
static const char * getGMInstrumentName(int midiInstrumentNumber)
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
static uint8 floatValueToMidiByte(float valueBetween0and1) noexcept
static MidiMessage channelPressureChange(int channel, int pressure) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
void setTimeStamp(double newTimestamp) noexcept
static MidiMessage programChange(int channel, int programNumber) noexcept
static const char * getControllerName(int controllerNumber)
int size() const noexcept
ObjectClass * add(ObjectClass *newObject)
void removeObject(const ObjectClass *objectToRemove, bool deleteObject=true)
bool isEmpty() const noexcept
static String toHexString(IntegerType number)
bool hasType(const Identifier &typeName) const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
void copyPropertiesFrom(const ValueTree &source, UndoManager *undoManager)
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void removeAllChildren(UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
const var & getProperty(const Identifier &name) const noexcept
ValueTree createCopy() const
Track * getTrack() const override
Returns the parent Track this clip is on (if any).
The Tracktion Edit class!
static constexpr double maximumLength
The maximum length an Edit can be.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
static const int ticksPerQuarterNote
The number of ticks per quarter note.
Engine & engine
A reference to the Engine.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
Determines the channels to assign to overlapping notes.
MPEChannelAssigner(juce::MidiMessageSequence &s, const MidiClip &c, MidiList::TimeBase tb, const GrooveTemplate *g)
Constructor.
Creates a MidiList with NoteExpression from a stream of MPE MIDI messages.
BeatPosition getEditBeats(const MidiClip &) const
This takes into account quantising, groove templates, clip offset, etc.
void insertRepeatedControllerValue(int type, int startVal, int endVal, BeatRange rangeBeats, BeatDuration intervalBeats, juce::UndoManager *)
Adds controller values over a specified time, at an even interval.
void copyFrom(const MidiList &, juce::UndoManager *)
Clears the current list and copies the others contents and properties.
MidiChannel getMidiChannel() const
Gets the list's midi channel number.
void setMidiChannel(MidiChannel chanNum)
Gives the list a channel number that it'll use when generating real midi messages.
void addFrom(const MidiList &, juce::UndoManager *)
Adds copies of the events in another list to this one.
juce::MidiMessageSequence exportToPlaybackMidiSequence(MidiClip &, TimeBase, bool generateMPE) const
Creates a juce::MidiMessageSequence from the list in order to be played back The sequence will be in ...
void importMidiSequence(const juce::MidiMessageSequence &, Edit *, TimePosition editTimeOfListTimeZero, juce::UndoManager *)
Adds the contents of a MidiMessageSequence to this list.
BeatPosition getLastBeatNumber() const
Beat number of last event in the list.
bool containsController(int controllerType) const
True if there are any controller events of this type.
TimeBase
Determines MIDI event timing.
@ beats
Event times will be in seconds relative to the Edit timeline.
@ beatsRaw
Event times will be in beats relative to the Edit timeline.
BeatPosition getFirstBeatNumber() const
Beat number of first event in the list.
void importFromEditTimeSequenceWithNoteExpression(const juce::MidiMessageSequence &, Edit *, TimePosition editTimeOfListTimeZero, juce::UndoManager *)
Adds the contents of a MidiSequence to this list assigning MPE expression changes to EXP expression.
static juce::MidiMessageSequence createDefaultPlaybackMidiSequence(const MidiList &, MidiClip &, TimeBase, bool generateMPE)
Creates the default MIDI playback sequence.
BeatPosition getQuantisedStartBeat(const MidiClip &) const
Returns the start, quantised according to the clip's settings.
TimePosition getEditStartTime(const MidiClip &) const
Gets the start of this note in terms of edit time, taking into account quantising,...
Holds a list of TempoSetting objects, to form a sequence of tempo changes.
TimePosition toTime(BeatPosition) const
Converts a number of beats a time.
BeatPosition toBeats(TimePosition) const
Converts a time to a number of beats.
T end(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
typedef int
typedef float
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
BeatPosition toBeats(TimePosition tp, const TempoSequence &ts)
Converts a TimePosition to a BeatPosition given a TempoSequence.
constexpr TimePosition toPosition(TimeDuration)
Converts a TimeDuration to a TimePosition.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
T size(T... args)
typedef uint8_t
Represents a duration in beats.
constexpr double inBeats() const
Returns the position as a number of beats.
Represents a position in beats.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
Represents a MIDI channel 1-16, and also contains extra ID bits to encode info about the event's orig...
static bool listHasExpression(const MidiList &midiList) noexcept
Returns true if this MidiList is an expressive note.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.