summaryrefslogtreecommitdiff
path: root/soh/src/code
diff options
context:
space:
mode:
authorDavid Racine <bass_dr@hotmail.com>2026-08-30 10:03:45 -0400
committerGitHub <noreply@github.com>2026-08-30 14:03:45 +0000
commit8bf26365beb9b67a971962f05cb89fcfcf1bf59c (patch)
tree15cfc2a6d94a9e21188903d15899a24ff24a1fc0 /soh/src/code
parent2ab81a9bab824401a13e8decb714b290216c1dbc (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.c35
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;
}