diff --git a/soh/include/z64audio.h b/soh/include/z64audio.h index 6fed61399d..9fb4ba6ea5 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 6f5ca02327..303a977d16 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 +#include #include #include "mixer.h" @@ -108,28 +109,52 @@ void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbyte #include -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 048135e448..84c5be6e82 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 ce6c5d6f19..ba250252ca 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;