@@ -146,7 +146,9 @@ void CSound::Mix(short *pFinalOut, unsigned Frames)
146146 if (Voice.m_Tick == Voice.m_pSample ->m_NumFrames )
147147 {
148148 if (Voice.m_Flags & ISound::FLAG_LOOP)
149- Voice.m_Tick = 0 ;
149+ {
150+ Voice.m_Tick = Voice.m_pSample ->m_LoopStart ;
151+ }
150152 else
151153 {
152154 Voice.m_pSample = nullptr ;
@@ -324,6 +326,10 @@ void CSound::RateConvert(CSample &Sample) const
324326 }
325327 }
326328
329+ // adjust looping position, note that this is not precise
330+ const double Factor = (double )m_MixingRate / (double )Sample.m_Rate ;
331+ Sample.m_LoopStart = std::round (Sample.m_LoopStart * Factor);
332+
327333 // free old data and apply new
328334 free (Sample.m_pData );
329335 Sample.m_pData = pNewData;
@@ -371,15 +377,40 @@ bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize, c
371377 Pos += Read;
372378 }
373379
374- op_free (pOpusFile);
375-
376380 Sample.m_pData = pSampleData;
377381 Sample.m_NumFrames = Pos;
378382 Sample.m_Rate = 48000 ;
379383 Sample.m_Channels = NumChannels;
380- Sample.m_LoopStart = -1 ;
381- Sample.m_LoopEnd = -1 ;
384+ Sample.m_LoopStart = 0 ;
382385 Sample.m_PausedAt = 0 ;
386+
387+ const OpusTags *pTags = op_tags (pOpusFile, -1 );
388+ if (pTags)
389+ {
390+ for (int i = 0 ; i < pTags->comments ; ++i)
391+ {
392+ const char *pComment = pTags->user_comments [i];
393+ if (!pComment)
394+ continue ;
395+ if (!str_startswith (pComment, " LOOP_START=" ))
396+ continue ;
397+ int LoopStart = -1 ;
398+ if (!str_toint (pComment + str_length (" LOOP_START=" ), &LoopStart))
399+ {
400+ log_error (" sound/opus" , " Invalid LOOP_START tag. Value='%s' Filename='%s'" , pComment + str_length (" LOOP_START=" ), pContextName);
401+ break ;
402+ }
403+ if (LoopStart < 0 || LoopStart >= Sample.m_NumFrames )
404+ {
405+ log_error (" sound/opus" , " Tag LOOP_START out of range. Value=%d Min=0 Max=%d Filename='%s'" , LoopStart, Sample.m_NumFrames - 1 , pContextName);
406+ break ;
407+ }
408+ Sample.m_LoopStart = LoopStart;
409+ break ;
410+ }
411+ }
412+
413+ op_free (pOpusFile);
383414 }
384415 else
385416 {
@@ -504,8 +535,7 @@ bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize, con
504535 Sample.m_NumFrames = NumSamples;
505536 Sample.m_Rate = SampleRate;
506537 Sample.m_Channels = NumChannels;
507- Sample.m_LoopStart = -1 ;
508- Sample.m_LoopEnd = -1 ;
538+ Sample.m_LoopStart = 0 ;
509539 Sample.m_PausedAt = 0 ;
510540
511541 s_pWVBuffer = nullptr ;
@@ -790,9 +820,28 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset)
790820 bool IsLooping = m_aVoices[VoiceId].m_Flags & ISound::FLAG_LOOP;
791821 uint64_t TickOffset = m_aVoices[VoiceId].m_pSample ->m_Rate * TimeOffset;
792822 if (m_aVoices[VoiceId].m_pSample ->m_NumFrames > 0 && IsLooping)
793- Tick = TickOffset % m_aVoices[VoiceId].m_pSample ->m_NumFrames ;
823+ {
824+ const int LoopStart = m_aVoices[VoiceId].m_pSample ->m_LoopStart ;
825+ const int NumFrames = m_aVoices[VoiceId].m_pSample ->m_NumFrames ;
826+ if (TickOffset < static_cast <uint64_t >(NumFrames))
827+ {
828+ // Still in first playthrough
829+ Tick = TickOffset;
830+ }
831+ else
832+ {
833+ // Past first playthrough, wrap within loop section only
834+ const int LoopLength = NumFrames - LoopStart;
835+ if (LoopLength > 0 )
836+ Tick = LoopStart + ((TickOffset - NumFrames) % LoopLength);
837+ else
838+ Tick = LoopStart;
839+ }
840+ }
794841 else
795- Tick = std::clamp (TickOffset, (uint64_t )0 , (uint64_t )m_aVoices[VoiceId].m_pSample ->m_NumFrames );
842+ {
843+ Tick = std::clamp<uint64_t >(TickOffset, 0 , m_aVoices[VoiceId].m_pSample ->m_NumFrames );
844+ }
796845
797846 // at least 200msec off, else depend on buffer size
798847 float Threshold = maximum (0 .2f * m_aVoices[VoiceId].m_pSample ->m_Rate , (float )m_MaxFrames);
0 commit comments