mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
AudioPlayHead: Improve granularity of position info
This commit is contained in:
parent
891daf1332
commit
8fbd99c424
27 changed files with 924 additions and 572 deletions
|
|
@ -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<PositionInfo> getPosition() const override
|
||||
{
|
||||
PositionInfo info;
|
||||
|
||||
info.setFrameRate ([this]() -> Optional<FrameRate>
|
||||
{
|
||||
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<float>& buffer, MidiBuffer& midiBuffer) noexcept
|
||||
{
|
||||
const ScopedLock sl (juceFilter->getCallbackLock());
|
||||
const ScopedPlayHead playhead { *this };
|
||||
|
||||
if (juceFilter->isSuspended())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -519,6 +519,7 @@ public:
|
|||
{
|
||||
midiMessages.clear();
|
||||
lastTimeStamp.mSampleTime = std::numeric_limits<Float64>::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<PositionInfo> 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<AUHostTransportStateBlock> hostTransportStateCallback;
|
||||
|
||||
AudioTimeStamp lastTimeStamp;
|
||||
CurrentPositionInfo lastAudioHead;
|
||||
|
||||
String contextName;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue