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>
This commit is contained in:
David Racine
2026-09-06 09:10:21 -04:00
committed by GitHub
parent e44bd8d989
commit 17a4c2cf89
4 changed files with 39 additions and 13 deletions
+2 -2
View File
@@ -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 {
+33 -8
View File
@@ -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) {
+3 -2
View File
@@ -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 { \
+1 -1
View File
@@ -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;