11namespace tracktion {
inline namespace engine
18 : owner (m), transport (m.edit.getTransport())
26 m.getFullFrameParameters (hours, minutes, seconds, frames, midiTCType);
38 if (m.isQuarterFrame())
40 const int value = m.getQuarterFrameValue();
48 switch (m.getQuarterFrameSequenceNumber())
50 case 0: frames = (frames & 0xf0) | value;
break;
51 case 1: frames = (frames & 0x0f) | (value << 4);
break;
52 case 2: seconds = (seconds & 0xf0) | value;
break;
53 case 3: seconds = (seconds & 0x0f) | (value << 4);
break;
54 case 4: minutes = (minutes & 0xf0) | value;
break;
55 case 5: minutes = (minutes & 0x0f) | (value << 4);
break;
56 case 6: hours = (hours & 0xf0) | value;
break;
60 hours = (hours & 0x0f) | ((value << 4) & 0x10);
63 lastTime = getTime() + 2.0 / getFPS();
67 const double drift = correctedTime - owner.
context.getPosition().
inSeconds();
69 averageDrift = averageDrift * 0.9 + drift * 0.1;
70 ++averageDriftNumSamples;
74 if (std::abs (drift) > 2.0)
78 averageDriftNumSamples = 0;
80 else if (std::abs (averageDrift) > 0.05
81 && averageDriftNumSamples > 50)
83 speedComp = (averageDrift > 0.0) ? 1 : -1;
85 averageDriftNumSamples = 0;
90 epc->setSpeedCompensation (speedComp);
107 auto m =
new TCMessage (10);
112 void handleMMCGotoMessage (
int h,
int m,
int s,
int f)
114 auto mess =
new TCMessage (11);
126 int hours = 0, minutes = 0, seconds = 0, frames = 0;
128 double lastTime = 0, correctedTime = 0, averageDrift = 0;
129 int averageDriftNumSamples = 0;
130 bool jumpPending =
false;
132 void timerCallback()
override
136 if (transport.isPlaying())
138 transport.stop (
false,
false,
false);
139 transport.setPosition (TimePosition::fromSeconds (correctedTime));
141 averageDriftNumSamples = 0;
147 TCMessage (
int tp) : type (tp) {}
156 if (
auto m =
dynamic_cast<const TCMessage*
> (&message))
162 if (transport.isPlaying())
164 transport.stop (
false,
false,
false);
165 transport.setPosition (TimePosition::fromSeconds (correctedTime));
167 averageDriftNumSamples = 0;
170 else if (m->type == 2)
172 if (! transport.isPlaying())
174 transport.play (
false);
177 averageDriftNumSamples = 0;
180 else if (m->type == 3)
182 transport.setPosition (TimePosition::fromSeconds (correctedTime));
185 averageDriftNumSamples = 0;
188 else if (m->type == 10)
190 handleMMC (m->command);
192 else if (m->type == 11)
194 handleMMCGoto (m->data[0], m->data[1], m->data[2], m->data[3]);
203 case juce::MidiMessage::mmc_stop: transport.stop (
false,
false,
false);
break;
204 case juce::MidiMessage::mmc_play: transport.play (
false);
break;
205 case juce::MidiMessage::mmc_deferredplay: transport.play (
false);
break;
206 case juce::MidiMessage::mmc_fastforward: transport.nudgeRight();
break;
207 case juce::MidiMessage::mmc_rewind: transport.nudgeLeft();
break;
208 case juce::MidiMessage::mmc_recordStart: transport.record (
false);
break;
209 case juce::MidiMessage::mmc_recordStop: transport.stop (
false,
false,
false);
break;
211 case juce::MidiMessage::mmc_pause:
212 if (transport.isPlaying())
213 transport.stop (
false,
false,
false);
215 transport.play (
false);
221 void handleMMCGoto (
int hours_,
int minutes_,
int seconds_,
int frames_)
224 transport.setPosition (TimePosition::fromSeconds (hours_ * 3600 + minutes_ * 60 + seconds_ + (1.0 /
double (fps) * frames_)));
227 double getFPS()
const noexcept
229 if (midiTCType == juce::MidiMessage::fps25)
return 25.0;
230 if (midiTCType == juce::MidiMessage::fps24)
return 24.0;
234 double getTime()
const noexcept
236 double timeWithoutHours = minutes * 60.0
241 if (pmi->isIgnoringHours())
242 return timeWithoutHours;
244 return hours * 3600.0 + timeWithoutHours;
261 int hours, minutes, seconds, frames;
264 timecodeReader->handleMMCGotoMessage (hours, minutes, seconds, frames);
271 return timecodeReader->processMessage (message);
276 if (getPhysicalMidiInput().inputDevice !=
nullptr)
294 : MidiInputDevice (e,
TRANS(
"MIDI Input"), info.name,
"midiin_" +
juce::String::toHexString (info.identifier.hashCode())),
295 deviceInfo (
std::move (info))
303PhysicalMidiInputDevice::~PhysicalMidiInputDevice()
310 if (! isTrackDevice() && retrospectiveBuffer ==
nullptr)
319 std::memset (keyDownVelocities, 0,
sizeof (keyDownVelocities));
321 if (inputDevice ==
nullptr)
326 if (inputDevice !=
nullptr)
328 TRACKTION_LOG (
"opening MIDI in device: " + getDeviceID() +
" (" + getName() +
")");
329 inputDevice->start();
333 if (inputDevice !=
nullptr)
336 return TRANS(
"Couldn't open the MIDI port");
339void PhysicalMidiInputDevice::closeDevice()
344 if (inputDevice !=
nullptr)
347 TRACKTION_LOG (
"Closing MIDI in device: " + getName());
348 inputDevice =
nullptr;
356 TRACKTION_LOG (
"MIDI External controller assigned: " + getName());
357 externalController = ec;
362 if (externalController == ec)
363 externalController =
nullptr;
366bool PhysicalMidiInputDevice::isAvailableToEdit()
const
368 return isEnabled() && (externalController ==
nullptr
369 || ! externalController->eatsAllMessages());
372bool PhysicalMidiInputDevice::tryToSendTimecode (
const juce::MidiMessage& message)
378 for (
auto p : instances)
379 p->handleMMCMessage (message);
384 if (isReadingMidiTimecode)
388 for (
auto p : instances)
389 if (p->handleTimecodeMessage (message))
396void PhysicalMidiInputDevice::handleIncomingMidiMessage (
const juce::MidiMessage& m)
398 if (minimumLengthMs > 0)
402 auto idx = (m.getChannel() - 1) + m.getNoteNumber();
406 noteDispatcher->clear (m.getChannel(), m.getNoteNumber());
408 else if (m.isNoteOff())
410 auto idx = (m.getChannel() - 1) + m.getNoteNumber();
413 if (now - lastNoteOns[
size_t (idx)] < minimumLengthMs)
415 auto delta = minimumLengthMs - (now - lastNoteOns[
size_t (idx)]);
418 noteDispatcher->enqueue (now + delta, m);
427 if (activeNotes.isNoteActive (m.getChannel(), m.getNoteNumber()))
431 handleIncomingMidiMessageInt (m);
436 activeNotes.startNote (m.getChannel(), m.getNoteNumber());
437 handleIncomingMidiMessageInt (m);
440 else if (m.isNoteOff())
443 if (activeNotes.isNoteActive (m.getChannel(), m.getNoteNumber()))
445 activeNotes.clearNote (m.getChannel(), m.getNoteNumber());
446 handleIncomingMidiMessageInt (m);
451 handleIncomingMidiMessageInt (m);
455void PhysicalMidiInputDevice::handleIncomingMidiMessageInt (
const juce::MidiMessage& m)
459 listeners.call ([m] (Listener& l) { l.handleIncomingMidiMessage (m); });
462 if (externalController !=
nullptr && externalController->wantsMessage (*
this, m))
464 externalController->acceptMidiMessage (*
this, m);
472 if (! tryToSendTimecode (message))
474 if (isTakingControllerMessages)
475 controllerParser->processMessage (message);
477 sendMessageToInstances (message);
485void PhysicalMidiInputDevice::loadProps()
487 isTakingControllerMessages =
true;
489 auto n = engine.
getPropertyStorage().getXmlPropertyItem (SettingID::midiin, getName());
492 isTakingControllerMessages = n->getBoolAttribute (
"controllerMessages", isTakingControllerMessages);
494 MidiInputDevice::loadMidiProps (n.get());
497void PhysicalMidiInputDevice::saveProps()
503 n.setAttribute (
"controllerMessages", isTakingControllerMessages);
505 MidiInputDevice::saveMidiProps (n);
510void PhysicalMidiInputDevice::setReadingMidiTimecode (
bool b)
512 isReadingMidiTimecode = b;
515void PhysicalMidiInputDevice::setIgnoresHours (
bool b)
520void PhysicalMidiInputDevice::setAcceptingMMC (
bool b)
525void PhysicalMidiInputDevice::setReadingControllerMessages (
bool b)
527 isTakingControllerMessages = b;
void postMessage(Message *message) const
bool isMidiMachineControlMessage() const noexcept
MidiMachineControlCommand
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
bool isMidiMachineControlGoto(int &hours, int &minutes, int &seconds, int &frames) const noexcept
MidiMachineControlCommand getMidiMachineControlCommand() const noexcept
static double getMillisecondCounterHiRes() noexcept
void stopTimer() noexcept
void startTimer(int intervalInMilliseconds) noexcept
void postPosition(TimePosition positionToJumpTo, std::optional< TimePosition > whenToJump={})
Posts a transport position change.
TimeDuration getTimecodeOffset() const noexcept
Returns the offset to apply to MIDI timecode.
TimecodeDisplayFormat getTimecodeFormat() const
Returns the current TimecodeDisplayFormat.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
Acts as a holder for a ControlSurface object.
Controls the transport of an Edit's playback.
EditPlaybackContext * getCurrentPlaybackContext() const
Returns the active EditPlaybackContext if this Edit is attached to the DeviceManager for playback.
bool isPlaying() const
Returns true if the transport is playing.
T emplace_back(T... args)
#define TRANS(stringLiteral)
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.