From 8fbd99c424d4c70c763353111209a4bee88cdb6e Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 13 Jun 2022 19:37:49 +0100 Subject: [PATCH] AudioPlayHead: Improve granularity of position info --- BREAKING-CHANGES.txt | 30 ++ examples/Plugins/ARAPluginDemo.h | 17 +- examples/Plugins/AudioPluginDemo.h | 48 ++- .../Source/Plugins/ARAPlugin.h | 9 +- .../audio_play_head/juce_AudioPlayHead.h | 310 +++++++++++++++++- .../native/juce_ios_Audio.cpp | 218 ++++++------ .../format/juce_ARAAudioReaders.cpp | 10 +- .../format/juce_ARAAudioReaders.h | 2 +- .../AAX/juce_AAX_Wrapper.cpp | 116 ++++--- .../AU/juce_AU_Wrapper.mm | 159 +++++---- .../AU/juce_AUv3_Wrapper.mm | 58 ++-- .../LV2/juce_LV2_Client.cpp | 46 ++- .../VST/juce_VST_Wrapper.cpp | 81 ++--- .../VST3/juce_VST3_Wrapper.cpp | 65 ++-- .../juce_AudioUnitPluginFormat.mm | 21 +- .../format_types/juce_LV2Common.h | 10 - .../format_types/juce_LV2PluginFormat.cpp | 56 ++-- .../format_types/juce_VST3PluginFormat.cpp | 98 +++--- .../format_types/juce_VSTPluginFormat.cpp | 85 +++-- .../processors/juce_AudioProcessor.h | 11 +- .../processors/juce_AudioProcessorGraph.cpp | 16 +- .../ARA/juce_ARAPlugInInstanceRoles.cpp | 2 +- .../ARA/juce_ARAPlugInInstanceRoles.h | 8 +- .../ARA/juce_AudioProcessor_ARAExtensions.cpp | 12 +- .../ARA/juce_AudioProcessor_ARAExtensions.h | 2 +- .../players/juce_AudioProcessorPlayer.cpp | 4 +- .../juce_core/native/juce_mac_ObjCHelpers.h | 2 +- 27 files changed, 924 insertions(+), 572 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index d1a426ed71..5a1fdc39e2 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,36 @@ JUCE breaking changes develop ======= +Change +------ +AudioPlayHead::getCurrentPosition() has been deprecated and replaced with +AudioPlayHead::getPosition(). + +Possible Issues +--------------- +Hosts that implemented custom playhead types may no longer compile. Plugins +that used host-provided timing information may trigger deprecation warnings +when building. + +Workaround +---------- +Classes that derive from AudioPlayHead must now override getPosition() instead +of getCurrentPosition(). Code that used to use the playhead's +CurrentPositionInfo must switch to using the new PositionInfo type. + +Rationale +--------- +Not all hosts and plugin formats are capable of providing the full complement +of timing information contained in the old CurrentPositionInfo class. +Previously, in the case that some information could not be provided, fallback +values would be used instead, but it was not possible for clients to distinguish +between "real" values set explicitly by the host, and "fallback" values set by +a plugin wrapper. The new PositionInfo type keeps track of which members have +been explicitly set, so clients can implement their own fallback behaviour. +The new PositionInfo type also includes a new "barCount" member, which is +currently only used by the LV2 host and client. + + Change ------ The optional JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS preprocessor diff --git a/examples/Plugins/ARAPluginDemo.h b/examples/Plugins/ARAPluginDemo.h index d07db90347..abca95660d 100644 --- a/examples/Plugins/ARAPluginDemo.h +++ b/examples/Plugins/ARAPluginDemo.h @@ -293,14 +293,14 @@ public: bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + const AudioPlayHead::PositionInfo& positionInfo) noexcept override { const auto numSamples = buffer.getNumSamples(); jassert (numSamples <= maximumSamplesPerBlock); jassert (numChannels == buffer.getNumChannels()); jassert (realtime == AudioProcessor::Realtime::no || useBufferedAudioSourceReader); - const auto timeInSamples = positionInfo.timeInSamples; - const auto isPlaying = positionInfo.isPlaying; + const auto timeInSamples = positionInfo.getTimeInSamples().orFallback (0); + const auto isPlaying = positionInfo.getIsPlaying(); bool success = true; bool didRenderAnyRegion = false; @@ -482,7 +482,7 @@ public: bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + const AudioPlayHead::PositionInfo& positionInfo) noexcept override { ignoreUnused (realtime); @@ -491,7 +491,7 @@ public: if (! locked) return true; - if (positionInfo.isPlaying) + if (positionInfo.getIsPlaying()) return true; if (const auto previewedRegion = previewState->previewedRegion.load()) @@ -1075,12 +1075,11 @@ private: void doResize() { auto* aph = getAudioPlayhead(); - AudioPlayHead::CurrentPositionInfo positionInfo; - aph->getCurrentPosition (positionInfo); + const auto info = aph->getPosition(); - if (positionInfo.isPlaying) + if (info.hasValue() && info->getIsPlaying()) { - const auto markerX = positionInfo.timeInSeconds * pixelPerSecond; + const auto markerX = info->getTimeInSeconds().orFallback (0) * pixelPerSecond; const auto playheadLine = getLocalBounds().withTrimmedLeft ((int) (markerX - markerWidth / 2.0) - horizontalOffset) .removeFromLeft ((int) markerWidth); playheadMarker.setVisible (true); diff --git a/examples/Plugins/AudioPluginDemo.h b/examples/Plugins/AudioPluginDemo.h index d75466b608..da12a3da59 100644 --- a/examples/Plugins/AudioPluginDemo.h +++ b/examples/Plugins/AudioPluginDemo.h @@ -321,12 +321,10 @@ public: class SpinLockedPosInfo { public: - SpinLockedPosInfo() { info.resetToDefault(); } - // Wait-free, but setting new info may fail if the main thread is currently // calling `get`. This is unlikely to matter in practice because // we'll be calling `set` much more frequently than `get`. - void set (const AudioPlayHead::CurrentPositionInfo& newInfo) + void set (const AudioPlayHead::PositionInfo& newInfo) { const juce::SpinLock::ScopedTryLockType lock (mutex); @@ -334,7 +332,7 @@ public: info = newInfo; } - AudioPlayHead::CurrentPositionInfo get() const noexcept + AudioPlayHead::PositionInfo get() const noexcept { const juce::SpinLock::ScopedLockType lock (mutex); return info; @@ -342,7 +340,7 @@ public: private: juce::SpinLock mutex; - AudioPlayHead::CurrentPositionInfo info; + AudioPlayHead::PositionInfo info; }; //============================================================================== @@ -510,13 +508,13 @@ private: } // quick-and-dirty function to format a bars/beats string - static String quarterNotePositionToBarsBeatsString (double quarterNotes, int numerator, int denominator) + static String quarterNotePositionToBarsBeatsString (double quarterNotes, AudioPlayHead::TimeSignature sig) { - if (numerator == 0 || denominator == 0) + if (sig.numerator == 0 || sig.denominator == 0) return "1|1|000"; - auto quarterNotesPerBar = (numerator * 4 / denominator); - auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator; + auto quarterNotesPerBar = (sig.numerator * 4 / sig.denominator); + auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * sig.numerator; auto bar = ((int) quarterNotes) / quarterNotesPerBar + 1; auto beat = ((int) beats) + 1; @@ -526,21 +524,21 @@ private: } // Updates the text in our position label. - void updateTimecodeDisplay (AudioPlayHead::CurrentPositionInfo pos) + void updateTimecodeDisplay (const AudioPlayHead::PositionInfo& pos) { MemoryOutputStream displayText; - displayText << "[" << SystemStats::getJUCEVersion() << "] " - << String (pos.bpm, 2) << " bpm, " - << pos.timeSigNumerator << '/' << pos.timeSigDenominator - << " - " << timeToTimecodeString (pos.timeInSeconds) - << " - " << quarterNotePositionToBarsBeatsString (pos.ppqPosition, - pos.timeSigNumerator, - pos.timeSigDenominator); + const auto sig = pos.getTimeSignature().orFallback (AudioPlayHead::TimeSignature{}); - if (pos.isRecording) + displayText << "[" << SystemStats::getJUCEVersion() << "] " + << String (pos.getBpm().orFallback (120.0), 2) << " bpm, " + << sig.numerator << '/' << sig.denominator + << " - " << timeToTimecodeString (pos.getTimeInSeconds().orFallback (0.0)) + << " - " << quarterNotePositionToBarsBeatsString (pos.getPpqPosition().orFallback (0.0), sig); + + if (pos.getIsRecording()) displayText << " (recording)"; - else if (pos.isPlaying) + else if (pos.getIsPlaying()) displayText << " (playing)"; timecodeDisplayLabel.setText (displayText.toString(), dontSendNotification); @@ -647,17 +645,11 @@ private: const auto newInfo = [&] { if (auto* ph = getPlayHead()) - { - AudioPlayHead::CurrentPositionInfo result; - - if (ph->getCurrentPosition (result)) - return result; - } + if (auto result = ph->getPosition()) + return *result; // If the host fails to provide the current time, we'll just use default values - AudioPlayHead::CurrentPositionInfo result; - result.resetToDefault(); - return result; + return AudioPlayHead::PositionInfo{}; }(); lastPosInfo.set (newInfo); diff --git a/extras/AudioPluginHost/Source/Plugins/ARAPlugin.h b/extras/AudioPluginHost/Source/Plugins/ARAPlugin.h index 704b9e5ac1..be6353b9b5 100644 --- a/extras/AudioPluginHost/Source/Plugins/ARAPlugin.h +++ b/extras/AudioPluginHost/Source/Plugins/ARAPlugin.h @@ -486,11 +486,12 @@ public: struct SimplePlayHead : public juce::AudioPlayHead { - bool getCurrentPosition (CurrentPositionInfo& result) override + Optional getPosition() const override { - result.timeInSamples = timeInSamples.load(); - result.isPlaying = isPlaying.load(); - return true; + PositionInfo result; + result.setTimeInSamples (timeInSamples.load()); + result.setIsPlaying (isPlaying.load()); + return result; } std::atomic timeInSamples { 0 }; diff --git a/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h b/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h index f3a265e1a6..af4966d5c6 100644 --- a/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h +++ b/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h @@ -152,8 +152,60 @@ public: bool drop = false, pulldown = false; }; + /** Describes a musical time signature. + + @see PositionInfo::getTimeSignature() PositionInfo::setTimeSignature() + */ + struct JUCE_API TimeSignature + { + /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ + int numerator = 4; + + /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ + int denominator = 4; + + bool operator== (const TimeSignature& other) const + { + const auto tie = [] (auto& x) { return std::tie (x.numerator, x.denominator); }; + return tie (*this) == tie (other); + } + + bool operator!= (const TimeSignature& other) const + { + return ! operator== (other); + } + }; + + /** Holds the begin and end points of a looped region. + + @see PositionInfo::getIsLooping() PositionInfo::setIsLooping() PositionInfo::getLoopPoints() PositionInfo::setLoopPoints() + */ + struct JUCE_API LoopPoints + { + /** The current cycle start position in units of quarter-notes. */ + double ppqStart = 0; + + /** The current cycle end position in units of quarter-notes. */ + double ppqEnd = 0; + + bool operator== (const LoopPoints& other) const + { + const auto tie = [] (auto& x) { return std::tie (x.ppqStart, x.ppqEnd); }; + return tie (*this) == tie (other); + } + + bool operator!= (const LoopPoints& other) const + { + return ! operator== (other); + } + }; + //============================================================================== - /** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. + /** This type is deprecated; prefer PositionInfo instead. + + Some position info may be unavailable, depending on the host or plugin format. + Unfortunately, CurrentPositionInfo doesn't have any way of differentiating between + default values and values that have been set explicitly. */ struct JUCE_API CurrentPositionInfo { @@ -162,6 +214,7 @@ public: /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ int timeSigNumerator = 4; + /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ int timeSigDenominator = 4; @@ -248,7 +301,199 @@ public: }; //============================================================================== - /** Fills-in the given structure with details about the transport's + /** + Describes the time at the start of the current audio callback. + + Not all hosts and plugin formats can provide all of the possible time + information, so most of the getter functions in this class return + an Optional that will only be engaged if the host provides the corresponding + information. As a plugin developer, you should code defensively so that + the plugin behaves sensibly even when the host fails to provide timing + information. + + A default-constructed instance of this class will return nullopt from + all functions that return an Optional. + */ + class PositionInfo + { + public: + /** Returns the number of samples that have elapsed. */ + Optional getTimeInSamples() const { return getOptional (flagTimeSamples, timeInSamples); } + + /** @see getTimeInSamples() */ + void setTimeInSamples (Optional timeInSamplesIn) { setOptional (flagTimeSamples, timeInSamples, timeInSamplesIn); } + + /** Returns the number of samples that have elapsed. */ + Optional getTimeInSeconds() const { return getOptional (flagTimeSeconds, timeInSeconds); } + + /** @see getTimeInSamples() */ + void setTimeInSeconds (Optional timeInSecondsIn) { setOptional (flagTimeSeconds, timeInSeconds, timeInSecondsIn); } + + /** Returns the bpm, if available. */ + Optional getBpm() const { return getOptional (flagTempo, tempoBpm); } + + /** @see getBpm() */ + void setBpm (Optional bpmIn) { setOptional (flagTempo, tempoBpm, bpmIn); } + + /** Returns the time signature, if available. */ + Optional getTimeSignature() const { return getOptional (flagTimeSignature, timeSignature); } + + /** @see getTimeSignature() */ + void setTimeSignature (Optional timeSignatureIn) { setOptional (flagTimeSignature, timeSignature, timeSignatureIn); } + + /** Returns host loop points, if available. */ + Optional getLoopPoints() const { return getOptional (flagLoopPoints, loopPoints); } + + /** @see getLoopPoints() */ + void setLoopPoints (Optional loopPointsIn) { setOptional (flagLoopPoints, loopPoints, loopPointsIn); } + + /** The number of bars since the beginning of the timeline. + + This value isn't available in all hosts or in all plugin formats. + */ + Optional getBarCount() const { return getOptional (flagBarCount, barCount); } + + /** @see getBarCount() */ + void setBarCount (Optional barCountIn) { setOptional (flagBarCount, barCount, barCountIn); } + + /** The position of the start of the last bar, in units of quarter-notes. + + This is the time from the start of the timeline to the start of the current + bar, in ppq units. + + Note - this value may be unavailable on some hosts, e.g. Pro-Tools. + */ + Optional getPpqPositionOfLastBarStart() const { return getOptional (flagLastBarStartPpq, lastBarStartPpq); } + + /** @see getPpqPositionOfLastBarStart() */ + void setPpqPositionOfLastBarStart (Optional positionIn) { setOptional (flagLastBarStartPpq, lastBarStartPpq, positionIn); } + + /** The video frame rate, if available. */ + Optional getFrameRate() const { return getOptional (flagFrameRate, frame); } + + /** @see getFrameRate() */ + void setFrameRate (Optional frameRateIn) { setOptional (flagFrameRate, frame, frameRateIn); } + + /** The current play position, in units of quarter-notes. */ + Optional getPpqPosition() const { return getOptional (flagPpqPosition, positionPpq); } + + /** @see getPpqPosition() */ + void setPpqPosition (Optional ppqPositionIn) { setOptional (flagPpqPosition, positionPpq, ppqPositionIn); } + + /** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ + Optional getEditOriginTime() const { return getOptional (flagOriginTime, originTime); } + + /** @see getEditOriginTime() */ + void setEditOriginTime (Optional editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } + + /** True if the transport is currently playing. */ + bool getIsPlaying() const { return getFlag (flagIsPlaying); } + + /** @see getIsPlaying() */ + void setIsPlaying (bool isPlayingIn) { setFlag (flagIsPlaying, isPlayingIn); } + + /** True if the transport is currently recording. + + (When isRecording is true, then isPlaying will also be true). + */ + bool getIsRecording() const { return getFlag (flagIsRecording); } + + /** @see getIsRecording() */ + void setIsRecording (bool isRecordingIn) { setFlag (flagIsRecording, isRecordingIn); } + + /** True if the transport is currently looping. */ + bool getIsLooping() const { return getFlag (flagIsLooping); } + + /** @see getIsLooping() */ + void setIsLooping (bool isLoopingIn) { setFlag (flagIsLooping, isLoopingIn); } + + bool operator== (const PositionInfo& other) const noexcept + { + const auto tie = [] (const PositionInfo& i) + { + return std::make_tuple (i.getTimeInSamples(), + i.getTimeInSeconds(), + i.getPpqPosition(), + i.getEditOriginTime(), + i.getPpqPositionOfLastBarStart(), + i.getFrameRate(), + i.getBarCount(), + i.getTimeSignature(), + i.getBpm(), + i.getLoopPoints(), + i.getIsPlaying(), + i.getIsRecording(), + i.getIsLooping()); + }; + + return tie (*this) == tie (other); + } + + bool operator!= (const PositionInfo& other) const noexcept + { + return ! operator== (other); + } + + private: + bool getFlag (int64_t flagToCheck) const + { + return (flagToCheck & flags) != 0; + } + + void setFlag (int64_t flagToCheck, bool value) + { + flags = (value ? flags | flagToCheck : flags & ~flagToCheck); + } + + template + Optional getOptional (int64_t flagToCheck, Value value) const + { + return getFlag (flagToCheck) ? makeOptional (std::move (value)) : nullopt; + } + + template + void setOptional (int64_t flagToCheck, Value& value, Optional opt) + { + if (opt.hasValue()) + value = *opt; + + setFlag (flagToCheck, opt.hasValue()); + } + + enum + { + flagTimeSignature = 1 << 0, + flagLoopPoints = 1 << 1, + flagFrameRate = 1 << 2, + flagTimeSeconds = 1 << 3, + flagLastBarStartPpq = 1 << 4, + flagPpqPosition = 1 << 5, + flagOriginTime = 1 << 6, + flagTempo = 1 << 7, + flagTimeSamples = 1 << 8, + flagBarCount = 1 << 9, + flagIsPlaying = 1 << 10, + flagIsRecording = 1 << 11, + flagIsLooping = 1 << 12 + }; + + TimeSignature timeSignature; + LoopPoints loopPoints; + FrameRate frame = FrameRateType::fps23976; + double timeInSeconds = 0.0; + double lastBarStartPpq = 0.0; + double positionPpq = 0.0; + double originTime = 0.0; + double tempoBpm = 0.0; + int64_t timeInSamples = 0; + int64_t barCount = 0; + int64_t flags = 0; + }; + + //============================================================================== + /** Deprecated, use getPosition() instead. + + Fills-in the given structure with details about the transport's position at the start of the current processing block. If this method returns false then the current play head position is not available and the given structure will be undefined. @@ -258,7 +503,66 @@ public: in which a time would make sense, and some hosts will almost certainly have multithreading issues if it's not called on the audio thread. */ - virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; + [[deprecated ("Use getPosition instead. Not all hosts are able to provide all time position information; getPosition differentiates clearly between set and unset fields.")]] + bool getCurrentPosition (CurrentPositionInfo& result) + { + if (const auto pos = getPosition()) + { + result.resetToDefault(); + + if (const auto sig = pos->getTimeSignature()) + { + result.timeSigNumerator = sig->numerator; + result.timeSigDenominator = sig->denominator; + } + + if (const auto loop = pos->getLoopPoints()) + { + result.ppqLoopStart = loop->ppqStart; + result.ppqLoopEnd = loop->ppqEnd; + } + + if (const auto frame = pos->getFrameRate()) + result.frameRate = *frame; + + if (const auto timeInSeconds = pos->getTimeInSeconds()) + result.timeInSeconds = *timeInSeconds; + + if (const auto lastBarStartPpq = pos->getPpqPositionOfLastBarStart()) + result.ppqPositionOfLastBarStart = *lastBarStartPpq; + + if (const auto ppqPosition = pos->getPpqPosition()) + result.ppqPosition = *ppqPosition; + + if (const auto originTime = pos->getEditOriginTime()) + result.editOriginTime = *originTime; + + if (const auto bpm = pos->getBpm()) + result.bpm = *bpm; + + if (const auto timeInSamples = pos->getTimeInSamples()) + result.timeInSamples = *timeInSamples; + + return true; + } + + return false; + } + + /** Fetches details about the transport's position at the start of the current + processing block. If this method returns nullopt then the current play head + position is not available. + + A non-null return value just indicates that the host was able to provide + *some* relevant timing information. Individual PositionInfo getters may + still return nullopt. + + You can ONLY call this from your processBlock() method! Calling it at other + times will produce undefined behaviour, as the host may not have any context + in which a time would make sense, and some hosts will almost certainly have + multithreading issues if it's not called on the audio thread. + */ + virtual Optional getPosition() const = 0; /** Returns true if this object can control the transport. */ virtual bool canControlTransport() { return false; } diff --git a/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/modules/juce_audio_devices/native/juce_ios_Audio.cpp index 2e162685e9..a3821a6e34 100644 --- a/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -252,8 +252,7 @@ private: }; //============================================================================== -struct iOSAudioIODevice::Pimpl : public AudioPlayHead, - public AsyncUpdater +struct iOSAudioIODevice::Pimpl : public AsyncUpdater { Pimpl (iOSAudioIODeviceType* ioDeviceType, iOSAudioIODevice& ioDevice) : deviceType (ioDeviceType), @@ -566,132 +565,140 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, } //============================================================================== - bool canControlTransport() override { return interAppAudioConnected; } - - void transportPlay (bool shouldSartPlaying) override + class PlayHead : public AudioPlayHead { - if (! canControlTransport()) - return; + public: + explicit PlayHead (Pimpl& implIn) : impl (implIn) {} - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); + bool canControlTransport() override { return canControlTransportImpl(); } - Boolean hostIsPlaying = NO; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - &hostIsPlaying, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); + void transportPlay (bool shouldSartPlaying) override + { + if (! canControlTransport()) + return; - ignoreUnused (err); - jassert (err == noErr); + HostCallbackInfo callbackInfo; + impl.fillHostCallbackInfo (callbackInfo); - if (hostIsPlaying != shouldSartPlaying) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause); - } + Boolean hostIsPlaying = NO; + OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, + &hostIsPlaying, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); - void transportRecord (bool shouldStartRecording) override - { - if (! canControlTransport()) - return; + ignoreUnused (err); + jassert (err == noErr); - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); + if (hostIsPlaying != shouldSartPlaying) + impl.handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause); + } - Boolean hostIsRecording = NO; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - nullptr, - &hostIsRecording, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - ignoreUnused (err); - jassert (err == noErr); + void transportRecord (bool shouldStartRecording) override + { + if (! canControlTransport()) + return; - if (hostIsRecording != shouldStartRecording) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord); - } + HostCallbackInfo callbackInfo; + impl.fillHostCallbackInfo (callbackInfo); - void transportRewind() override - { - if (canControlTransport()) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind); - } + Boolean hostIsRecording = NO; + OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, + nullptr, + &hostIsRecording, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + ignoreUnused (err); + jassert (err == noErr); - bool getCurrentPosition (CurrentPositionInfo& result) override - { - if (! canControlTransport()) - return false; + if (hostIsRecording != shouldStartRecording) + impl.handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord); + } - zerostruct (result); + void transportRewind() override + { + if (canControlTransport()) + impl.handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind); + } - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); + Optional getPosition() const override + { + if (! canControlTransportImpl()) + return {}; - if (callbackInfo.hostUserData == nullptr) - return false; + HostCallbackInfo callbackInfo; + impl.fillHostCallbackInfo (callbackInfo); - Boolean hostIsPlaying = NO; - Boolean hostIsRecording = NO; - Float64 hostCurrentSampleInTimeLine = 0; - Boolean hostIsCycling = NO; - Float64 hostCycleStartBeat = 0; - Float64 hostCycleEndBeat = 0; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - &hostIsPlaying, - &hostIsRecording, - nullptr, - &hostCurrentSampleInTimeLine, - &hostIsCycling, - &hostCycleStartBeat, - &hostCycleEndBeat); - if (err == kAUGraphErr_CannotDoInCurrentContext) - return false; + if (callbackInfo.hostUserData == nullptr) + return {}; - jassert (err == noErr); + Boolean hostIsPlaying = NO; + Boolean hostIsRecording = NO; + Float64 hostCurrentSampleInTimeLine = 0; + Boolean hostIsCycling = NO; + Float64 hostCycleStartBeat = 0; + Float64 hostCycleEndBeat = 0; + OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, + &hostIsPlaying, + &hostIsRecording, + nullptr, + &hostCurrentSampleInTimeLine, + &hostIsCycling, + &hostCycleStartBeat, + &hostCycleEndBeat); + if (err == kAUGraphErr_CannotDoInCurrentContext) + return {}; - result.timeInSamples = (int64) hostCurrentSampleInTimeLine; - result.isPlaying = hostIsPlaying; - result.isRecording = hostIsRecording; - result.isLooping = hostIsCycling; - result.ppqLoopStart = hostCycleStartBeat; - result.ppqLoopEnd = hostCycleEndBeat; + jassert (err == noErr); - result.timeInSeconds = result.timeInSamples / sampleRate; + PositionInfo result; - Float64 hostBeat = 0; - Float64 hostTempo = 0; - err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData, - &hostBeat, - &hostTempo); - jassert (err == noErr); + result.setTimeInSamples ((int64) hostCurrentSampleInTimeLine); + result.setIsPlaying (hostIsPlaying); + result.setIsRecording (hostIsRecording); + result.setIsLooping (hostIsCycling); + result.setLoopPoints (LoopPoints { hostCycleStartBeat, hostCycleEndBeat }); + result.setTimeInSeconds (*result.getTimeInSamples() / impl.sampleRate); - result.ppqPosition = hostBeat; - result.bpm = hostTempo; + Float64 hostBeat = 0; + Float64 hostTempo = 0; + err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData, + &hostBeat, + &hostTempo); + jassert (err == noErr); - Float32 hostTimeSigNumerator = 0; - UInt32 hostTimeSigDenominator = 0; - Float64 hostCurrentMeasureDownBeat = 0; - err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData, - nullptr, - &hostTimeSigNumerator, - &hostTimeSigDenominator, - &hostCurrentMeasureDownBeat); - jassert (err == noErr); + result.setPpqPosition (hostBeat); + result.setBpm (hostTempo); - result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat; - result.timeSigNumerator = (int) hostTimeSigNumerator; - result.timeSigDenominator = (int) hostTimeSigDenominator; + Float32 hostTimeSigNumerator = 0; + UInt32 hostTimeSigDenominator = 0; + Float64 hostCurrentMeasureDownBeat = 0; + err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData, + nullptr, + &hostTimeSigNumerator, + &hostTimeSigDenominator, + &hostCurrentMeasureDownBeat); + jassert (err == noErr); - result.frameRate = AudioPlayHead::fpsUnknown; + result.setPpqPositionOfLastBarStart (hostCurrentMeasureDownBeat); + result.setTimeSignature (TimeSignature { (int) hostTimeSigNumerator, (int) hostTimeSigDenominator }); - return true; - } + result.setFrameRate (AudioPlayHead::fpsUnknown); + + return result; + } + + private: + bool canControlTransportImpl() const { return impl.interAppAudioConnected; } + + Pimpl& impl; + }; //============================================================================== #if JUCE_MODULE_AVAILABLE_juce_graphics @@ -1379,6 +1386,7 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, Float64 lastSampleTime; unsigned int lastNumFrames; int xrun; + PlayHead playhead { *this }; JUCE_DECLARE_NON_COPYABLE (Pimpl) }; @@ -1429,7 +1437,7 @@ int iOSAudioIODevice::getOutputLatencyInSamples() { return rou int iOSAudioIODevice::getXRunCount() const noexcept { return pimpl->xrun; } void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; } -AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl.get(); } +AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return &pimpl->playhead; } bool iOSAudioIODevice::isInterAppAudioConnected() const { return pimpl->interAppAudioConnected; } #if JUCE_MODULE_AVAILABLE_juce_graphics diff --git a/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp b/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp index 0f6a423af3..e02ab8f743 100644 --- a/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp +++ b/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp @@ -172,8 +172,7 @@ ARAPlaybackRegionReader::ARAPlaybackRegionReader (double rate, int numChans, // We're only providing the minimal set of meaningful values, since the ARA renderer should only // look at the time position and the playing state, and read any related tempo or bar signature // information from the ARA model directly (MusicalContext). - positionInfo.resetToDefault(); - positionInfo.isPlaying = true; + positionInfo.setIsPlaying (true); sampleRate = rate; numChannels = (unsigned int) numChans; @@ -252,16 +251,17 @@ bool ARAPlaybackRegionReader::readSamples (int** destSamples, int numDestChannel { success = true; needClearSamples = false; - positionInfo.timeInSamples = startSampleInFile + startInSamples; + positionInfo.setTimeInSamples (startSampleInFile + startInSamples); + while (numSamples > 0) { const int numSliceSamples = jmin (numSamples, maximumBlockSize); AudioBuffer buffer ((float **) destSamples, numDestChannels, startOffsetInDestBuffer, numSliceSamples); - positionInfo.timeInSeconds = static_cast (positionInfo.timeInSamples) / sampleRate; + positionInfo.setTimeInSeconds (static_cast (*positionInfo.getTimeInSamples()) / sampleRate); success &= playbackRenderer->processBlock (buffer, AudioProcessor::Realtime::no, positionInfo); numSamples -= numSliceSamples; startOffsetInDestBuffer += numSliceSamples; - positionInfo.timeInSamples += numSliceSamples; + positionInfo.setTimeInSamples (*positionInfo.getTimeInSamples() + numSliceSamples); } } } diff --git a/modules/juce_audio_formats/format/juce_ARAAudioReaders.h b/modules/juce_audio_formats/format/juce_ARAAudioReaders.h index 1a3ddfc7ab..c55a0636f7 100644 --- a/modules/juce_audio_formats/format/juce_ARAAudioReaders.h +++ b/modules/juce_audio_formats/format/juce_ARAAudioReaders.h @@ -183,7 +183,7 @@ public: private: std::unique_ptr playbackRenderer; - AudioPlayHead::CurrentPositionInfo positionInfo; + AudioPlayHead::PositionInfo positionInfo; ReadWriteLock lock; static constexpr int maximumBlockSize = 4 * 1024; diff --git a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp index 1ac7039003..9abcb11375 100644 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -1035,52 +1035,72 @@ namespace AAXClasses AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } - bool getCurrentPosition (juce::AudioPlayHead::CurrentPositionInfo& info) override + Optional getPosition() const override { + PositionInfo info; + const AAX_ITransport& transport = *Transport(); - info.bpm = 0.0; - check (transport.GetCurrentTempo (&info.bpm)); - - int32_t num = 4, den = 4; - transport.GetCurrentMeter (&num, &den); - info.timeSigNumerator = (int) num; - info.timeSigDenominator = (int) den; - info.timeInSamples = 0; - - if (transport.IsTransportPlaying (&info.isPlaying) != AAX_SUCCESS) - info.isPlaying = false; - - if (info.isPlaying - || transport.GetTimelineSelectionStartPosition (&info.timeInSamples) != AAX_SUCCESS) - check (transport.GetCurrentNativeSampleLocation (&info.timeInSamples)); - - info.timeInSeconds = (float) info.timeInSamples / sampleRate; - - int64_t ticks = 0; - - if (info.isPlaying) - check (transport.GetCustomTickPosition (&ticks, info.timeInSamples)); - else - check (transport.GetCurrentTickPosition (&ticks)); - - info.ppqPosition = (double) ticks / 960000.0; - - info.isLooping = false; - int64_t loopStartTick = 0, loopEndTick = 0; - check (transport.GetCurrentLoopPosition (&info.isLooping, &loopStartTick, &loopEndTick)); - info.ppqLoopStart = (double) loopStartTick / 960000.0; - info.ppqLoopEnd = (double) loopEndTick / 960000.0; - - std::tie (info.frameRate, info.editOriginTime) = [&transport] + info.setBpm ([&] { - AAX_EFrameRate frameRate; - int32_t offset; + double bpm = 0.0; - if (transport.GetTimeCodeInfo (&frameRate, &offset) != AAX_SUCCESS) - return std::make_tuple (FrameRate(), 0.0); + return transport.GetCurrentTempo (&bpm) == AAX_SUCCESS ? makeOptional (bpm) : nullopt; + }()); - const auto rate = [&] + info.setTimeSignature ([&] + { + int32_t num = 4, den = 4; + + return transport.GetCurrentMeter (&num, &den) == AAX_SUCCESS + ? makeOptional (TimeSignature { (int) num, (int) den }) + : nullopt; + }()); + + info.setIsPlaying ([&] + { + bool isPlaying = false; + + return transport.IsTransportPlaying (&isPlaying) == AAX_SUCCESS && isPlaying; + }()); + + info.setTimeInSamples ([&] + { + int64_t timeInSamples = 0; + + return ((! info.getIsPlaying() && transport.GetTimelineSelectionStartPosition (&timeInSamples) == AAX_SUCCESS) + || transport.GetCurrentNativeSampleLocation (&timeInSamples) == AAX_SUCCESS) + ? makeOptional (timeInSamples) + : nullopt; + }()); + + info.setTimeInSeconds ((float) info.getTimeInSamples().orFallback (0) / sampleRate); + + info.setPpqPosition ([&] + { + int64_t ticks = 0; + + return ((info.getIsPlaying() && transport.GetCustomTickPosition (&ticks, info.getTimeInSamples().orFallback (0))) == AAX_SUCCESS) + || transport.GetCurrentTickPosition (&ticks) == AAX_SUCCESS + ? makeOptional (ticks / 960000.0) + : nullopt; + }()); + + bool isLooping = false; + int64_t loopStartTick = 0, loopEndTick = 0; + + if (transport.GetCurrentLoopPosition (&isLooping, &loopStartTick, &loopEndTick) == AAX_SUCCESS) + { + info.setIsLooping (isLooping); + info.setLoopPoints (LoopPoints { (double) loopStartTick / 960000.0, (double) loopEndTick / 960000.0 }); + } + + AAX_EFrameRate frameRate; + int32_t offset; + + if (transport.GetTimeCodeInfo (&frameRate, &offset) == AAX_SUCCESS) + { + info.setFrameRate ([&]() -> Optional { switch ((JUCE_AAX_EFrameRate) frameRate) { @@ -1114,18 +1134,14 @@ namespace AAXClasses case JUCE_AAX_eFrameRate_Undeclared: break; } - return FrameRate(); - }(); + return {}; + }()); + } - const auto effectiveRate = rate.getEffectiveRate(); - return std::make_tuple (rate, effectiveRate != 0.0 ? offset / effectiveRate : 0.0); - }(); + const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; + info.setEditOriginTime (effectiveRate != 0.0 ? makeOptional (offset / effectiveRate) : nullopt); - // No way to get these: (?) - info.isRecording = false; - info.ppqPositionOfLastBarStart = 0; - - return true; + return info; } void audioProcessorParameterChanged (AudioProcessor* /*processor*/, int parameterIndex, float newValue) override diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index e017793e10..499a0831ae 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -103,7 +103,6 @@ struct AudioProcessorHolder class JuceAU : public AudioProcessorHolder, public MusicDeviceBase, public AudioProcessorListener, - public AudioPlayHead, public AudioProcessorParameter::Listener { public: @@ -140,7 +139,6 @@ public: totalInChannels = juceFilter->getTotalNumInputChannels(); totalOutChannels = juceFilter->getTotalNumOutputChannels(); - juceFilter->setPlayHead (this); juceFilter->addListener (this); addParameters(); @@ -1089,80 +1087,99 @@ public: return rate > 0 ? juceFilter->getLatencySamples() / rate : 0; } - //============================================================================== - bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info) override + class ScopedPlayHead : private AudioPlayHead { - info.timeSigNumerator = 0; - info.timeSigDenominator = 0; - info.editOriginTime = 0; - info.ppqPositionOfLastBarStart = 0; - info.isRecording = false; - - info.frameRate = [this] + public: + explicit ScopedPlayHead (JuceAU& juceAudioUnit) + : audioUnit (juceAudioUnit) { - switch (lastTimeStamp.mSMPTETime.mType) + audioUnit.juceFilter->setPlayHead (this); + } + + ~ScopedPlayHead() override + { + audioUnit.juceFilter->setPlayHead (nullptr); + } + + private: + Optional getPosition() const override + { + PositionInfo info; + + info.setFrameRate ([this]() -> Optional { - case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); - case kSMPTETimeType24: return FrameRate().withBaseRate (24); - case kSMPTETimeType25: return FrameRate().withBaseRate (25); - case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); - case kSMPTETimeType30: return FrameRate().withBaseRate (30); - case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); - case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - case kSMPTETimeType60: return FrameRate().withBaseRate (60); - case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); - case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); - case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - case kSMPTETimeType50: return FrameRate().withBaseRate (50); - default: break; + switch (audioUnit.lastTimeStamp.mSMPTETime.mType) + { + case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); + case kSMPTETimeType24: return FrameRate().withBaseRate (24); + case kSMPTETimeType25: return FrameRate().withBaseRate (25); + case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); + case kSMPTETimeType30: return FrameRate().withBaseRate (30); + case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); + case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + case kSMPTETimeType60: return FrameRate().withBaseRate (60); + case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); + case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); + case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + case kSMPTETimeType50: return FrameRate().withBaseRate (50); + default: break; + } + + return {}; + }()); + + double ppqPosition = 0.0; + double bpm = 0.0; + + if (audioUnit.CallHostBeatAndTempo (&ppqPosition, &bpm) == noErr) + { + info.setPpqPosition (ppqPosition); + info.setBpm (bpm); } - return FrameRate(); - }(); + UInt32 outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat; + float num; + UInt32 den; - if (CallHostBeatAndTempo (&info.ppqPosition, &info.bpm) != noErr) - { - info.ppqPosition = 0; - info.bpm = 0; + if (audioUnit.CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, + &num, + &den, + &outCurrentMeasureDownBeat) == noErr) + { + info.setTimeSignature (TimeSignature { (int) num, (int) den }); + info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); + } + + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; + Boolean playing = false, looping = false, playchanged; + + if (audioUnit.CallHostTransportState (&playing, + &playchanged, + &outCurrentSampleInTimeLine, + &looping, + &outCycleStartBeat, + &outCycleEndBeat) == noErr) + { + info.setIsPlaying (playing); + info.setTimeInSamples ((int64) (outCurrentSampleInTimeLine + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / audioUnit.getSampleRate()); + info.setIsLooping (looping); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); + } + else + { + // If the host doesn't support this callback, then use the sample time from lastTimeStamp: + outCurrentSampleInTimeLine = audioUnit.lastTimeStamp.mSampleTime; + } + + return info; } - UInt32 outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat; - float num; - UInt32 den; - - if (CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, &num, &den, - &outCurrentMeasureDownBeat) == noErr) - { - info.timeSigNumerator = (int) num; - info.timeSigDenominator = (int) den; - info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; - } - - double outCurrentSampleInTimeLine, outCycleStartBeat = 0, outCycleEndBeat = 0; - Boolean playing = false, looping = false, playchanged; - - if (CallHostTransportState (&playing, - &playchanged, - &outCurrentSampleInTimeLine, - &looping, - &outCycleStartBeat, - &outCycleEndBeat) != noErr) - { - // If the host doesn't support this callback, then use the sample time from lastTimeStamp: - outCurrentSampleInTimeLine = lastTimeStamp.mSampleTime; - } - - info.isPlaying = playing; - info.timeInSamples = (int64) (outCurrentSampleInTimeLine + 0.5); - info.timeInSeconds = info.timeInSamples / getSampleRate(); - info.isLooping = looping; - info.ppqLoopStart = outCycleStartBeat; - info.ppqLoopEnd = outCycleEndBeat; - - return true; - } + JuceAU& audioUnit; + }; + //============================================================================== void sendAUEvent (const AudioUnitEventType type, const int juceParamIndex) { if (restoringState) @@ -1309,14 +1326,11 @@ public: jassert (! juceFilter->getHostTimeNs()); if ((inTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) - { - const auto timestamp = timeConversions.hostTimeToNanos (inTimeStamp.mHostTime); - juceFilter->setHostTimeNanos (×tamp); - } + juceFilter->setHostTimeNanos (timeConversions.hostTimeToNanos (inTimeStamp.mHostTime)); struct AtEndOfScope { - ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } AudioProcessor& proc; }; @@ -1952,6 +1966,7 @@ private: void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept { const ScopedLock sl (juceFilter->getCallbackLock()); + const ScopedPlayHead playhead { *this }; if (juceFilter->isSuspended()) { diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index 1e5fb1e034..d056f8adc3 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -519,6 +519,7 @@ public: { midiMessages.clear(); lastTimeStamp.mSampleTime = std::numeric_limits::max(); + lastTimeStamp.mFlags = 0; } //============================================================================== @@ -852,7 +853,6 @@ public: midiMessages.ensureSize (2048); midiMessages.clear(); - zeromem (&lastAudioHead, sizeof (lastAudioHead)); hostMusicalContextCallback = [getAudioUnit() musicalContextBlock]; hostTransportStateCallback = [getAudioUnit() transportStateBlock]; @@ -1004,16 +1004,13 @@ public: } //============================================================================== - bool getCurrentPosition (CurrentPositionInfo& info) override + Optional getPosition() const override { - bool musicContextCallSucceeded = false; - bool transportStateCallSucceeded = false; + PositionInfo info; + info.setTimeInSamples ((int64) (lastTimeStamp.mSampleTime + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); - info = lastAudioHead; - info.timeInSamples = (int64) (lastTimeStamp.mSampleTime + 0.5); - info.timeInSeconds = info.timeInSamples / getAudioProcessor().getSampleRate(); - - info.frameRate = [this] + info.setFrameRate ([this] { switch (lastTimeStamp.mSMPTETime.mType) { @@ -1033,7 +1030,7 @@ public: } return FrameRate(); - }(); + }()); double num; NSInteger den; @@ -1047,17 +1044,14 @@ public: if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat)) { - musicContextCallSucceeded = true; - - info.timeSigNumerator = (int) num; - info.timeSigDenominator = (int) den; - info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; - info.bpm = bpm; - info.ppqPosition = ppqPosition; + info.setTimeSignature (TimeSignature { (int) num, (int) den }); + info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); + info.setBpm (bpm); + info.setPpqPosition (ppqPosition); } } - double outCurrentSampleInTimeLine, outCycleStartBeat = 0, outCycleEndBeat = 0; + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; AUHostTransportStateFlags flags; if (hostTransportStateCallback != nullptr) @@ -1066,22 +1060,16 @@ public: if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) { - transportStateCallSucceeded = true; - - info.timeInSamples = (int64) (outCurrentSampleInTimeLine + 0.5); - info.timeInSeconds = info.timeInSamples / getAudioProcessor().getSampleRate(); - info.isPlaying = ((flags & AUHostTransportStateMoving) != 0); - info.isLooping = ((flags & AUHostTransportStateCycling) != 0); - info.isRecording = ((flags & AUHostTransportStateRecording) != 0); - info.ppqLoopStart = outCycleStartBeat; - info.ppqLoopEnd = outCycleEndBeat; + info.setTimeInSamples ((int64) (outCurrentSampleInTimeLine + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); + info.setIsPlaying ((flags & AUHostTransportStateMoving) != 0); + info.setIsLooping ((flags & AUHostTransportStateCycling) != 0); + info.setIsRecording ((flags & AUHostTransportStateRecording) != 0); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); } } - if (musicContextCallSucceeded && transportStateCallSucceeded) - lastAudioHead = info; - - return true; + return info; } //============================================================================== @@ -1556,15 +1544,12 @@ private: if (timestamp != nullptr) { if ((timestamp->mFlags & kAudioTimeStampHostTimeValid) != 0) - { - const auto convertedTime = timeConversions.hostTimeToNanos (timestamp->mHostTime); - getAudioProcessor().setHostTimeNanos (&convertedTime); - } + getAudioProcessor().setHostTimeNanos (timeConversions.hostTimeToNanos (timestamp->mHostTime)); } struct AtEndOfScope { - ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } AudioProcessor& proc; }; @@ -1854,7 +1839,6 @@ private: ObjCBlock hostTransportStateCallback; AudioTimeStamp lastTimeStamp; - CurrentPositionInfo lastAudioHead; String contextName; diff --git a/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp b/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp index 03e6a2c966..dc2e6cc830 100644 --- a/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp +++ b/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp @@ -267,15 +267,9 @@ public: PlayHead (LV2_URID_Map mapFeatureIn, double sampleRateIn) : parser (mapFeatureIn), sampleRate (sampleRateIn) { - info.frameRate = fpsUnknown; - info.isLooping = false; - info.isRecording = false; - info.ppqLoopEnd = 0; - info.ppqLoopStart = 0; - info.ppqPositionOfLastBarStart = 0; } - void invalidate() { valid = false; } + void invalidate() { info = nullopt; } void readNewInfo (const LV2_Atom_Event* event) { @@ -289,6 +283,7 @@ public: const LV2_Atom* atomFrame = nullptr; const LV2_Atom* atomSpeed = nullptr; + const LV2_Atom* atomBar = nullptr; const LV2_Atom* atomBeat = nullptr; const LV2_Atom* atomBeatUnit = nullptr; const LV2_Atom* atomBeatsPerBar = nullptr; @@ -296,6 +291,7 @@ public: LV2_Atom_Object_Query query[] { { mLV2_TIME__frame, &atomFrame }, { mLV2_TIME__speed, &atomSpeed }, + { mLV2_TIME__bar, &atomBar }, { mLV2_TIME__beat, &atomBeat }, { mLV2_TIME__beatUnit, &atomBeatUnit }, { mLV2_TIME__beatsPerBar, &atomBeatsPerBar }, @@ -304,37 +300,38 @@ public: lv2_atom_object_query (object, query); - const auto setTimeInFrames = [&] (int64_t value) - { - info.timeInSamples = value; - info.timeInSeconds = (double) info.timeInSamples / sampleRate; - }; + info.emplace(); // Carla always seems to give us an integral 'beat' even though I'd expect // it to be a floating-point value - if ( lv2_shared::withValue (parser.parseNumericAtom (atomBeatsPerMinute), [&] (float value) { info.bpm = value; }) - && lv2_shared::withValue (parser.parseNumericAtom (atomBeatsPerBar), [&] (float value) { info.timeSigNumerator = (int) value; }) - && lv2_shared::withValue (parser.parseNumericAtom (atomBeatUnit), [&] (int32_t value) { info.timeSigDenominator = value; }) - && lv2_shared::withValue (parser.parseNumericAtom (atomBeat), [&] (double value) { info.ppqPosition = value; }) - && lv2_shared::withValue (parser.parseNumericAtom (atomSpeed), [&] (float value) { info.isPlaying = value != 0.0f; }) - && lv2_shared::withValue (parser.parseNumericAtom (atomFrame), setTimeInFrames)) + const auto numerator = parser.parseNumericAtom (atomBeatsPerBar); + const auto denominator = parser.parseNumericAtom (atomBeatUnit); + + if (numerator.hasValue() && denominator.hasValue()) + info->setTimeSignature (TimeSignature { (int) *numerator, (int) *denominator }); + + info->setBpm (parser.parseNumericAtom (atomBeatsPerMinute)); + info->setPpqPosition (parser.parseNumericAtom (atomBeat)); + info->setIsPlaying (parser.parseNumericAtom (atomSpeed).orFallback (0.0f) != 0.0f); + info->setBarCount (parser.parseNumericAtom (atomBar)); + + if (const auto parsed = parser.parseNumericAtom (atomFrame)) { - valid = true; + info->setTimeInSamples (*parsed); + info->setTimeInSeconds ((double) *parsed / sampleRate); } } - bool getCurrentPosition (CurrentPositionInfo& result) override + Optional getPosition() const override { - result = info; - return valid; + return info; } private: lv2_shared::NumericAtomParser parser; - CurrentPositionInfo info; + Optional info; double sampleRate; - bool valid = false; #define X(str) const LV2_URID m##str = parser.map (str); X (LV2_ATOM__Blank) @@ -346,6 +343,7 @@ private: X (LV2_TIME__beatsPerMinute) X (LV2_TIME__frame) X (LV2_TIME__speed) + X (LV2_TIME__bar) #undef X JUCE_LEAK_DETECTOR (PlayHead) diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index d4a282f167..36d3c6d835 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -400,7 +400,7 @@ public: struct AtEndOfScope { - ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } AudioProcessor& proc; }; @@ -628,30 +628,19 @@ public: } auto& info = currentPosition.emplace(); - info.bpm = (ti->flags & Vst2::kVstTempoValid) != 0 ? ti->tempo : 0.0; + info.setBpm ((ti->flags & Vst2::kVstTempoValid) != 0 ? makeOptional (ti->tempo) : nullopt); - if ((ti->flags & Vst2::kVstTimeSigValid) != 0) + info.setTimeSignature ((ti->flags & Vst2::kVstTimeSigValid) != 0 ? makeOptional (TimeSignature { ti->timeSigNumerator, ti->timeSigDenominator }) + : nullopt); + + info.setTimeInSamples ((int64) (ti->samplePos + 0.5)); + info.setTimeInSeconds (ti->samplePos / ti->sampleRate); + info.setPpqPosition ((ti->flags & Vst2::kVstPpqPosValid) != 0 ? makeOptional (ti->ppqPos) : nullopt); + info.setPpqPositionOfLastBarStart ((ti->flags & Vst2::kVstBarsValid) != 0 ? makeOptional (ti->barStartPos) : nullopt); + + if ((ti->flags & Vst2::kVstSmpteValid) != 0) { - info.timeSigNumerator = ti->timeSigNumerator; - info.timeSigDenominator = ti->timeSigDenominator; - } - else - { - info.timeSigNumerator = 4; - info.timeSigDenominator = 4; - } - - info.timeInSamples = (int64) (ti->samplePos + 0.5); - info.timeInSeconds = ti->samplePos / ti->sampleRate; - info.ppqPosition = (ti->flags & Vst2::kVstPpqPosValid) != 0 ? ti->ppqPos : 0.0; - info.ppqPositionOfLastBarStart = (ti->flags & Vst2::kVstBarsValid) != 0 ? ti->barStartPos : 0.0; - - std::tie (info.frameRate, info.editOriginTime) = [ti] - { - if ((ti->flags & Vst2::kVstSmpteValid) == 0) - return std::make_tuple (FrameRate(), 0.0); - - const auto rate = [&] + info.setFrameRate ([&]() -> Optional { switch (ti->smpteFrameRate) { @@ -673,43 +662,27 @@ public: case Vst2::kVstSmpteFilm35mm: return FrameRate().withBaseRate (24); } - return FrameRate(); - }(); + return nullopt; + }()); - const auto effectiveRate = rate.getEffectiveRate(); - return std::make_tuple (rate, effectiveRate != 0.0 ? ti->smpteOffset / (80.0 * effectiveRate) : 0.0); - }(); - - info.isRecording = (ti->flags & Vst2::kVstTransportRecording) != 0; - info.isPlaying = (ti->flags & (Vst2::kVstTransportRecording | Vst2::kVstTransportPlaying)) != 0; - info.isLooping = (ti->flags & Vst2::kVstTransportCycleActive) != 0; - - if ((ti->flags & Vst2::kVstCyclePosValid) != 0) - { - info.ppqLoopStart = ti->cycleStartPos; - info.ppqLoopEnd = ti->cycleEndPos; - } - else - { - info.ppqLoopStart = 0; - info.ppqLoopEnd = 0; + const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; + info.setEditOriginTime (effectiveRate != 0.0 ? makeOptional (ti->smpteOffset / (80.0 * effectiveRate)) : nullopt); } - if ((ti->flags & Vst2::kVstNanosValid) != 0) - { - const auto nanos = (uint64_t) ti->nanoSeconds; - processor->setHostTimeNanos (&nanos); - } + info.setIsRecording ((ti->flags & Vst2::kVstTransportRecording) != 0); + info.setIsPlaying ((ti->flags & (Vst2::kVstTransportRecording | Vst2::kVstTransportPlaying)) != 0); + info.setIsLooping ((ti->flags & Vst2::kVstTransportCycleActive) != 0); + + info.setLoopPoints ((ti->flags & Vst2::kVstCyclePosValid) != 0 ? makeOptional (LoopPoints { ti->cycleStartPos, ti->cycleEndPos }) + : nullopt); + + processor->setHostTimeNanos ((ti->flags & Vst2::kVstNanosValid) != 0 ? makeOptional ((uint64_t) ti->nanoSeconds) : nullopt); } //============================================================================== - bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info) override + Optional getPosition() const override { - if (! currentPosition.hasValue()) - return false; - - info = *currentPosition; - return true; + return currentPosition; } //============================================================================== @@ -2149,7 +2122,7 @@ private: Vst2::ERect editorRect; MidiBuffer midiEvents; VSTMidiEventList outgoingEvents; - Optional currentPosition; + Optional currentPosition; LegacyAudioParametersWrapper juceParameters; diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index bc753adb9b..091a272798 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -2868,34 +2868,46 @@ public: Steinberg::int32 channel, Vst::UnitID& unitId) override { return comPluginInstance->getUnitByBus (type, dir, busIndex, channel, unitId); } //============================================================================== - bool getCurrentPosition (CurrentPositionInfo& info) override + Optional getPosition() const override { - info.timeInSamples = jmax ((juce::int64) 0, processContext.projectTimeSamples); - info.timeInSeconds = static_cast (info.timeInSamples) / processContext.sampleRate; - info.bpm = jmax (1.0, processContext.tempo); - info.timeSigNumerator = jmax (1, (int) processContext.timeSigNumerator); - info.timeSigDenominator = jmax (1, (int) processContext.timeSigDenominator); - info.ppqPositionOfLastBarStart = processContext.barPositionMusic; - info.ppqPosition = processContext.projectTimeMusic; - info.ppqLoopStart = processContext.cycleStartMusic; - info.ppqLoopEnd = processContext.cycleEndMusic; - info.isRecording = (processContext.state & Vst::ProcessContext::kRecording) != 0; - info.isPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; - info.isLooping = (processContext.state & Vst::ProcessContext::kCycleActive) != 0; + PositionInfo info; + info.setTimeInSamples (jmax ((juce::int64) 0, processContext.projectTimeSamples)); + info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processContext.sampleRate); + info.setIsRecording ((processContext.state & Vst::ProcessContext::kRecording) != 0); + info.setIsPlaying ((processContext.state & Vst::ProcessContext::kPlaying) != 0); + info.setIsLooping ((processContext.state & Vst::ProcessContext::kCycleActive) != 0); - info.frameRate = [&] - { - if ((processContext.state & Vst::ProcessContext::kSmpteValid) == 0) - return FrameRate(); + info.setBpm ((processContext.state & Vst::ProcessContext::kTempoValid) != 0 + ? makeOptional (processContext.tempo) + : nullopt); - return FrameRate().withBaseRate ((int) processContext.frameRate.framesPerSecond) - .withDrop ((processContext.frameRate.flags & Vst::FrameRate::kDropRate) != 0) - .withPullDown ((processContext.frameRate.flags & Vst::FrameRate::kPullDownRate) != 0); - }(); + info.setTimeSignature ((processContext.state & Vst::ProcessContext::kTimeSigValid) != 0 + ? makeOptional (TimeSignature { processContext.timeSigNumerator, processContext.timeSigDenominator }) + : nullopt); - info.editOriginTime = (double) processContext.smpteOffsetSubframes / (80.0 * info.frameRate.getEffectiveRate()); + info.setLoopPoints ((processContext.state & Vst::ProcessContext::kCycleValid) != 0 + ? makeOptional (LoopPoints { processContext.cycleStartMusic, processContext.cycleEndMusic }) + : nullopt); - return true; + info.setPpqPosition ((processContext.state & Vst::ProcessContext::kProjectTimeMusicValid) != 0 + ? makeOptional (processContext.projectTimeMusic) + : nullopt); + + info.setPpqPositionOfLastBarStart ((processContext.state & Vst::ProcessContext::kBarPositionValid) != 0 + ? makeOptional (processContext.barPositionMusic) + : nullopt); + + info.setFrameRate ((processContext.state & Vst::ProcessContext::kSmpteValid) != 0 + ? makeOptional (FrameRate().withBaseRate ((int) processContext.frameRate.framesPerSecond) + .withDrop ((processContext.frameRate.flags & Vst::FrameRate::kDropRate) != 0) + .withPullDown ((processContext.frameRate.flags & Vst::FrameRate::kPullDownRate) != 0)) + : nullopt); + + info.setEditOriginTime (info.getFrameRate().hasValue() + ? makeOptional ((double) processContext.smpteOffsetSubframes / (80.0 * info.getFrameRate()->getEffectiveRate())) + : nullopt); + + return info; } //============================================================================== @@ -3335,10 +3347,7 @@ public: processContext = *data.processContext; if ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0) - { - const auto timestamp = (uint64_t) processContext.systemTime; - getPluginInstance().setHostTimeNanos (×tamp); - } + getPluginInstance().setHostTimeNanos ((uint64_t) processContext.systemTime); if (juceVST3EditController != nullptr) juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; @@ -3353,7 +3362,7 @@ public: struct AtEndOfScope { - ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } AudioProcessor& proc; }; diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index c40fcfd459..c2967bff3a 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -1385,7 +1385,7 @@ public: void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) { - if (const auto* hostTimeNs = getHostTimeNs()) + if (const auto hostTimeNs = getHostTimeNs()) { timeStamp.mHostTime = *hostTimeNs; timeStamp.mFlags |= kAudioTimeStampHostTimeValid; @@ -2298,12 +2298,10 @@ private: { if (auto* ph = getPlayHead()) { - AudioPlayHead::CurrentPositionInfo result; - - if (ph->getCurrentPosition (result)) + if (const auto pos = ph->getPosition()) { - setIfNotNull (outCurrentBeat, result.ppqPosition); - setIfNotNull (outCurrentTempo, result.bpm); + setIfNotNull (outCurrentBeat, pos->getPpqPosition().orFallback (0.0)); + setIfNotNull (outCurrentTempo, pos->getBpm().orFallback (0.0)); return noErr; } } @@ -2318,14 +2316,13 @@ private: { if (auto* ph = getPlayHead()) { - AudioPlayHead::CurrentPositionInfo result; - - if (ph->getCurrentPosition (result)) + if (const auto pos = ph->getPosition()) { + const auto signature = pos->getTimeSignature().orFallback (AudioPlayHead::TimeSignature{}); setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx - setIfNotNull (outTimeSig_Numerator, (UInt32) result.timeSigNumerator); - setIfNotNull (outTimeSig_Denominator, (UInt32) result.timeSigDenominator); - setIfNotNull (outCurrentMeasureDownBeat, result.ppqPositionOfLastBarStart); //xxx wrong + setIfNotNull (outTimeSig_Numerator, (UInt32) signature.numerator); + setIfNotNull (outTimeSig_Denominator, (UInt32) signature.denominator); + setIfNotNull (outCurrentMeasureDownBeat, pos->getPpqPositionOfLastBarStart().orFallback (0.0)); //xxx wrong return noErr; } } diff --git a/modules/juce_audio_processors/format_types/juce_LV2Common.h b/modules/juce_audio_processors/format_types/juce_LV2Common.h index 7479c4d389..edcf956114 100644 --- a/modules/juce_audio_processors/format_types/juce_LV2Common.h +++ b/modules/juce_audio_processors/format_types/juce_LV2Common.h @@ -134,16 +134,6 @@ struct ObjectTraits { static constexpr auto construct = lv2_atom_forge_object; using SequenceFrame = ScopedFrame; using ObjectFrame = ScopedFrame; -template -bool withValue (const Optional& opt, Callback&& callback) -{ - if (! opt.hasValue()) - return false; - - callback (*opt); - return true; -} - struct NumericAtomParser { explicit NumericAtomParser (LV2_URID_Map mapFeatureIn) diff --git a/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp index 267394acd1..53419384ff 100644 --- a/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp @@ -4767,9 +4767,9 @@ private: return; // Write timing info to the control port - AudioPlayHead::CurrentPositionInfo info; + const auto info = playhead->getPosition(); - if (! playhead->getCurrentPosition (info)) + if (! info.hasValue()) return; const auto& urids = instance->urids; @@ -4778,29 +4778,47 @@ private: lv2_shared::ObjectFrame object { forge, (uint32_t) 0, urids.mLV2_TIME__Position }; - lv2_atom_forge_key (forge, urids.mLV2_TIME__frame); - lv2_atom_forge_long (forge, info.timeInSamples); - - lv2_atom_forge_key (forge, urids.mLV2_TIME__bar); - lv2_atom_forge_long (forge, (info.ppqPosition * info.timeSigDenominator) / (4 * info.timeSigNumerator)); - lv2_atom_forge_key (forge, urids.mLV2_TIME__speed); - lv2_atom_forge_float (forge, info.isPlaying ? 1.0f : 0.0f); + lv2_atom_forge_float (forge, info->getIsPlaying() ? 1.0f : 0.0f); - lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat); - lv2_atom_forge_float (forge, (float) (info.ppqPosition - info.ppqPositionOfLastBarStart)); + if (const auto samples = info->getTimeInSamples()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__frame); + lv2_atom_forge_long (forge, *samples); + } - lv2_atom_forge_key (forge, urids.mLV2_TIME__beat); - lv2_atom_forge_double (forge, info.ppqPosition); + if (const auto bar = info->getBarCount()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__bar); + lv2_atom_forge_long (forge, *bar); + } - lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit); - lv2_atom_forge_int (forge, info.timeSigDenominator); + if (const auto beat = info->getPpqPosition()) + { + if (const auto barStart = info->getPpqPositionOfLastBarStart()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat); + lv2_atom_forge_float (forge, (float) (*beat - *barStart)); + } - lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar); - lv2_atom_forge_float (forge, (float) info.timeSigNumerator); + lv2_atom_forge_key (forge, urids.mLV2_TIME__beat); + lv2_atom_forge_double (forge, *beat); + } - lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute); - lv2_atom_forge_float (forge, (float) info.bpm); + if (const auto sig = info->getTimeSignature()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit); + lv2_atom_forge_int (forge, sig->denominator); + + lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar); + lv2_atom_forge_float (forge, (float) sig->numerator); + } + + if (const auto bpm = info->getBpm()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute); + lv2_atom_forge_float (forge, (float) *bpm); + } } void preparePortsForRun (AudioBuffer& audio, MidiBuffer& midiBuffer) diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 1997448951..536a6bdc6b 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -262,7 +262,7 @@ static void setStateForAllBusesOfType (Vst::IComponent* component, static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, double sampleRate, - const uint64_t* hostTimeNs) + Optional hostTimeNs) { jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK @@ -270,55 +270,67 @@ static void toProcessContext (Vst::ProcessContext& context, zerostruct (context); context.sampleRate = sampleRate; - auto& fr = context.frameRate; - if (playHead != nullptr) + const auto position = playHead != nullptr ? playHead->getPosition() + : nullopt; + + if (position.hasValue()) { - AudioPlayHead::CurrentPositionInfo position; - playHead->getCurrentPosition (position); + if (const auto timeInSamples = position->getTimeInSamples()) + context.projectTimeSamples = *timeInSamples; + else + jassertfalse; // The time in samples *must* be valid. - context.projectTimeSamples = position.timeInSamples; // Must always be valid, as stated by the VST3 SDK - context.projectTimeMusic = position.ppqPosition; // Does not always need to be valid... - context.tempo = position.bpm; - context.timeSigNumerator = position.timeSigNumerator; - context.timeSigDenominator = position.timeSigDenominator; - context.barPositionMusic = position.ppqPositionOfLastBarStart; - context.cycleStartMusic = position.ppqLoopStart; - context.cycleEndMusic = position.ppqLoopEnd; + if (const auto tempo = position->getBpm()) + { + context.state |= ProcessContext::kTempoValid; + context.tempo = *tempo; + } - context.frameRate.framesPerSecond = (Steinberg::uint32) position.frameRate.getBaseRate(); - context.frameRate.flags = (Steinberg::uint32) ((position.frameRate.isDrop() ? FrameRate::kDropRate : 0) - | (position.frameRate.isPullDown() ? FrameRate::kPullDownRate : 0)); + if (const auto loop = position->getLoopPoints()) + { + context.state |= ProcessContext::kCycleValid; + context.cycleStartMusic = loop->ppqStart; + context.cycleEndMusic = loop->ppqEnd; + } - if (position.isPlaying) context.state |= ProcessContext::kPlaying; - if (position.isRecording) context.state |= ProcessContext::kRecording; - if (position.isLooping) context.state |= ProcessContext::kCycleActive; - } - else - { - context.tempo = 120.0; - context.timeSigNumerator = 4; - context.timeSigDenominator = 4; - fr.framesPerSecond = 30; - fr.flags = 0; + if (const auto sig = position->getTimeSignature()) + { + context.state |= ProcessContext::kTimeSigValid; + context.timeSigNumerator = sig->numerator; + context.timeSigDenominator = sig->denominator; + } + + if (const auto pos = position->getPpqPosition()) + { + context.state |= ProcessContext::kProjectTimeMusicValid; + context.projectTimeMusic = *pos; + } + + if (const auto barStart = position->getPpqPositionOfLastBarStart()) + { + context.state |= ProcessContext::kBarPositionValid; + context.barPositionMusic = *barStart; + } + + if (const auto frameRate = position->getFrameRate()) + { + if (const auto offset = position->getEditOriginTime()) + { + context.state |= ProcessContext::kSmpteValid; + context.smpteOffsetSubframes = (Steinberg::int32) (80.0 * *offset * frameRate->getEffectiveRate()); + context.frameRate.framesPerSecond = (Steinberg::uint32) frameRate->getBaseRate(); + context.frameRate.flags = (Steinberg::uint32) ((frameRate->isDrop() ? FrameRate::kDropRate : 0) + | (frameRate->isPullDown() ? FrameRate::kPullDownRate : 0)); + } + } + + if (position->getIsPlaying()) context.state |= ProcessContext::kPlaying; + if (position->getIsRecording()) context.state |= ProcessContext::kRecording; + if (position->getIsLooping()) context.state |= ProcessContext::kCycleActive; } - if (context.projectTimeMusic >= 0.0) context.state |= ProcessContext::kProjectTimeMusicValid; - if (context.barPositionMusic >= 0.0) context.state |= ProcessContext::kBarPositionValid; - if (context.tempo > 0.0) context.state |= ProcessContext::kTempoValid; - if (context.frameRate.framesPerSecond > 0) context.state |= ProcessContext::kSmpteValid; - - if (context.cycleStartMusic >= 0.0 - && context.cycleEndMusic > 0.0 - && context.cycleEndMusic > context.cycleStartMusic) - { - context.state |= ProcessContext::kCycleValid; - } - - if (context.timeSigNumerator > 0 && context.timeSigDenominator > 0) - context.state |= ProcessContext::kTimeSigValid; - - if (hostTimeNs != nullptr) + if (hostTimeNs.hasValue()) { context.systemTime = (int64_t) *hostTimeNs; jassert (context.systemTime >= 0); diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index fc58cd2db2..2c7d618a81 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -2286,6 +2286,20 @@ private: return { nullptr, nullptr }; } + template + void setFromOptional (Member& target, Optional opt, int32_t flag) + { + if (opt.hasValue()) + { + target = static_cast (*opt); + vstHostTime.flags |= flag; + } + else + { + vstHostTime.flags &= ~flag; + } + } + //============================================================================== template void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, @@ -2311,34 +2325,32 @@ private: { if (auto* currentPlayHead = getPlayHead()) { - AudioPlayHead::CurrentPositionInfo position; - - if (currentPlayHead->getCurrentPosition (position)) + if (const auto position = currentPlayHead->getPosition()) { - vstHostTime.samplePos = (double) position.timeInSamples; - vstHostTime.tempo = position.bpm; - vstHostTime.timeSigNumerator = position.timeSigNumerator; - vstHostTime.timeSigDenominator = position.timeSigDenominator; - vstHostTime.ppqPos = position.ppqPosition; - vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; - vstHostTime.flags |= Vst2::kVstTempoValid - | Vst2::kVstTimeSigValid - | Vst2::kVstPpqPosValid - | Vst2::kVstBarsValid; + if (const auto samplePos = position->getTimeInSamples()) + vstHostTime.samplePos = (double) *samplePos; + else + jassertfalse; // VST hosts *must* call setTimeInSamples on the audio playhead - if (const auto* hostTimeNs = getHostTimeNs()) + if (auto sig = position->getTimeSignature()) { - vstHostTime.nanoSeconds = (double) *hostTimeNs; - vstHostTime.flags |= Vst2::kVstNanosValid; + vstHostTime.flags |= Vst2::kVstTimeSigValid; + vstHostTime.timeSigNumerator = sig->numerator; + vstHostTime.timeSigDenominator = sig->denominator; } else { - vstHostTime.flags &= ~Vst2::kVstNanosValid; + vstHostTime.flags &= ~Vst2::kVstTimeSigValid; } + setFromOptional (vstHostTime.ppqPos, position->getPpqPosition(), Vst2::kVstPpqPosValid); + setFromOptional (vstHostTime.barStartPos, position->getPpqPositionOfLastBarStart(), Vst2::kVstBarsValid); + setFromOptional (vstHostTime.nanoSeconds, getHostTimeNs(), Vst2::kVstNanosValid); + setFromOptional (vstHostTime.tempo, position->getBpm(), Vst2::kVstTempoValid); + int32 newTransportFlags = 0; - if (position.isPlaying) newTransportFlags |= Vst2::kVstTransportPlaying; - if (position.isRecording) newTransportFlags |= Vst2::kVstTransportRecording; + if (position->getIsPlaying()) newTransportFlags |= Vst2::kVstTransportPlaying; + if (position->getIsRecording()) newTransportFlags |= Vst2::kVstTransportRecording; if (newTransportFlags != (vstHostTime.flags & (Vst2::kVstTransportPlaying | Vst2::kVstTransportRecording))) @@ -2346,15 +2358,18 @@ private: else vstHostTime.flags &= ~Vst2::kVstTransportChanged; - const auto optionalFrameRate = [&fr = position.frameRate]() -> Optional + const auto optionalFrameRate = [fr = position->getFrameRate()]() -> Optional { - switch (fr.getBaseRate()) + if (! fr.hasValue()) + return {}; + + switch (fr->getBaseRate()) { - case 24: return fr.isPullDown() ? Vst2::kVstSmpte239fps : Vst2::kVstSmpte24fps; - case 25: return fr.isPullDown() ? Vst2::kVstSmpte249fps : Vst2::kVstSmpte25fps; - case 30: return fr.isPullDown() ? (fr.isDrop() ? Vst2::kVstSmpte2997dfps : Vst2::kVstSmpte2997fps) - : (fr.isDrop() ? Vst2::kVstSmpte30dfps : Vst2::kVstSmpte30fps); - case 60: return fr.isPullDown() ? Vst2::kVstSmpte599fps : Vst2::kVstSmpte60fps; + case 24: return fr->isPullDown() ? Vst2::kVstSmpte239fps : Vst2::kVstSmpte24fps; + case 25: return fr->isPullDown() ? Vst2::kVstSmpte249fps : Vst2::kVstSmpte25fps; + case 30: return fr->isPullDown() ? (fr->isDrop() ? Vst2::kVstSmpte2997dfps : Vst2::kVstSmpte2997fps) + : (fr->isDrop() ? Vst2::kVstSmpte30dfps : Vst2::kVstSmpte30fps); + case 60: return fr->isPullDown() ? Vst2::kVstSmpte599fps : Vst2::kVstSmpte60fps; } return {}; @@ -2362,18 +2377,24 @@ private: vstHostTime.flags |= optionalFrameRate ? Vst2::kVstSmpteValid : 0; vstHostTime.smpteFrameRate = optionalFrameRate.orFallback (Vst2::VstSmpteFrameRate{}); - vstHostTime.smpteOffset = (int32) (position.timeInSeconds * 80.0 * position.frameRate.getEffectiveRate() + 0.5); + const auto effectiveRate = position->getFrameRate().hasValue() ? position->getFrameRate()->getEffectiveRate() : 0.0; + vstHostTime.smpteOffset = (int32) (position->getTimeInSeconds().orFallback (0.0) * 80.0 * effectiveRate + 0.5); - if (position.isLooping) + if (const auto loop = position->getLoopPoints()) { - vstHostTime.cycleStartPos = position.ppqLoopStart; - vstHostTime.cycleEndPos = position.ppqLoopEnd; - vstHostTime.flags |= (Vst2::kVstCyclePosValid | Vst2::kVstTransportCycleActive); + vstHostTime.flags |= Vst2::kVstCyclePosValid; + vstHostTime.cycleStartPos = loop->ppqStart; + vstHostTime.cycleEndPos = loop->ppqEnd; } else { - vstHostTime.flags &= ~(Vst2::kVstCyclePosValid | Vst2::kVstTransportCycleActive); + vstHostTime.flags &= ~Vst2::kVstCyclePosValid; } + + if (position->getIsLooping()) + vstHostTime.flags |= Vst2::kVstTransportCycleActive; + else + vstHostTime.flags &= ~Vst2::kVstTransportCycleActive; } } diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 02f3faa0d3..7ee330a2b1 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1171,11 +1171,7 @@ public: processor.setHostTimeNanos (nullptr); // Clear host time @endcode */ - void setHostTimeNanos (const uint64_t* hostTimeIn) - { - hasHostTime = hostTimeIn != nullptr; - hostTime = hasHostTime ? *hostTimeIn : 0; - } + void setHostTimeNanos (Optional hostTimeIn) { hostTime = hostTimeIn; } /** The plugin may call this function inside the processBlock function (and only there!) to find the timestamp associated with the current audio block. @@ -1196,7 +1192,7 @@ public: } @endcode */ - const uint64_t* getHostTimeNs() const { return hasHostTime ? &hostTime : nullptr; } + Optional getHostTimeNs() const { return hostTime; } //============================================================================== /** This is called by the processor to specify its details before being played. Use this @@ -1552,8 +1548,7 @@ private: AudioProcessorParameterGroup parameterTree; Array flatParameterList; - uint64_t hostTime = 0; - bool hasHostTime = false; + Optional hostTime; AudioProcessorParameter* getParamChecked (int) const; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 20c5d5bd77..7803e9d39c 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -267,7 +267,7 @@ private: void perform (const Context& c) override { processor.setPlayHead (c.audioPlayHead); - processor.setHostTimeNanos (c.hostTimeNs.hasValue() ? &(*c.hostTimeNs) : nullptr); + processor.setHostTimeNanos (c.hostTimeNs); for (int i = 0; i < totalChans; ++i) audioChannels[i] = c.audioBuffers[audioChannelsToUse.getUnchecked (i)]; @@ -290,7 +290,7 @@ private: else callProcess (buffer, c.midiBuffers[midiBufferToUse]); - processor.setHostTimeNanos (nullptr); + processor.setHostTimeNanos (nullopt); } void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) @@ -1402,14 +1402,6 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m std::unique_ptr& renderSequence, std::atomic& isPrepared) { - const auto getHostTime = [&]() -> Optional - { - if (auto* nanos = graph.getHostTimeNs()) - return *nanos; - - return nullopt; - }; - if (graph.isNonRealtime()) { while (! isPrepared) @@ -1418,7 +1410,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m const ScopedLock sl (graph.getCallbackLock()); if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), graph.getHostTimeNs()); } else { @@ -1427,7 +1419,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m if (isPrepared) { if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), graph.getHostTimeNs()); } else { diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp index f5535412d5..fcece498b5 100644 --- a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp @@ -30,7 +30,7 @@ namespace juce bool ARARenderer::processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept + const AudioPlayHead::PositionInfo& positionInfo) noexcept { ignoreUnused (buffer, realtime, positionInfo); diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h index c03fb4ee8c..01762924e4 100644 --- a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h @@ -88,7 +88,7 @@ public: */ virtual bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept = 0; + const AudioPlayHead::PositionInfo& positionInfo) noexcept = 0; /** Renders the output into the given buffer. Returns true if rendering executed without error, false otherwise. @@ -108,7 +108,7 @@ public: */ virtual bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept; + const AudioPlayHead::PositionInfo& positionInfo) noexcept; }; //============================================================================== @@ -128,7 +128,7 @@ public: bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + const AudioPlayHead::PositionInfo& positionInfo) noexcept override { ignoreUnused (buffer, realtime, positionInfo); return false; @@ -189,7 +189,7 @@ public: // isNonRealtime of the process context - typically preview is limited to realtime. bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime isNonRealtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + const AudioPlayHead::PositionInfo& positionInfo) noexcept override { ignoreUnused (buffer, isNonRealtime, positionInfo); return true; diff --git a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp index a8471b9cfe..9d871b3d72 100644 --- a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp +++ b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp @@ -84,7 +84,7 @@ bool AudioProcessorARAExtension::releaseResourcesForARA() bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) + const AudioPlayHead::PositionInfo& positionInfo) { // validate that the host has prepared us before processing ARA_VALIDATE_API_STATE (isPrepared); @@ -109,12 +109,10 @@ bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer& buffer, juce::AudioProcessor::Realtime realtime, AudioPlayHead* playhead) { - AudioPlayHead::CurrentPositionInfo positionInfo; - - if (! isBoundToARA() || ! playhead || ! playhead->getCurrentPosition (positionInfo)) - positionInfo.resetToDefault(); - - return processBlockForARA (buffer, realtime, positionInfo); + return processBlockForARA (buffer, + realtime, + playhead != nullptr ? playhead->getPosition().orFallback (AudioPlayHead::PositionInfo{}) + : AudioPlayHead::PositionInfo{}); } //============================================================================== diff --git a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h index c3669e29f9..82a5644c46 100644 --- a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h +++ b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h @@ -144,7 +144,7 @@ protected: */ bool processBlockForARA (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo); + const AudioPlayHead::PositionInfo& positionInfo); /** Implementation helper for AudioProcessor::processBlock(). diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp index 7996d5ab6f..2d18e47880 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp @@ -267,11 +267,11 @@ void AudioProcessorPlayer::audioDeviceIOCallbackWithContext (const float** const const ScopedLock sl2 (processor->getCallbackLock()); - processor->setHostTimeNanos (context.hostTimeNs); + processor->setHostTimeNanos (context.hostTimeNs != nullptr ? makeOptional (*context.hostTimeNs) : nullopt); struct AtEndOfScope { - ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } AudioProcessor& proc; }; diff --git a/modules/juce_core/native/juce_mac_ObjCHelpers.h b/modules/juce_core/native/juce_mac_ObjCHelpers.h index 363a88167f..55cb01a236 100644 --- a/modules/juce_core/native/juce_mac_ObjCHelpers.h +++ b/modules/juce_core/native/juce_mac_ObjCHelpers.h @@ -502,7 +502,7 @@ public: bool operator!= (const void* ptr) const { return ((const void*) block != ptr); } ~ObjCBlock() { if (block != nullptr) [block release]; } - operator BlockType() { return block; } + operator BlockType() const { return block; } private: BlockType block;