diff options
| author | David Racine <bass_dr@hotmail.com> | 2026-08-30 10:03:45 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-30 14:03:45 +0000 |
| commit | 8bf26365beb9b67a971962f05cb89fcfcf1bf59c (patch) | |
| tree | 15cfc2a6d94a9e21188903d15899a24ff24a1fc0 /soh/src/code | |
| parent | 2ab81a9bab824401a13e8decb714b290216c1dbc (diff) | |
Honour the loop end when reading streamed music (#7129)
Streamed samples were read a whole block at a time regardless of where the loop
ended, so playback ran past it and only then jumped back. The seam therefore landed
at a random offset up to a block late instead of where the sample asked for it, which
is why a loop that is clean in an editor pops in game.
Fixes #5780.
Claude-Session: https://claude.ai/code/session_011Ex3z29fQzPEZgwH4EA641
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'soh/src/code')
| -rw-r--r-- | soh/src/code/audio_synthesis.c | 35 |
1 files changed, 25 insertions, 10 deletions
diff --git a/soh/src/code/audio_synthesis.c b/soh/src/code/audio_synthesis.c index d283d4cfb..7b2db2267 100644 --- a/soh/src/code/audio_synthesis.c +++ b/soh/src/code/audio_synthesis.c @@ -851,28 +851,43 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS s5 = samplesLenAdjusted; goto skip; case CODEC_S16: - case CODEC_OPUS: - AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, (samplesLenAdjusted + 16) * 2); + case CODEC_OPUS: { + if (nSamplesProcessed == 0) { + AudioSynth_ClearBuffer(cmd++, DMEM_UNCOMPRESSED_NOTE, (samplesLenAdjusted + 16) * 2); + } flags = A_CONTINUE; skipBytes = 0; - size_t bytesToRead; - nSamplesProcessed += samplesLenAdjusted; + s32 nSamplesToRead = nSamplesToProcess; - if (((synthState->samplePosInt * 2) + (samplesLenAdjusted)*2) < audioFontSample->size) { - bytesToRead = (samplesLenAdjusted)*2; - } else { - bytesToRead = audioFontSample->size - (synthState->samplePosInt * 2); + // Reading a whole block past the loop end plays back whatever the song has after + // it and only then jumps, landing the seam at a random offset rather than the one + // the sample asked for. + if (nSamplesUntilLoopEnd > 0 && nSamplesToRead > nSamplesUntilLoopEnd) { + nSamplesToRead = nSamplesUntilLoopEnd; + } + + size_t bytesToRead = nSamplesToRead * 2; + size_t bytesAvailable = (synthState->samplePosInt * 2) < audioFontSample->size + ? audioFontSample->size - (synthState->samplePosInt * 2) + : 0; + if (bytesToRead > bytesAvailable) { + bytesToRead = bytesAvailable; } + // 2S2H [Port] [Custom audio] Handle decoding OPUS data if (audioFontSample->codec == CODEC_OPUS) { - aOPUSdecImpl(sampleAddr, DMEM_UNCOMPRESSED_NOTE, bytesToRead, &synthState->opusFile, + aOPUSdecImpl(sampleAddr, DMEM_UNCOMPRESSED_NOTE + s5, bytesToRead, &synthState->opusFile, synthState->samplePosInt, audioFontSample->fileSize); } else { - aLoadBuffer(cmd++, sampleAddr + (synthState->samplePosInt * 2), DMEM_UNCOMPRESSED_NOTE, + aLoadBuffer(cmd++, sampleAddr + (synthState->samplePosInt * 2), DMEM_UNCOMPRESSED_NOTE + s5, bytesToRead); } + nSamplesProcessed += nSamplesToRead; + s5 += nSamplesToRead * 2; + goto skip; + } case CODEC_REVERB: break; } |
