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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_MidiNodeHelpers.h
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
11#pragma once
12
13namespace tracktion { inline namespace engine
14{
15
16namespace MidiNodeHelpers
17{
18 inline void createMessagesForTime (MidiMessageArray& destBuffer,
19 juce::MidiMessageSequence& sourceSequence, double time,
20 juce::Range<int> channelNumbers,
21 LiveClipLevel& clipLevel,
22 bool useMPEChannelMode, MidiMessageArray::MPESourceID midiSourceID,
23 juce::Array<juce::MidiMessage>& controllerMessagesScratchBuffer)
24 {
25 if (useMPEChannelMode)
26 {
27 const int indexOfTime = sourceSequence.getNextIndexAtTime (time);
28
29 controllerMessagesScratchBuffer.clearQuick();
30
31 for (int i = channelNumbers.getStart(); i < channelNumbers.getEnd(); ++i)
32 MPEStartTrimmer::reconstructExpression (controllerMessagesScratchBuffer, sourceSequence, indexOfTime, i);
33
34 for (auto& m : controllerMessagesScratchBuffer)
35 destBuffer.addMidiMessage (m, 0.0001, midiSourceID);
36 }
37 else
38 {
39 {
40 controllerMessagesScratchBuffer.clearQuick();
41
42 for (int i = channelNumbers.getStart(); i < channelNumbers.getEnd(); ++i)
43 sourceSequence.createControllerUpdatesForTime (i, time, controllerMessagesScratchBuffer);
44
45 for (auto& m : controllerMessagesScratchBuffer)
46 destBuffer.addMidiMessage (m, 0.0001, midiSourceID);
47 }
48
49 if (! clipLevel.isMute())
50 {
51 auto volScale = clipLevel.getGain();
52
53 for (int i = 0; i < sourceSequence.getNumEvents(); ++i)
54 {
55 if (auto meh = sourceSequence.getEventPointer (i))
56 {
57 if (meh->noteOffObject != nullptr
58 && meh->message.isNoteOn())
59 {
60 if (meh->message.getTimeStamp() >= time)
61 break;
62
63 // don't play very short notes or ones that have already finished
64 if (meh->noteOffObject->message.getTimeStamp() > time + 0.0001)
65 {
66 juce::MidiMessage m (meh->message);
67 m.multiplyVelocity (volScale);
68
69 // give these a tiny offset to make sure they're played after the controller updates
70 destBuffer.addMidiMessage (m, 0.0001, midiSourceID);
71 }
72 }
73 }
74 }
75 }
76 }
77 }
78
79 inline void createNoteOffs (MidiMessageArray& destination, const juce::MidiMessageSequence& sourceSequence,
80 MidiMessageArray::MPESourceID midiSourceID,
81 double time, double midiTimeOffset, bool isPlaying)
82 {
83 int activeChannels = 0;
84
85 for (int i = 0; i < sourceSequence.getNumEvents(); ++i)
86 {
87 if (auto meh = sourceSequence.getEventPointer (i))
88 {
89 if (meh->message.isNoteOn())
90 {
91 activeChannels |= (1 << meh->message.getChannel());
92
93 if (meh->message.getTimeStamp() < time
94 && meh->noteOffObject != nullptr
95 && meh->noteOffObject->message.getTimeStamp() >= time)
96 destination.addMidiMessage (meh->noteOffObject->message, midiTimeOffset, midiSourceID);
97 }
98 }
99 else
100 {
101 break;
102 }
103 }
104
105 for (int i = 1; i <= 16; ++i)
106 {
107 if ((activeChannels & (1 << i)) != 0)
108 {
109 destination.addMidiMessage (juce::MidiMessage::controllerEvent (i, 66 /* sustain pedal off */, 0), midiTimeOffset, midiSourceID);
110 destination.addMidiMessage (juce::MidiMessage::controllerEvent (i, 64 /* hold pedal off */, 0), midiTimeOffset, midiSourceID);
111
112 // NB: Some buggy plugins seem to fail to respond to note-ons if they are preceded
113 // by an all-notes-off, so avoid this while playing.
114 if (! isPlaying)
115 destination.addMidiMessage (juce::MidiMessage::allNotesOff (i), midiTimeOffset, midiSourceID);
116 }
117 }
118 }
119
121 inline void sanityCheckMidiBuffer (const MidiMessageArray& midi, double maxTimeStamp)
122 {
123 for (const auto& m : midi)
124 jassertquiet (m.getTimeStamp() < maxTimeStamp);
125 }
126
127 inline void createNoteOffs (ActiveNoteList& activeNoteList,
128 MidiMessageArray& destination,
129 MidiMessageArray::MPESourceID midiSourceID,
130 double midiTimeOffset, bool isPlaying)
131 {
132 int activeChannels = 0;
133
134 // First send note-off events for currently playing notes
135 activeNoteList.iterate ([&] (int channel, int noteNumber)
136 {
137 activeChannels |= (1 << channel);
138 destination.addMidiMessage (juce::MidiMessage::noteOff (channel, noteNumber), midiTimeOffset, midiSourceID);
139 });
140 activeNoteList.reset();
141
142 // Send controller off events for used channels
143 for (int i = 1; i <= 16; ++i)
144 {
145 if ((activeChannels & (1 << i)) != 0)
146 {
147 destination.addMidiMessage (juce::MidiMessage::controllerEvent (i, 66 /* sustain pedal off */, 0), midiTimeOffset, midiSourceID);
148 destination.addMidiMessage (juce::MidiMessage::controllerEvent (i, 64 /* hold pedal off */, 0), midiTimeOffset, midiSourceID);
149
150 // NB: Some buggy plugins seem to fail to respond to note-ons if they are preceded
151 // by an all-notes-off, so avoid this while playing.
152 if (! isPlaying)
153 destination.addMidiMessage (juce::MidiMessage::allNotesOff (i), midiTimeOffset, midiSourceID);
154 }
155 }
156 }
157}
158
159}} // namespace tracktion { inline namespace engine
void clearQuick()
void createControllerUpdatesForTime(int channelNumber, double time, Array< MidiMessage > &resultMessages)
int getNextIndexAtTime(double timeStamp) const noexcept
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
static MidiMessage allNotesOff(int channel) noexcept
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
constexpr ValueType getStart() const noexcept
constexpr ValueType getEnd() const noexcept
#define jassertquiet(expression)
static void reconstructExpression(juce::Array< juce::MidiMessage > &mpeMessagesToAddAtStart, const juce::MidiMessageSequence &data, int trimIndex, int channel)
Reconstruct note expression for a particular channel.
time
void sanityCheckMidiBuffer(const MidiMessageArray &midi, double maxTimeStamp)
Asserts if any MIDI messages are timestamped outside the given range.