summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Racine <bass_dr@hotmail.com>2026-09-06 09:10:21 -0400
committerGitHub <noreply@github.com>2026-09-06 13:10:21 +0000
commit17a4c2cf89a9b34c8b1b6db9bf17cd46ddb3c93b (patch)
tree3ab37dee33fc88d6e0b8be83a7395f01771e3acf
parente44bd8d9897c367d9b3e853e14114cc1901839d7 (diff)
Reopen the OPUS decoder when a note moves to another sample (#7137)
The decoder cached on a note was keyed on nothing, so a note reused for a different streamed sample carried on decoding the previous track. Audible as the wrong custom music: the sequence and soundfont the audio editor reports are correct, only the samples reaching the mixer are not. Reproduced on the game-start cutscene chain with a streamed music pack in 4 of 6 runs; 0 of 6 after. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: serprex <159546+serprex@users.noreply.github.com>
-rw-r--r--soh/include/z64audio.h4
-rw-r--r--soh/soh/mixer.c41
-rw-r--r--soh/soh/mixer.h5
-rw-r--r--soh/src/code/audio_playback.c2
4 files changed, 39 insertions, 13 deletions
diff --git a/soh/include/z64audio.h b/soh/include/z64audio.h
index 6fed61399..9fb4ba6ea 100644
--- a/soh/include/z64audio.h
+++ b/soh/include/z64audio.h
@@ -468,7 +468,7 @@ typedef struct {
/* 0x00F0 */ s16 dummyResampleState[0x10];
} NoteSynthesisBuffers; // size = 0x110
-struct OggOpusFile;
+struct OpusDecState;
typedef struct {
/* 0x00 */ u8 restart;
@@ -488,7 +488,7 @@ typedef struct {
/* 0x1A */ u8 unk_1A;
/* 0x1C */ u16 unk_1C;
/* 0x1E */ u16 unk_1E;
- struct OggOpusFile* opusFile; // Only for streamed opus audio
+ struct OpusDecState* opusFile; // Only for streamed opus audio
} NoteSynthesisState; // size = 0x20
typedef struct {
diff --git a/soh/soh/mixer.c b/soh/soh/mixer.c
index 6f5ca0232..303a977d1 100644
--- a/soh/soh/mixer.c
+++ b/soh/soh/mixer.c
@@ -2,6 +2,7 @@
//! when unoptimized and clang does not allow optimizing a single function.
#include <stdint.h>
+#include <stdlib.h>
#include <string.h>
#include "mixer.h"
@@ -108,28 +109,52 @@ void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbyte
#include <opusfile.h>
-void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OggOpusFile** decState, int32_t pos,
+// The decoder is cached on the note, so remember which buffer it was opened for.
+struct OpusDecState {
+ OggOpusFile* file;
+ const void* source;
+};
+
+void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OpusDecState** decState, int32_t pos,
uint32_t size) {
int readSamples = 0;
- if (*decState == NULL) {
- *decState = op_open_memory(source_addr, size, NULL);
+ struct OpusDecState* dec = *decState;
+
+ // Note reused for another streamed sample may have previous decoder opened,
+ // which would keep playing the previous track under new note.
+ if (dec != NULL && dec->source != source_addr) {
+ aOPUSFree(dec);
+ dec = NULL;
+ *decState = NULL;
}
- op_pcm_seek(*decState, pos);
- int ret = op_read(*decState, BUF_S16(dest_addr), nbytes / 2, NULL);
+ if (dec == NULL) {
+ OggOpusFile* file = op_open_memory(source_addr, size, NULL);
+ if (file == NULL) {
+ return;
+ }
+ dec = malloc(sizeof(struct OpusDecState));
+ dec->file = file;
+ dec->source = source_addr;
+ *decState = dec;
+ }
+
+ op_pcm_seek(dec->file, pos);
+ int ret = op_read(dec->file, BUF_S16(dest_addr), nbytes / 2, NULL);
if (ret < 0) {
return;
}
readSamples += ret;
while (readSamples < nbytes / 2) {
- ret = op_read(*decState, BUF_S16(dest_addr + readSamples * 2), (nbytes - readSamples * 2) / 2, NULL);
+ ret = op_read(dec->file, BUF_S16(dest_addr + readSamples * 2), (nbytes - readSamples * 2) / 2, NULL);
if (ret == 0)
break;
readSamples += ret;
}
}
-void aOPUSFree(struct OggOpusFile* opusFile) {
- op_free(opusFile);
+void aOPUSFree(struct OpusDecState* dec) {
+ op_free(dec->file);
+ free(dec);
}
void aSaveBufferImpl(uint16_t source_addr, int16_t* dest_addr, uint16_t nbytes) {
diff --git a/soh/soh/mixer.h b/soh/soh/mixer.h
index 048135e44..84c5be6e8 100644
--- a/soh/soh/mixer.h
+++ b/soh/soh/mixer.h
@@ -57,10 +57,11 @@ void aHiLoGainImpl(uint8_t g, uint16_t count, uint16_t addr);
void aUnkCmd3Impl(uint16_t a, uint16_t b, uint16_t c);
void aUnkCmd19Impl(uint8_t f, uint16_t count, uint16_t out_addr, uint16_t in_addr);
-struct OggOpusFile;
+struct OpusDecState;
-void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OggOpusFile** decState, int32_t pos,
+void aOPUSdecImpl(void* source_addr, uint16_t dest_addr, uint16_t nbytes, struct OpusDecState** decState, int32_t pos,
uint32_t size);
+void aOPUSFree(struct OpusDecState* dec);
#define aSegment(pkt, s, b) \
do { \
diff --git a/soh/src/code/audio_playback.c b/soh/src/code/audio_playback.c
index ce6c5d6f1..ba250252c 100644
--- a/soh/src/code/audio_playback.c
+++ b/soh/src/code/audio_playback.c
@@ -150,7 +150,7 @@ void Audio_NoteInit(Note* note) {
note->noteSubEu = gDefaultNoteSub;
}
-extern void aOPUSFree(struct OggOpusFile* opusFile);
+extern void aOPUSFree(struct OpusDecState* dec);
void Audio_NoteDisable(Note* note) {
if (note->noteSubEu.bitField0.needsInit == true) {
note->noteSubEu.bitField0.needsInit = false;