diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 5a1fdc39e2..0ab1778645 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,28 @@ JUCE breaking changes develop ======= +Change +------ +AudioProcessor::getHostTimeNs() and AudioProcessor::setHostTimeNanos() have +been removed. + +Possible Issues +--------------- +Code that used these functions will no longer compile. + +Workaround +---------- +Set and get the system time corresponding to the current audio callback using +the new functions AudioPlayHead::PositionInfo::getHostTimeNs() and +AudioPlayHead::PositionInfo::setHostTimeNs(). + +Rationale +--------- +This change consolidates callback-related timing information into the +PositionInfo type, improving the consistency of the AudioProcessor and +AudioPlayHead APIs. + + Change ------ AudioPlayHead::getCurrentPosition() has been deprecated and replaced with 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 af4966d5c6..031a76dff1 100644 --- a/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h +++ b/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h @@ -386,6 +386,12 @@ public: /** @see getEditOriginTime() */ void setEditOriginTime (Optional editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } + /** Get the host's callback time in nanoseconds, if available. */ + Optional getHostTimeNs() const { return getOptional (flagHostTimeNs, hostTimeNs); } + + /** @see getHostTimeNs() */ + void setHostTimeNs (Optional hostTimeNsIn) { setOptional (flagHostTimeNs, hostTimeNs, hostTimeNsIn); } + /** True if the transport is currently playing. */ bool getIsPlaying() const { return getFlag (flagIsPlaying); } @@ -421,6 +427,7 @@ public: i.getTimeSignature(), i.getBpm(), i.getLoopPoints(), + i.getHostTimeNs(), i.getIsPlaying(), i.getIsRecording(), i.getIsLooping()); @@ -472,9 +479,10 @@ public: flagTempo = 1 << 7, flagTimeSamples = 1 << 8, flagBarCount = 1 << 9, - flagIsPlaying = 1 << 10, - flagIsRecording = 1 << 11, - flagIsLooping = 1 << 12 + flagHostTimeNs = 1 << 10, + flagIsPlaying = 1 << 11, + flagIsRecording = 1 << 12, + flagIsLooping = 1 << 13 }; TimeSignature timeSignature; @@ -487,6 +495,7 @@ public: double tempoBpm = 0.0; int64_t timeInSamples = 0; int64_t barCount = 0; + uint64_t hostTimeNs = 0; int64_t flags = 0; }; 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 499a0831ae..fff199ba47 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -1173,6 +1173,10 @@ public: outCurrentSampleInTimeLine = audioUnit.lastTimeStamp.mSampleTime; } + info.setHostTimeNs ((audioUnit.lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0 + ? makeOptional (audioUnit.timeConversions.hostTimeToNanos (audioUnit.lastTimeStamp.mHostTime)) + : nullopt); + return info; } @@ -1323,19 +1327,6 @@ public: { lastTimeStamp = inTimeStamp; - jassert (! juceFilter->getHostTimeNs()); - - if ((inTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) - juceFilter->setHostTimeNanos (timeConversions.hostTimeToNanos (inTimeStamp.mHostTime)); - - struct AtEndOfScope - { - ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } - AudioProcessor& proc; - }; - - const AtEndOfScope scope { *juceFilter }; - // prepare buffers { pullInputAudio (ioActionFlags, inTimeStamp, nFrames); 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 d056f8adc3..4b049b32e0 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -1069,6 +1069,9 @@ public: } } + if ((lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) + info.setHostTimeNs (timeConversions.hostTimeToNanos (lastTimeStamp.mHostTime)); + return info; } @@ -1541,20 +1544,6 @@ private: const auto numProcessorBusesOut = AudioUnitHelpers::getBusCount (processor, false); - if (timestamp != nullptr) - { - if ((timestamp->mFlags & kAudioTimeStampHostTimeValid) != 0) - getAudioProcessor().setHostTimeNanos (timeConversions.hostTimeToNanos (timestamp->mHostTime)); - } - - struct AtEndOfScope - { - ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } - AudioProcessor& proc; - }; - - const AtEndOfScope scope { getAudioProcessor() }; - if (lastTimeStamp.mSampleTime != timestamp->mSampleTime) { // process params and incoming midi (only once for a given timestamp) 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 36d3c6d835..1127dd2524 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -398,14 +398,6 @@ public: { updateCallbackContextInfo(); - struct AtEndOfScope - { - ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } - AudioProcessor& proc; - }; - - const AtEndOfScope scope { *processor }; - int i; for (i = 0; i < numOut; ++i) { @@ -676,7 +668,7 @@ public: 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); + info.setHostTimeNs ((ti->flags & Vst2::kVstNanosValid) != 0 ? makeOptional ((uint64_t) ti->nanoSeconds) : nullopt); } //============================================================================== 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 091a272798..83e6e6f1c8 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -2907,6 +2907,10 @@ public: ? makeOptional ((double) processContext.smpteOffsetSubframes / (80.0 * info.getFrameRate()->getEffectiveRate())) : nullopt); + info.setHostTimeNs ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0 + ? makeOptional ((uint64_t) processContext.systemTime) + : nullopt); + return info; } @@ -3346,9 +3350,6 @@ public: { processContext = *data.processContext; - if ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0) - getPluginInstance().setHostTimeNanos ((uint64_t) processContext.systemTime); - if (juceVST3EditController != nullptr) juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; } @@ -3360,14 +3361,6 @@ public: juceVST3EditController->vst3IsPlaying = false; } - struct AtEndOfScope - { - ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } - AudioProcessor& proc; - }; - - const AtEndOfScope scope { getPluginInstance() }; - midiBuffer.clear(); if (data.inputParameterChanges != nullptr) diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index c2967bff3a..ec695c4f81 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -1385,7 +1385,10 @@ public: void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) { - if (const auto hostTimeNs = getHostTimeNs()) + auto* playhead = getPlayHead(); + const auto position = playhead != nullptr ? playhead->getPosition() : nullopt; + + if (const auto hostTimeNs = position.hasValue() ? position->getHostTimeNs() : nullopt) { timeStamp.mHostTime = *hostTimeNs; timeStamp.mFlags |= kAudioTimeStampHostTimeValid; diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 536a6bdc6b..69392dea09 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -261,8 +261,7 @@ static void setStateForAllBusesOfType (Vst::IComponent* component, //============================================================================== static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, - double sampleRate, - Optional hostTimeNs) + double sampleRate) { jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK @@ -325,17 +324,17 @@ static void toProcessContext (Vst::ProcessContext& context, } } + if (const auto hostTime = position->getHostTimeNs()) + { + context.state |= ProcessContext::kSystemTimeValid; + context.systemTime = (int64_t) *hostTime; + jassert (context.systemTime >= 0); + } + if (position->getIsPlaying()) context.state |= ProcessContext::kPlaying; if (position->getIsRecording()) context.state |= ProcessContext::kRecording; if (position->getIsLooping()) context.state |= ProcessContext::kCycleActive; } - - if (hostTimeNs.hasValue()) - { - context.systemTime = (int64_t) *hostTimeNs; - jassert (context.systemTime >= 0); - context.state |= ProcessContext::kSystemTimeValid; - } } //============================================================================== @@ -3477,7 +3476,7 @@ private: void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate) { - toProcessContext (timingInfo, getPlayHead(), processSampleRate, getHostTimeNs()); + toProcessContext (timingInfo, getPlayHead(), processSampleRate); destination.processContext = &timingInfo; } diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 2c7d618a81..499c39ccbe 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -2345,7 +2345,7 @@ private: setFromOptional (vstHostTime.ppqPos, position->getPpqPosition(), Vst2::kVstPpqPosValid); setFromOptional (vstHostTime.barStartPos, position->getPpqPositionOfLastBarStart(), Vst2::kVstBarsValid); - setFromOptional (vstHostTime.nanoSeconds, getHostTimeNs(), Vst2::kVstNanosValid); + setFromOptional (vstHostTime.nanoSeconds, position->getHostTimeNs(), Vst2::kVstNanosValid); setFromOptional (vstHostTime.tempo, position->getBpm(), Vst2::kVstTempoValid); int32 newTransportFlags = 0; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 7ee330a2b1..36e9769f22 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1153,47 +1153,6 @@ public: */ virtual void setPlayHead (AudioPlayHead* newPlayHead); - //============================================================================== - /** Hosts may call this function to supply the system time corresponding to the - current audio buffer. - - If you want to set a valid time, pass a pointer to a uint64_t holding the current time. The - value will be copied into the AudioProcessor instance without any allocation/deallocation. - - If you want to clear any stored host time, pass nullptr. - - Calls to this function must be synchronised (i.e. not simultaneous) with the audio callback. - - @code - const auto currentHostTime = computeHostTimeNanos(); - processor.setHostTimeNanos (¤tHostTime); // Set a valid host time - // ...call processBlock etc. - processor.setHostTimeNanos (nullptr); // Clear host time - @endcode - */ - 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. - - If a timestamp is available, this will return a pointer to that timestamp. You should - immediately copy the pointed-to value and use that in any following code. Do *not* free - any pointer returned by this function. - - If no timestamp is provided, this will return nullptr. - - @code - void processBlock (AudioBuffer&, MidiBuffer&) override - { - if (auto* timestamp = getHostTimeNs()) - { - // Use *timestamp here to compensate for callback jitter etc. - } - } - @endcode - */ - Optional getHostTimeNs() const { return hostTime; } - //============================================================================== /** This is called by the processor to specify its details before being played. Use this version of the function if you are not interested in any sidechain and/or aux buses @@ -1548,8 +1507,6 @@ private: AudioProcessorParameterGroup parameterTree; Array flatParameterList; - Optional hostTime; - AudioProcessorParameter* getParamChecked (int) const; #if JUCE_DEBUG diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 7803e9d39c..ea943564b0 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -37,18 +37,15 @@ static void updateOnMessageThread (AsyncUpdater& updater) template struct GraphRenderSequence { - GraphRenderSequence() {} - struct Context { FloatType** audioBuffers; MidiBuffer* midiBuffers; AudioPlayHead* audioPlayHead; - Optional hostTimeNs; int numSamples; }; - void perform (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead, Optional hostTimeNs) + void perform (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead) { auto numSamples = buffer.getNumSamples(); auto maxSamples = renderingBuffer.getNumSamples(); @@ -67,7 +64,7 @@ struct GraphRenderSequence // Splitting up the buffer like this will cause the play head and host time to be // invalid for all but the first chunk... - perform (audioChunk, midiChunk, audioPlayHead, hostTimeNs); + perform (audioChunk, midiChunk, audioPlayHead); chunkStartSample += maxSamples; } @@ -82,7 +79,7 @@ struct GraphRenderSequence currentMidiOutputBuffer.clear(); { - const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, hostTimeNs, numSamples }; + const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, numSamples }; for (auto* op : renderOps) op->perform (context); @@ -267,7 +264,6 @@ private: void perform (const Context& c) override { processor.setPlayHead (c.audioPlayHead); - processor.setHostTimeNanos (c.hostTimeNs); for (int i = 0; i < totalChans; ++i) audioChannels[i] = c.audioBuffers[audioChannelsToUse.getUnchecked (i)]; @@ -289,8 +285,6 @@ private: buffer.clear(); else callProcess (buffer, c.midiBuffers[midiBufferToUse]); - - processor.setHostTimeNanos (nullopt); } void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) @@ -1410,7 +1404,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m const ScopedLock sl (graph.getCallbackLock()); if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), graph.getHostTimeNs()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); } else { @@ -1419,7 +1413,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m if (isPrepared) { if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), graph.getHostTimeNs()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); } else { diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp index 2d18e47880..bb1c5580fa 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp @@ -171,6 +171,8 @@ void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) if (processor == processorToPlay) return; + sampleCount = 0; + if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0) { defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() }; @@ -267,15 +269,48 @@ void AudioProcessorPlayer::audioDeviceIOCallbackWithContext (const float** const const ScopedLock sl2 (processor->getCallbackLock()); - processor->setHostTimeNanos (context.hostTimeNs != nullptr ? makeOptional (*context.hostTimeNs) : nullopt); - - struct AtEndOfScope + class PlayHead : private AudioPlayHead { - ~AtEndOfScope() { proc.setHostTimeNanos (nullopt); } - AudioProcessor& proc; + public: + PlayHead (AudioProcessor& proc, + Optional hostTimeIn, + uint64_t sampleCountIn, + double sampleRateIn) + : processor (proc), + hostTimeNs (hostTimeIn), + sampleCount (sampleCountIn), + seconds ((double) sampleCountIn / sampleRateIn) + { + processor.setPlayHead (this); + } + + ~PlayHead() override + { + processor.setPlayHead (nullptr); + } + + private: + Optional getPosition() const override + { + PositionInfo info; + info.setHostTimeNs (hostTimeNs); + info.setTimeInSamples ((int64_t) sampleCount); + info.setTimeInSeconds (seconds); + return info; + } + + AudioProcessor& processor; + Optional hostTimeNs; + uint64_t sampleCount; + double seconds; }; - const AtEndOfScope scope { *processor }; + PlayHead playHead { *processor, + context.hostTimeNs != nullptr ? makeOptional (*context.hostTimeNs) : nullopt, + sampleCount, + sampleRate }; + + sampleCount += (uint64_t) numSamples; if (! processor->isSuspended()) { diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h index 89abfac99a..8f14d52e95 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h @@ -137,6 +137,7 @@ private: MidiBuffer incomingMidi; MidiMessageCollector messageCollector; MidiOutput* midiOutput = nullptr; + uint64_t sampleCount = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorPlayer) };