diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp index f06aeb6..3314759 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp @@ -1732,6 +1732,42 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::OnProceduralUnderflow( void UPS_AI_ConvAgent_ElevenLabsComponent::EnqueueAgentAudio(const TArray& PCMData) { + // Debug: flag a waveform discontinuity at the splice between the previous + // enqueued block and this one — the signature of the audio pops. The host's + // raw-PCM stream is continuous so its count should stay ~0; if a client's + // count climbs at message cadence, a splice problem remains. + { + const int32 DbgVal = CVarDebugElevenLabs.GetValueOnGameThread(); + const bool bAudioDbg = (DbgVal >= 0) ? (DbgVal > 0) : bDebug; + if (bAudioDbg && PCMData.Num() >= static_cast(sizeof(int16))) + { + const int16* Samples = reinterpret_cast(PCMData.GetData()); + const int32 NumSamples = PCMData.Num() / static_cast(sizeof(int16)); + if (bHasLastEnqueuedSample) + { + const int32 Delta = FMath::Abs(static_cast(Samples[0]) - static_cast(LastEnqueuedSample)); + static constexpr int32 DiscontinuityThreshold = 3000; // |delta| on the int16 scale + if (Delta > DiscontinuityThreshold) + { + ++AudioSpliceDiscontinuityCount; + UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning, + TEXT("[AUDIO-DBG] Role=%d splice discontinuity: |delta|=%d (prev=%d first=%d) count=%d — pop likely here."), + static_cast(GetOwnerRole()), Delta, + static_cast(LastEnqueuedSample), static_cast(Samples[0]), + AudioSpliceDiscontinuityCount); + } + else if (DbgVal >= 2) + { + UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, + TEXT("[AUDIO-DBG] Role=%d splice ok: |delta|=%d"), + static_cast(GetOwnerRole()), Delta); + } + } + LastEnqueuedSample = Samples[NumSamples - 1]; + bHasLastEnqueuedSample = true; + } + } + { FScopeLock Lock(&AudioQueueLock); AudioQueue.Append(PCMData); @@ -1908,6 +1944,7 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::StopAgentAudio() bPreBuffering = false; // Clear pre-buffer state on stop. ClientReassemblyPCM.Reset(); // Client: drop any half-received message so it can't leak into the next turn. AgentAudioEncodeLeftover.Reset(); // Server: drop the carried sub-frame tail so it can't glue onto the next turn. + bHasLastEnqueuedSample = false; // Debug: don't count the inter-turn splice as a discontinuity. { FScopeLock Lock(&AudioQueueLock); AudioQueue.Empty(); diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h index b159b6e..f93dd15 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h @@ -819,6 +819,14 @@ private: // per turn (new-turn guard in HandleAudioReceived + StopAgentAudio()). TArray AgentAudioEncodeLeftover; + // Debug: detects waveform discontinuities at enqueue/message splices (the + // signature of the audio "pops"). Tracks the last enqueued sample; a large + // jump to the next block's first sample is flagged and counted. Toggle with + // ps.ai.ConvAgent.Debug.ElevenLabs. Reset in StopAgentAudio(). + int16 LastEnqueuedSample = 0; + bool bHasLastEnqueuedSample = false; + int32 AudioSpliceDiscontinuityCount = 0; + // Pre-buffer state: delay playback start to absorb TTS inter-chunk gaps. bool bPreBuffering = false; double PreBufferStartTime = 0.0;