Files
Henrik RydgårdandClaude Opus 5.5 b9ea98d2c5 Atrac3: Set up the decoder the way libatrac3plus does
libatrac3plus picks the codec parameter from the frame size and the
header's joint stereo flag, and the channel count plays no part. We
guessed joint stereo from the frame size and channel count instead, so
LocoRoco 2's MuiMui house music never started: the game writes a 2-channel
normal-stereo header for every track it streams, and that 0xC0 track holds
one mono sound unit per frame. Taken as joint stereo, its first frame failed
during setup, and the game retried forever.

Now the joint stereo flag comes from the track header, and the decoder's
channel count from the parameter it maps to, so that track decodes as mono
into both output channels, as on hardware. Low-level decoding, which has
no header, still goes by the frame size. Atrac2 saves the flag; older
states fall back to the guess.

Fixes #8647.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-29 17:14:00 -06:00

86 lines
3.4 KiB
C++

#pragma once
#include <cstdint>
#include "Core/HLE/AtracBase.h"
class Atrac2 : public AtracBase {
public:
// The default values are only used during save state load, in which case they get restored by DoState.
Atrac2(u32 contextAddr = 0, int codecType = 0);
~Atrac2();
AtracStatus BufferState() const override {
return context_->info.state;
}
void DoState(PointerWrap &p) override;
int GetNextDecodePosition(int *pos) const override;
int RemainingFrames() const override;
int LoopStatus() const override;
int Bitrate() const override;
int LoopNum() const override;
int SamplesPerFrame() const override { return context_->info.SamplesPerFrame(); }
int Channels() const override { return context_->info.numChan; }
int BytesPerFrame() const override { return context_->info.sampleSize; }
int SetLoopNum(int loopNum) override;
int CodecType() const override { return context_->info.codec; }
void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) override;
int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) const override;
int AddStreamData(u32 bytesToAdd) override;
int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) override;
int GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) override;
int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize, int outputChannels, bool isAA3) override;
int SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) override;
bool HasSecondBuffer() const override;
u32 DecodeData(u8 *outbuf, u32 outbufPtr, int *SamplesNum, int *finish, int *remains) override;
int DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) override;
void CheckForSas() override;
int EnqueueForSas(u32 address, u32 ptr) override;
void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) override;
const AtracSasStreamState *StreamStateForSas() const override { return context_->info.state == 0x10 ? &sas_ : nullptr; }
u32 GetNextSamples() override;
void InitLowLevel(const Atrac3LowLevelParams &params, int codecType) override;
int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const override;
// These will not be used by the new implementation.
void UpdateContextFromPSPMem() override {}
void NotifyGetContextAddress() override {}
int GetContextVersion() const override { return 2; }
u32 GetInternalCodecError() const override;
private:
u32 DecodeInternal(u32 outbufAddr, int *SamplesNum, int *finish);
void GetResetBufferInfoInternal(AtracResetBufferInfo *bufferInfo, int sample) const;
u32 ResetPlayPositionInternal(int seekPos, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf);
u32 SkipFrames(int *skippedCount);
void WrapLastPacket();
void DumpBufferToFile();
// Just the current decoded frame, in order to be able to cut off the first part of it
// to write the initial partial frame.
// Does not need to be saved.
int16_t *decodeTemp_ = nullptr;
// This is hidden state inside sceSas, really. Not visible in the context.
// But it doesn't really matter whether it's here or there.
AtracSasStreamState sas_;
// From the track header (RIFF fmt or OMA). The bitrate alone doesn't determine it (#8647).
bool jointStereo_ = false;
std::vector<u8> dumpBuffer_; // Used for dumping audio data to files.
bool dumped_ = false; // Whether we already dumped the audio data to a file.
};