11namespace tracktion {
inline namespace engine
14static bool shouldSendAllControllersOffMessages =
true;
25 void update (
Edit* edit)
28 midiTCType = juce::MidiMessage::fps24;
31 timeBetweenMessages = 0.1;
38 if (framesPerSecond == 25)
39 midiTCType = juce::MidiMessage::fps25;
40 else if (framesPerSecond == 30)
41 midiTCType = juce::MidiMessage::fps30drop;
43 timeBetweenMessages = 1.0 / (framesPerSecond * 4);
47 void addMessages (
bool isPlaying,
bool isScrubbing,
50 double blockStart,
double blockEnd)
53 if (isScrubbing || ! isPlaying)
55 auto len = blockEnd - blockStart;
58 blockEnd = blockStart + len;
61 if (playing != isPlaying
62 || scrubbing != isScrubbing)
65 scrubbing = isScrubbing;
67 updateParts (blockStart);
69 0, MidiMessageArray::notMPE);
72 else if (lastTime != blockStart)
74 lastTime = blockStart;
80 if (now > lastMessageSent + 50)
82 updateParts (blockStart);
84 0, MidiMessageArray::notMPE);
85 lastMessageSent = now;
90 auto sequenceNum = (
int)
std::floor (blockStart / timeBetweenMessages);
91 auto t = sequenceNum * timeBetweenMessages;
100 auto sequenceIndex = (sequenceNum + 65536) & 7;
102 switch (sequenceIndex)
104 case 0: value = frames & 0x0f;
break;
105 case 1: value = (frames >> 4);
break;
106 case 2: value = seconds & 0x0f;
break;
107 case 3: value = (seconds >> 4);
break;
108 case 4: value = minutes & 0x0f;
break;
109 case 5: value = (minutes >> 4);
break;
110 case 6: value = hours & 0x0f;
break;
113 value = ((hours >> 4) & 1);
115 if (framesPerSecond == 25)
119 else if (framesPerSecond == 30)
131 t - blockStart, MidiMessageArray::notMPE);
135 t += timeBetweenMessages;
142 bool playing =
false, scrubbing =
false;
143 double lastTime = 0, offset = 0, timeBetweenMessages = 0;
145 int framesPerSecond = 0;
146 bool isDropFrame =
false;
148 int hours = 0, minutes = 0, seconds = 0, frames = 0;
150 void updateParts (
double t)
152 const double nudge = 0.05 / 96000.0;
154 t =
std::max (0.0, t + offset) + nudge;
156 frames = ((
int) (t * framesPerSecond)) % framesPerSecond;
157 hours = (
int) (t * (1.0 / 3600.0));
158 minutes = (((
int) t) / 60) % 60;
159 seconds = (((
int) t) % 60);
174 void reset (
Edit* edit)
179 needsToSendPosition =
true;
180 lastBlockStart = -100000.0s;
191 const bool isPlaying = playHeadIsPlaying && position !=
nullptr;
193 if (isPlaying != wasPlaying)
195 wasPlaying = isPlaying;
196 needsToSendPosition =
true;
206 position->set (blockStartTime);
207 auto blockStartPPQ = position->getPPQTime();
209 position->set (blockStartTime + blockLength);
210 auto endPPQ = position->getPPQTime();
212 const bool jumped = std::abs (lastBlockEndPPQ - endPPQ) > 0.4;
213 lastBlockEndPPQ = endPPQ;
215 bool looped = blockStartTime < lastBlockStart;
216 lastBlockStart = blockStartTime;
218 needsToSendPosition = needsToSendPosition || jumped || looped;
220 const double startNum = blockStartPPQ * 24.0;
221 const double endNum = endPPQ * 24.0;
222 const double timePerNum = blockLength.
inSeconds() / (endNum - startNum);
227 if (needsToSendPosition)
233 if (num == 0 || (tc !=
nullptr && tc->
isRecording()))
238 needsToSendPosition =
false;
242 if (! needsToSendPosition)
244 (num - startNum) * timePerNum,
245 MidiMessageArray::notMPE);
253 bool wasPlaying =
false;
254 bool needsToSendPosition =
false;
258 double lastBlockEndPPQ = 0;
266 deviceInfo (
std::move (info))
272 programNameSet = getMidiProgramManager().getDefaultCustomName();
275 shouldSendAllControllersOffMessages = getControllerOffMessagesSent (engine);
278MidiOutputDevice::~MidiOutputDevice()
280 notifyListenersOfDeletion();
284void MidiOutputDevice::setEnabled (
bool b)
294juce::String MidiOutputDevice::prepareToPlay (Edit* edit, TimePosition)
296 if (outputDevice ==
nullptr)
297 return TRANS(
"Couldn't open the MIDI port");
301 timecodeGenerator->update (edit);
302 midiClockGenerator->reset (edit);
308bool MidiOutputDevice::start()
310 if (outputDevice !=
nullptr)
320void MidiOutputDevice::stop()
322 if (outputDevice !=
nullptr)
327 sendNoteOffMessages();
332void MidiOutputDevice::setControllerOffMessagesSent (
Engine& e,
bool b)
334 shouldSendAllControllersOffMessages = b;
335 e.getPropertyStorage().setProperty (SettingID::sendControllerOffMessages, b);
338bool MidiOutputDevice::getControllerOffMessagesSent (
Engine& e)
340 return e.getPropertyStorage().getProperty (SettingID::sendControllerOffMessages,
true);
343juce::String MidiOutputDevice::getNameForMidiNoteNumber (
int note,
int midiChannel,
bool useSharp)
const
346 :
juce::MidiMessage::getMidiNoteName (note, useSharp, true,
347 engine.getEngineBehaviour().getMiddleCOctave());
350void MidiOutputDevice::updateMidiTC (Edit* edit)
352 timecodeGenerator->update (edit);
355void MidiOutputDevice::setSendingMMC (
bool b)
362 TRACKTION_LOG (
"MIDI External controller assigned: " + getName());
363 externalController = ec;
368 if (externalController == ec)
369 externalController =
nullptr;
372void MidiOutputDevice::loadProps()
374 preDelayMillisecs = 0;
375 sendTimecode =
false;
376 sendMidiClock =
false;
377 sendControllerMidiClock =
false;
379 if (
auto n = engine.
getPropertyStorage().getXmlPropertyItem (SettingID::midiout, getName()))
381 enabled = n->getBoolAttribute (
"enabled", enabled);
382 preDelayMillisecs = n->getIntAttribute (
"preDelay", preDelayMillisecs);
383 sendTimecode = n->getBoolAttribute (
"sendTimecode", sendTimecode);
384 sendMidiClock = n->getBoolAttribute (
"sendMidiClock", sendMidiClock);
385 timecodeGenerator->update (
nullptr);
387 if (getName() ==
"Microsoft GS Wavetable SW Synth")
388 programNameSet = n->getStringAttribute (
"programNames",
TRANS(
"General MIDI"));
390 programNameSet = n->getStringAttribute (
"programNames", getMidiProgramManager().getDefaultCustomName());
394void MidiOutputDevice::saveProps()
398 n.setAttribute (
"enabled", enabled);
399 n.setAttribute (
"preDelay", preDelayMillisecs);
400 n.setAttribute (
"sendTimecode", sendTimecode);
401 n.setAttribute (
"sendMidiClock", sendMidiClock);
402 n.setAttribute (
"programNames", programNameSet);
411 if (outputDevice ==
nullptr)
414 TRACKTION_LOG (
"opening MIDI out device: " + getDeviceID() +
" (" + getName() +
")");
421 outputDevice.
reset();
429 if (outputDevice ==
nullptr)
431 TRACKTION_LOG_ERROR (
"Failed to open MIDI output " + getName());
432 return TRANS(
"Couldn't open device");
437 outputDevice.
reset();
445void MidiOutputDevice::closeDevice()
449 if (outputDevice !=
nullptr)
451 TRACKTION_LOG (
"closing MIDI output: " + getName());
452 outputDevice =
nullptr;
456void MidiOutputDevice::sendNoteOffMessages()
458 if (isConnectedToExternalController())
461 if (outputDevice !=
nullptr)
465 for (
int channel = channelsUsed.
getHighestBit() + 1; --channel > 0;)
467 if (channelsUsed [channel])
469 for (
int note = midiNotesOn.
getHighestBit() + 1; --note >= 0;)
470 if (midiNotesOn [note])
481 if (shouldSendAllControllersOffMessages)
486 channelsUsed.
clear();
493 if (outputDevice !=
nullptr)
494 outputDevice->sendMessageNow (message);
501 sendMessageNow (message);
521TimeDuration MidiOutputDevice::getDeviceDelay() const noexcept
523 return TimeDuration::fromSeconds ((preDelayMillisecs + audioAdjustmentDelay) * 0.001);
526void MidiOutputDevice::setPreDelayMs (
int ms)
528 if (preDelayMillisecs != ms)
530 preDelayMillisecs = ms;
537void MidiOutputDevice::setSendingClock (
bool b)
539 if (sendMidiClock != b)
547void MidiOutputDevice::flipSendingTimecode()
549 sendTimecode = ! sendTimecode;
556 return getMidiProgramManager().getMidiProgramSetNames();
559int MidiOutputDevice::getCurrentSetIndex()
const
561 return getMidiProgramManager().getSetIndex (programNameSet);
564void MidiOutputDevice::setCurrentProgramSet (
const juce::String& newSet)
566 if (programNameSet != newSet)
568 programNameSet = newSet;
570 SelectionManager::refreshAllPropertyPanels();
574juce::String MidiOutputDevice::getProgramName (
int programNumber,
int bank)
576 return getMidiProgramManager().getProgramName (getCurrentSetIndex(), bank, programNumber);
579bool MidiOutputDevice::canEditProgramSet (
int index)
const
581 return getMidiProgramManager().canEditProgramSet (index);
584bool MidiOutputDevice::canDeleteProgramSet (
int index)
const
586 return getMidiProgramManager().canDeleteProgramSet (index);
591 return getMidiProgramManager().getBankName (getCurrentSetIndex(), bank);
594int MidiOutputDevice::getBankID (
int bank)
596 return getMidiProgramManager().getBankID (getCurrentSetIndex(), bank);
599bool MidiOutputDevice::areMidiPatchesZeroBased()
601 return getMidiProgramManager().isZeroBased (getCurrentSetIndex());
604MidiOutputDeviceInstance* MidiOutputDevice::createInstance (EditPlaybackContext& c)
606 return new MidiOutputDeviceInstance (*
this, c);
610MidiOutputDeviceInstance::MidiOutputDeviceInstance (MidiOutputDevice& d, EditPlaybackContext& e)
611 : OutputDeviceInstance (d, e)
617MidiOutputDeviceInstance::~MidiOutputDeviceInstance()
621juce::String MidiOutputDeviceInstance::prepareToPlay (TimePosition,
bool shouldSendMidiTC)
623 if (getMidiOutput().outputDevice ==
nullptr)
624 return TRANS(
"Couldn't open the MIDI port");
628 shouldSendMidiTimecode = shouldSendMidiTC;
629 timecodeGenerator->update (&edit);
630 midiClockGenerator->reset (&edit);
632 sampleRate = edit.engine.getDeviceManager().getSampleRate();
637bool MidiOutputDeviceInstance::start()
639 if (getMidiOutput().outputDevice !=
nullptr)
641 audioAdjustmentDelay =
juce::roundToInt (2.0 * edit.engine.getDeviceManager().getBlockSizeMs());
649void MidiOutputDeviceInstance::stop()
654 getMidiOutput().sendNoteOffMessages();
658void MidiOutputDeviceInstance::mergeInMidiMessages (
const MidiMessageArray& source, TimePosition editTime)
660 midiMessages.mergeFromWithOffset (source, (editTime + getMidiOutput().getDeviceDelay()).inSeconds());
661 midiMessages.sortByTimestamp();
664void MidiOutputDeviceInstance::addMidiClockMessagesToCurrentBlock (
bool isPlaying,
bool isDragging, TimeRange editTimeRange)
666 auto& midiOut = getMidiOutput();
668 if (shouldSendMidiTimecode)
670 if (midiOut.sendTimecode)
671 timecodeGenerator->addMessages (isPlaying, isDragging,
672 &edit.getTransport(), midiMessages,
673 editTimeRange.getStart().inSeconds(),
674 editTimeRange.getEnd().inSeconds());
676 if (midiOut.sendMidiClock || midiOut.sendControllerMidiClock)
677 midiClockGenerator->addMessages (isPlaying,
678 &edit.getTransport(), midiMessages,
679 editTimeRange.getStart(),
680 editTimeRange.getLength());
BigInteger & clear() noexcept
int getHighestBit() const noexcept
BigInteger & clearBit(int bitNumber) noexcept
BigInteger & setBit(int bitNumber)
static MidiMessage midiStart() noexcept
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
int getChannel() const noexcept
bool isController() const noexcept
int getControllerNumber() const noexcept
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
static MidiMessage quarterFrame(int sequenceNumber, int value) noexcept
static MidiMessage midiStop() noexcept
int getNoteNumber() const noexcept
static MidiMessage allNotesOff(int channel) noexcept
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
static MidiMessage midiClock() noexcept
static MidiMessage fullFrame(int hours, int minutes, int seconds, int frames, SmpteTimecodeType timecodeType)
bool isNoteOnOrOff() const noexcept
static MidiMessage midiContinue() noexcept
int getControllerValue() const noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
bool isMetaEvent() const noexcept
static const char * getRhythmInstrumentName(int midiNoteNumber)
static MidiMessage songPositionPointer(int positionInMidiBeats) noexcept
static MidiMessage allControllersOff(int channel) noexcept
static std::unique_ptr< MidiOutput > openDevice(const String &deviceIdentifier)
static std::unique_ptr< MidiOutput > createNewDevice(const String &deviceName)
bool isNotEmpty() const noexcept
static uint32 getMillisecondCounter() noexcept
The Tracktion Edit class!
TimeDuration getTimecodeOffset() const noexcept
Returns the offset to apply to MIDI timecode.
TimecodeDisplayFormat getTimecodeFormat() const
Returns the current TimecodeDisplayFormat.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
The Engine is the central class for all tracktion sessions.
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.
void setExternalController(ExternalController *)
sets the external controller messages are coming from
Base class for audio or midi output devices, to which a track's output can be sent.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
Controls the transport of an Edit's playback.
TimePosition getPosition() const
Returns the current transport position.
bool isRecording() const
Returns true if recording is in progress.
static std::vector< std::unique_ptr< ScopedContextAllocator > > restartAllTransports(Engine &, bool clearDevices)
Restarts all TransportControl[s] in the Edit.
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
int roundToInt(const FloatType value) noexcept
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
Represents a duration in real-life time.
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
Represents a position in real-life time.
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.