Break out AtracTrack into its own file. Add a little atrac analysis tool to ImDebugger (for future use)

This commit is contained in:
Henrik Rydgård 2025-03-19 16:15:51 +01:00
parent 24d859f773
commit e70dd3b2df
21 changed files with 571 additions and 443 deletions

View file

@ -2413,6 +2413,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/System.h
Core/ThreadPools.cpp
Core/ThreadPools.h
Core/Util/AtracTrack.cpp
Core/Util/AtracTrack.h
Core/Util/AudioFormat.cpp
Core/Util/AudioFormat.h
Core/Util/GameManager.cpp

View file

@ -104,6 +104,7 @@ enum class BrowseFileType {
SOUND_EFFECT,
ZIP,
SYMBOL_MAP,
ATRAC3,
ANY,
};

View file

@ -1086,6 +1086,7 @@
<ClCompile Include="System.cpp" />
<ClCompile Include="ThreadPools.cpp" />
<ClCompile Include="TiltEventProcessor.cpp" />
<ClCompile Include="Util\AtracTrack.cpp" />
<ClCompile Include="Util\AudioFormat.cpp" />
<ClCompile Include="Util\BlockAllocator.cpp" />
<ClCompile Include="Util\DisArm64.cpp" />
@ -1471,6 +1472,7 @@
<ClInclude Include="ThreadEventQueue.h" />
<ClInclude Include="ThreadPools.h" />
<ClInclude Include="TiltEventProcessor.h" />
<ClInclude Include="Util\AtracTrack.h" />
<ClInclude Include="Util\AudioFormat.h" />
<ClInclude Include="Util\BlockAllocator.h" />
<ClInclude Include="Util\DisArm64.h" />

View file

@ -1342,6 +1342,9 @@
<ClCompile Include="HLE\sceAac.cpp">
<Filter>HLE\Libraries</Filter>
</ClCompile>
<ClCompile Include="Util\AtracTrack.cpp">
<Filter>Util</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
@ -2166,6 +2169,9 @@
<ClInclude Include="HLE\ErrorCodes.h">
<Filter>HLE</Filter>
</ClInclude>
<ClInclude Include="Util\AtracTrack.h">
<Filter>Util</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.TXT" />

View file

@ -32,13 +32,6 @@
const size_t overAllocBytes = 16384;
const int RIFF_CHUNK_MAGIC = 0x46464952;
const int RIFF_WAVE_MAGIC = 0x45564157;
const int FMT_CHUNK_MAGIC = 0x20746D66;
const int DATA_CHUNK_MAGIC = 0x61746164;
const int SMPL_CHUNK_MAGIC = 0x6C706D73;
const int FACT_CHUNK_MAGIC = 0x74636166;
Atrac::~Atrac() {
ResetData();
}
@ -238,284 +231,6 @@ void Track::DebugLog() const {
DEBUG_LOG(Log::ME, "sampleSize: %d (%03x", bytesPerFrame, bytesPerFrame);
}
int AnalyzeAtracTrack(u32 addr, u32 size, Track *track) {
// 72 is about the size of the minimum required data to even be valid.
// TODO: Validate stuff more.
if (Memory::ReadUnchecked_U32(addr) != RIFF_CHUNK_MAGIC) {
ERROR_LOG(Log::ME, "Couldn't find RIFF header");
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
struct RIFFFmtChunk {
u16_le fmtTag;
u16_le channels;
u32_le samplerate;
u32_le avgBytesPerSec;
u16_le blockAlign;
};
u32 offset = 8;
track->firstSampleOffset = 0;
while (Memory::Read_U32(addr + offset) != RIFF_WAVE_MAGIC) {
// Get the size preceding the magic.
int chunk = Memory::Read_U32(addr + offset - 4);
// Round the chunk size up to the nearest 2.
offset += chunk + (chunk & 1);
if (offset + 12 > size) {
ERROR_LOG(Log::ME, "AnalyzeAtracTrack(%08x, %d): too small for WAVE chunk at offset %d", addr, size, offset);
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
if (Memory::Read_U32(addr + offset) != RIFF_CHUNK_MAGIC) {
ERROR_LOG(Log::ME, "AnalyzeAtracTrack(%08x, %d): RIFF chunk did not contain WAVE", addr, size);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
offset += 8;
}
offset += 4;
if (offset != 12) {
WARN_LOG(Log::ME, "RIFF chunk at offset: %d", offset);
}
// RIFF size excluding chunk header.
track->fileSize = Memory::Read_U32(addr + offset - 8) + 8;
// Even if the RIFF size is too low, it may simply be incorrect. This works on real firmware.
u32 maxSize = std::max(track->fileSize, size);
bool bfoundData = false;
u32 dataChunkSize = 0;
int sampleOffsetAdjust = 0;
while (maxSize >= offset + 8 && !bfoundData) {
int chunkMagic = Memory::Read_U32(addr + offset);
u32 chunkSize = Memory::Read_U32(addr + offset + 4);
// Account for odd sized chunks.
if (chunkSize & 1) {
WARN_LOG_REPORT_ONCE(oddchunk, Log::ME, "RIFF chunk had uneven size");
}
chunkSize += (chunkSize & 1);
offset += 8;
if (chunkSize > maxSize - offset)
break;
switch (chunkMagic) {
case FMT_CHUNK_MAGIC:
{
if (track->codecType != 0) {
ERROR_LOG(Log::ME, "AnalyzeTrack: multiple fmt chunks is not valid");
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
auto at3fmt = PSPPointer<const RIFFFmtChunk>::Create(addr + offset);
if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) {
ERROR_LOG(Log::ME, "AnalyzeTrack: fmt definition too small(%d)", chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (at3fmt->fmtTag == AT3_MAGIC)
track->codecType = PSP_MODE_AT_3;
else if (at3fmt->fmtTag == AT3_PLUS_MAGIC)
track->codecType = PSP_MODE_AT_3_PLUS;
else {
ERROR_LOG(Log::ME, "AnalyzeTrack: invalid fmt magic: %04x", at3fmt->fmtTag);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->channels = at3fmt->channels;
if (track->channels != 1 && track->channels != 2) {
ERROR_LOG_REPORT(Log::ME, "AnalyzeTrack: unsupported channel count %d", track->channels);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (at3fmt->samplerate != 44100) {
ERROR_LOG_REPORT(Log::ME, "AnalyzeTrack: unsupported sample rate %d", at3fmt->samplerate);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->bitrate = at3fmt->avgBytesPerSec * 8;
track->bytesPerFrame = at3fmt->blockAlign;
if (track->bytesPerFrame == 0) {
ERROR_LOG_REPORT(Log::ME, "invalid bytes per frame: %d", track->bytesPerFrame);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
// TODO: There are some format specific bytes here which seem to have fixed values?
// Probably don't need them.
if (at3fmt->fmtTag == AT3_MAGIC) {
// This is the offset to the jointStereo_ field.
track->jointStereo = Memory::Read_U16(addr + offset + 24);
// Then there are more fields here.
u16 unknown1_2 = Memory::Read_U16(addr + offset + 30);
}
if (chunkSize > 16) {
// Read and format extra bytes as hexadecimal
std::string hex;
DataToHexString(Memory::GetPointer(addr + offset + 16), chunkSize - 16, &hex, false);
DEBUG_LOG(Log::ME, "Additional chunk data (beyond 16 bytes): %s", hex.c_str());
}
break;
}
case FACT_CHUNK_MAGIC:
{
track->endSample = Memory::Read_U32(addr + offset);
if (chunkSize >= 8) {
track->firstSampleOffset = Memory::Read_U32(addr + offset + 4);
}
if (chunkSize >= 12) {
u32 largerOffset = Memory::Read_U32(addr + offset + 8);
// Works, but "largerOffset"??
sampleOffsetAdjust = track->firstSampleOffset - largerOffset;
}
break;
}
case SMPL_CHUNK_MAGIC:
{
if (chunkSize < 32) {
ERROR_LOG(Log::ME, "AnalyzeTrack: smpl chunk too small (%d)", chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
int checkNumLoops = Memory::Read_U32(addr + offset + 28);
if (checkNumLoops != 0 && chunkSize < 36 + 20) {
ERROR_LOG(Log::ME, "AnalyzeTrack: smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (checkNumLoops < 0) {
ERROR_LOG(Log::ME, "bad checkNumLoops (%d)", checkNumLoops);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->loopinfo.resize(checkNumLoops);
u32 loopinfoAddr = addr + offset + 36;
// The PSP only cares about the first loop start and end, it seems.
// Most likely can skip the rest of this data, but it's not hurting anyone.
for (int i = 0; i < checkNumLoops && 36 + (u32)i < chunkSize; i++, loopinfoAddr += 24) {
track->loopinfo[i].cuePointID = Memory::Read_U32(loopinfoAddr);
track->loopinfo[i].type = Memory::Read_U32(loopinfoAddr + 4);
track->loopinfo[i].startSample = Memory::Read_U32(loopinfoAddr + 8);
track->loopinfo[i].endSample = Memory::Read_U32(loopinfoAddr + 12);
track->loopinfo[i].fraction = Memory::Read_U32(loopinfoAddr + 16);
track->loopinfo[i].playCount = Memory::Read_U32(loopinfoAddr + 20);
if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) {
ERROR_LOG(Log::ME, "AnalyzeTrack: loop starts after it ends");
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
}
}
break;
}
case DATA_CHUNK_MAGIC:
{
bfoundData = true;
track->dataByteOffset = offset;
dataChunkSize = chunkSize;
if (track->fileSize < offset + chunkSize) {
WARN_LOG_REPORT(Log::ME, "Atrac data chunk extends beyond riff chunk");
track->fileSize = offset + chunkSize;
}
}
break;
}
offset += chunkSize;
}
if (track->codecType == 0) {
ERROR_LOG(Log::ME, "AnalyzeTrack: Could not detect codec");
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (!bfoundData) {
ERROR_LOG(Log::ME, "AnalyzeTrack: No data chunk found");
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
// set the loopStartSample_ and loopEndSample_ by loopinfo_
if (track->loopinfo.size() > 0) {
track->loopStartSample = track->loopinfo[0].startSample + track->FirstOffsetExtra() + sampleOffsetAdjust;
track->loopEndSample = track->loopinfo[0].endSample + track->FirstOffsetExtra() + sampleOffsetAdjust;
} else {
track->loopStartSample = -1;
track->loopEndSample = -1;
}
// if there is no correct endsample, try to guess it
if (track->endSample <= 0 && track->bytesPerFrame != 0) {
track->endSample = (dataChunkSize / track->bytesPerFrame) * track->SamplesPerFrame();
track->endSample -= track->FirstSampleOffsetFull();
}
track->endSample -= 1;
if (track->loopEndSample != -1 && track->loopEndSample > track->endSample + track->FirstSampleOffsetFull()) {
ERROR_LOG(Log::ME, "AnalyzeTrack: loop after end of data");
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
}
return 0;
}
int AnalyzeAA3Track(u32 addr, u32 size, u32 fileSize, Track *track) {
if (size < 10) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
// TODO: Make sure this validation is correct, more testing.
const u8 *buffer = Memory::GetPointer(addr);
if (buffer[0] != 'e' || buffer[1] != 'a' || buffer[2] != '3') {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
// It starts with an id3 header (replaced with ea3.) This is the size.
u32 tagSize = buffer[9] | (buffer[8] << 7) | (buffer[7] << 14) | (buffer[6] << 21);
if (size < tagSize + 36) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
// EA3 header starts at id3 header (10) + tagSize.
buffer = Memory::GetPointer(addr + 10 + tagSize);
if (buffer[0] != 'E' || buffer[1] != 'A' || buffer[2] != '3') {
ERROR_LOG(Log::ME, "AnalyzeAA3Track: Invalid EA3 magic bytes");
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
track->fileSize = fileSize;
// Based on FFmpeg's code.
u32 codecParams = buffer[33] | (buffer[34] << 8) | (buffer[35] << 16);
const u32 at3SampleRates[8] = { 32000, 44100, 48000, 88200, 96000, 0 };
switch (buffer[32]) {
case 0:
track->codecType = PSP_MODE_AT_3;
track->bytesPerFrame = (codecParams & 0x03FF) * 8;
track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 1024;
track->channels = 2;
track->jointStereo = (codecParams >> 17) & 1;
break;
case 1:
track->codecType = PSP_MODE_AT_3_PLUS;
track->bytesPerFrame = ((codecParams & 0x03FF) * 8) + 8;
track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 2048;
track->channels = (codecParams >> 10) & 7;
break;
case 3:
case 4:
case 5:
ERROR_LOG(Log::ME, "AnalyzeAA3Track: unsupported codec type %d", buffer[32]);
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
default:
ERROR_LOG(Log::ME, "AnalyzeAA3Track: invalid codec type %d", buffer[32]);
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
track->dataByteOffset = 10 + tagSize + 96;
track->firstSampleOffset = 0;
if (track->endSample < 0 && track->bytesPerFrame != 0) {
track->endSample = ((track->fileSize - track->dataByteOffset) / track->bytesPerFrame) * track->SamplesPerFrame();
}
track->endSample -= 1;
return 0;
}
int Atrac::GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const {
*endSample = track_.endSample;
*loopStartSample = track_.loopStartSample == -1 ? -1 : track_.loopStartSample - track_.FirstSampleOffsetFull();

View file

@ -37,10 +37,6 @@ struct AtracResetBufferInfo {
AtracSingleResetBufferInfo second;
};
#define AT3_MAGIC 0x0270
#define AT3_PLUS_MAGIC 0xFFFE
#define PSP_MODE_AT_3_PLUS 0x00001000
#define PSP_MODE_AT_3 0x00001001
const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1;
const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2;
@ -66,107 +62,8 @@ struct InputBuffer {
u32 fileoffset;
};
struct AtracLoopInfo {
int cuePointID;
int type;
int startSample;
int endSample;
int fraction;
int playCount;
};
class AudioDecoder;
// This is (mostly) constant info, once a track has been loaded.
struct Track {
// This both does and doesn't belong in Track - it's fixed for an Atrac instance. Oh well.
u32 codecType = 0;
// Size of the full track being streamed or played. Can be a lot larger than the in-memory buffer in the streaming modes.
u32 fileSize = 0;
// Not really used for much except queries, this keeps track of the bitrate of the track (kbps).
u32 bitrate = 64;
// Signifies whether to use a more efficient coding mode with less stereo separation. For our purposes, just metadata,
// not actually used in decoding.
int jointStereo = 0;
// Number of audio channels in the track.
u16 channels = 2;
// The size of an encoded frame in bytes.
u16 bytesPerFrame = 0;
// Byte offset of the first encoded frame in the input buffer. Note: Some samples may be skipped according to firstSampleOffset.
int dataByteOffset = 0;
// How many samples to skip from the beginning of a track when decoding.
// Actually, the real number is this added to FirstOffsetExtra(codecType). You can call
// FirstSampleOffset2() to get that.
// Some use of these offsets around the code seem to be inconsistent, sometimes the extra is included,
// sometimes not.
int firstSampleOffset = 0;
// Last sample number. Inclusive. Though, we made it so that in Analyze, it's exclusive in the file.
// Does not take firstSampleOffset into account.
int endSample = -1;
// NOTE: The below CAN be written.
// Loop configuration. The PSP only supports one loop but we store them all.
std::vector<AtracLoopInfo> loopinfo;
// The actual used loop offsets. These appear to be raw offsets, not taking FirstSampleOffset2() into account.
int loopStartSample = -1;
int loopEndSample = -1;
// Input frame size
int BytesPerFrame() const {
return bytesPerFrame;
}
inline int FirstOffsetExtra() const {
// These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45;
}
// Includes the extra offset. See firstSampleOffset comment above.
int FirstSampleOffsetFull() const {
return FirstOffsetExtra() + firstSampleOffset;
}
// Output frame size, different between the two supported codecs.
int SamplesPerFrame() const {
return codecType == PSP_MODE_AT_3_PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
}
int Bitrate() const {
int bitrate = (bytesPerFrame * 352800) / 1000;
if (codecType == PSP_MODE_AT_3_PLUS)
bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
else
bitrate = (bitrate + 511) >> 10;
return bitrate;
}
// This appears to be buggy, should probably include FirstOffsetExtra?
// Actually the units don't even make sense here.
int DecodePosBySample(int sample) const {
return (u32)(firstSampleOffset + sample / (int)SamplesPerFrame() * bytesPerFrame);
}
// This appears to be buggy, should probably include FirstOffsetExtra?
int FileOffsetBySample(int sample) const {
int offsetSample = sample + firstSampleOffset;
int frameOffset = offsetSample / (int)SamplesPerFrame();
return (u32)(dataByteOffset + bytesPerFrame + frameOffset * bytesPerFrame);
}
void DebugLog() const;
};
int AnalyzeAA3Track(u32 addr, u32 size, u32 filesize, Track *track);
int AnalyzeAtracTrack(u32 addr, u32 size, Track *track);
class AtracBase {
public:
virtual ~AtracBase();

View file

@ -503,7 +503,7 @@ u32 Atrac2::AddStreamDataSas(u32 bufPtr, u32 bytesToAdd) {
// Sol Trigger is the only game I know that uses this.
_dbg_assert_(false);
u8 *dest = Memory::GetPointerWrite(info.buffer);
u8 *dest = Memory::GetPointerWrite(sasBasePtr_ + sasReadOffset_);
memcpy(dest, Memory::GetPointer(bufPtr), bytesToAdd);
info.buffer += bytesToAdd;
info.streamDataByte += bytesToAdd;

View file

@ -635,9 +635,10 @@ static u32 sceAtracSetHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u32 b
}
Track track;
int ret = AnalyzeAtracTrack(buffer, readSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
if (track.codecType != atracContextTypes[atracID]) {
// TODO: Should this not change the buffer size?
@ -670,9 +671,10 @@ static u32 sceAtracSetData(int atracID, u32 buffer, u32 bufferSize) {
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
if (track.codecType != atracContextTypes[atracID]) {
// TODO: Should this not change the buffer size?
@ -697,9 +699,10 @@ static int sceAtracSetDataAndGetID(u32 buffer, int bufferSize) {
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
int atracID = AllocAndRegisterAtrac(track.codecType);
@ -722,9 +725,10 @@ static int sceAtracSetHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 buffer
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
int atracID = AllocAndRegisterAtrac(track.codecType);
@ -841,9 +845,10 @@ static int sceAtracSetMOutHalfwayBuffer(int atracID, u32 buffer, u32 readSize, u
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
ret = atrac->SetData(track, buffer, readSize, bufferSize, 1);
@ -865,9 +870,10 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
ret = atrac->SetData(track, buffer, bufferSize, bufferSize, 1);
@ -883,10 +889,12 @@ static u32 sceAtracSetMOutData(int atracID, u32 buffer, u32 bufferSize) {
// See note in above function.
static int sceAtracSetMOutDataAndGetID(u32 buffer, u32 bufferSize) {
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
if (track.channels != 1) {
return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data");
}
@ -910,10 +918,12 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
if (track.channels != 1) {
return hleReportError(Log::ME, SCE_ERROR_ATRAC_NOT_MONO, "not mono data");
}
@ -933,9 +943,10 @@ static int sceAtracSetMOutHalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bu
static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize, u32 metadataSizeAddr) {
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), bufferSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret);
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
int atracID = AllocAndRegisterAtrac(track.codecType);
@ -952,6 +963,32 @@ static int sceAtracSetAA3DataAndGetID(u32 buffer, u32 bufferSize, u32 fileSize,
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set aa3 data", 100);
}
static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) {
if (readSize > bufferSize) {
return hleLogError(Log::ME, SCE_ERROR_ATRAC_INCORRECT_READ_SIZE, "read size too large");
}
Track track;
std::string error;
int ret = AnalyzeAtracTrack(Memory::GetPointer(buffer), readSize, &track, &error);
if (ret < 0) {
return hleLogError(Log::ME, ret, "%s", error.c_str());
}
int atracID = AllocAndRegisterAtrac(track.codecType);
if (atracID < 0) {
return hleLogError(Log::ME, atracID, "no free ID");
}
ret = atracContexts[atracID]->SetData(track, buffer, readSize, bufferSize, 2);
if (ret < 0) {
UnregisterAndDeleteAtrac(atracID);
return hleLogError(Log::ME, ret);
}
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set data", 100);
}
// TODO: Should see if these are stored contiguously in memory somewhere, or if there really are
// individual allocations being used.
static u32 _sceAtracGetContextAddress(int atracID) {
@ -1048,31 +1085,6 @@ static int sceAtracLowLevelDecode(int atracID, u32 sourceAddr, u32 sourceBytesCo
return hleDelayResult(hleLogDebug(Log::ME, retval), "low level atrac decode data", atracDecodeDelay);
}
static int sceAtracSetAA3HalfwayBufferAndGetID(u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize) {
if (readSize > bufferSize) {
return hleLogError(Log::ME, SCE_ERROR_ATRAC_INCORRECT_READ_SIZE, "read size too large");
}
Track track;
int ret = AnalyzeAtracTrack(buffer, bufferSize, &track);
if (ret < 0) {
return hleLogError(Log::ME, ret);
}
int atracID = AllocAndRegisterAtrac(track.codecType);
if (atracID < 0) {
return hleLogError(Log::ME, atracID, "no free ID");
}
ret = atracContexts[atracID]->SetData(track, buffer, readSize, bufferSize, 2);
if (ret < 0) {
UnregisterAndDeleteAtrac(atracID);
return hleLogError(Log::ME, ret);
}
return hleDelayResult(hleLogDebug(Log::ME, atracID), "atrac set data", 100);
}
// These three are the external interface used by sceSas' AT3 integration.
// NOTE: There are special rules.

View file

@ -17,7 +17,8 @@
#pragma once
#include "sceAudiocodec.h"
#include "Core/HLE/sceAudiocodec.h"
#include "Core/Util/AtracTrack.h"
class PointerWrap;
@ -29,9 +30,6 @@ void __AtracShutdown();
void __AtracNotifyLoadModule(int version, u32 crc, u32 bssAddr, int bssSize);
void __AtracNotifyUnloadModule();
constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024
constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048
// The "state" member of SceAtracIdInfo.
enum AtracStatus : u8 {
ATRAC_STATUS_UNINITIALIZED = 0, // bad state
@ -68,6 +66,9 @@ const char *AtracStatusToString(AtracStatus status);
inline bool AtracStatusIsStreaming(AtracStatus status) {
return (status & ATRAC_STATUS_STREAMED_MASK) != 0;
}
inline bool AtracStatusIsNormal(AtracStatus status) {
return (int)status >= ATRAC_STATUS_ALL_DATA_LOADED && (int)status <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER;
}
struct SceAtracIdInfo {
s32 decodePos; // Sample position in the song that we'll next be decoding from.

300
Core/Util/AtracTrack.cpp Normal file
View file

@ -0,0 +1,300 @@
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Core/Util/AtracTrack.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/MemMap.h"
const int RIFF_CHUNK_MAGIC = 0x46464952;
const int RIFF_WAVE_MAGIC = 0x45564157;
const int FMT_CHUNK_MAGIC = 0x20746D66;
const int DATA_CHUNK_MAGIC = 0x61746164;
const int SMPL_CHUNK_MAGIC = 0x6C706D73;
const int FACT_CHUNK_MAGIC = 0x74636166;
static u16 Read16(const u8 *buffer, int offset) {
u16 value;
memcpy(&value, buffer + offset, sizeof(u16));
return value;
}
static u32 Read32(const u8 *buffer, int offset) {
u32 value;
memcpy(&value, buffer + offset, sizeof(u32));
return value;
}
int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error) {
// 72 is about the size of the minimum required data to even be valid.
// TODO: Validate stuff more.
if (Read32(buffer, 0) != RIFF_CHUNK_MAGIC) {
ERROR_LOG(Log::ME, "Couldn't find RIFF header");
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
struct RIFFFmtChunk {
u16 fmtTag;
u16 channels;
u32 samplerate;
u32 avgBytesPerSec;
u16 blockAlign;
};
u32 offset = 8;
track->firstSampleOffset = 0;
while (Read32(buffer, offset) != RIFF_WAVE_MAGIC) {
// Get the size preceding the magic.
int chunk = Read32(buffer, offset - 4);
// Round the chunk size up to the nearest 2.
offset += chunk + (chunk & 1);
if (offset + 12 > size) {
*error = StringFromFormat("%d too small for WAVE chunk at offset %d", size, offset);
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
if (Read32(buffer, offset) != RIFF_CHUNK_MAGIC) {
*error = "RIFF chunk did not contain WAVE";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
offset += 8;
}
offset += 4;
if (offset != 12) {
WARN_LOG(Log::ME, "RIFF chunk at offset: %d", offset);
}
// RIFF size excluding chunk header.
track->fileSize = Read32(buffer, offset - 8) + 8;
// Even if the RIFF size is too low, it may simply be incorrect. This works on real firmware.
u32 maxSize = std::max(track->fileSize, size);
bool bfoundData = false;
u32 dataChunkSize = 0;
int sampleOffsetAdjust = 0;
while (maxSize >= offset + 8 && !bfoundData) {
int chunkMagic = Read32(buffer, offset);
u32 chunkSize = Read32(buffer, offset + 4);
// Account for odd sized chunks.
if (chunkSize & 1) {
WARN_LOG(Log::ME, "RIFF chunk had uneven size");
}
chunkSize += (chunkSize & 1);
offset += 8;
if (chunkSize > maxSize - offset)
break;
switch (chunkMagic) {
case FMT_CHUNK_MAGIC:
{
if (track->codecType != 0) {
*error = "AnalyzeTrack: multiple fmt chunks is not valid";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
auto at3fmt = (const RIFFFmtChunk *)(buffer + offset);
if (chunkSize < 32 || (at3fmt->fmtTag == AT3_PLUS_MAGIC && chunkSize < 52)) {
*error = "AnalyzeTrack: fmt definition too small(%d)";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (at3fmt->fmtTag == AT3_MAGIC)
track->codecType = PSP_MODE_AT_3;
else if (at3fmt->fmtTag == AT3_PLUS_MAGIC)
track->codecType = PSP_MODE_AT_3_PLUS;
else {
*error = "AnalyzeTrack: invalid fmt magic: %04x";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->channels = at3fmt->channels;
if (track->channels != 1 && track->channels != 2) {
*error = "AnalyzeTrack: unsupported channel count %d";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (at3fmt->samplerate != 44100) {
*error = "AnalyzeTrack: unsupported sample rate %d";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->bitrate = at3fmt->avgBytesPerSec * 8;
track->bytesPerFrame = at3fmt->blockAlign;
if (track->bytesPerFrame == 0) {
*error = "invalid bytes per frame: %d";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
// TODO: There are some format specific bytes here which seem to have fixed values?
// Probably don't need them.
if (at3fmt->fmtTag == AT3_MAGIC) {
// This is the offset to the jointStereo_ field.
track->jointStereo = Read16(buffer, offset + 24);
// Then there are more fields here.
u16 unknown1_2 = Read16(buffer, offset + 30);
}
if (chunkSize > 16) {
// Read and format extra bytes as hexadecimal
std::string hex;
DataToHexString(buffer + offset + 16, chunkSize - 16, &hex, false);
DEBUG_LOG(Log::ME, "Additional chunk data (beyond 16 bytes): %s", hex.c_str());
}
break;
}
case FACT_CHUNK_MAGIC:
{
track->endSample = Read32(buffer, offset);
if (chunkSize >= 8) {
track->firstSampleOffset = Read32(buffer, offset + 4);
}
if (chunkSize >= 12) {
u32 largerOffset = Read32(buffer, offset + 8);
// Works, but "largerOffset"??
sampleOffsetAdjust = track->firstSampleOffset - largerOffset;
}
break;
}
case SMPL_CHUNK_MAGIC:
{
if (chunkSize < 32) {
*error = StringFromFormat("smpl chunk too small (%d)", chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
int checkNumLoops = Read32(buffer, offset + 28);
if (checkNumLoops != 0 && chunkSize < 36 + 20) {
*error = StringFromFormat("smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (checkNumLoops < 0) {
*error = StringFromFormat("bad checkNumLoops (%d)", checkNumLoops);
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
track->loopinfo.resize(checkNumLoops);
u32 loopinfoOffset = offset + 36;
// The PSP only cares about the first loop start and end, it seems.
// Most likely can skip the rest of this data, but it's not hurting anyone.
for (int i = 0; i < checkNumLoops && 36 + (u32)i < chunkSize; i++, loopinfoOffset += 24) {
track->loopinfo[i].cuePointID = Read32(buffer, loopinfoOffset + 0);
track->loopinfo[i].type = Read32(buffer, loopinfoOffset + 4);
track->loopinfo[i].startSample = Read32(buffer, loopinfoOffset + 8);
track->loopinfo[i].endSample = Read32(buffer, loopinfoOffset + 12);
track->loopinfo[i].fraction = Read32(buffer, loopinfoOffset + 16);
track->loopinfo[i].playCount = Read32(buffer, loopinfoOffset + 20);
if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) {
*error = "AnalyzeTrack: loop starts after it ends";
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
}
}
break;
}
case DATA_CHUNK_MAGIC:
{
bfoundData = true;
track->dataByteOffset = offset;
dataChunkSize = chunkSize;
if (track->fileSize < offset + chunkSize) {
WARN_LOG(Log::ME, "Atrac data chunk extends beyond riff chunk");
track->fileSize = offset + chunkSize;
}
}
break;
}
offset += chunkSize;
}
if (track->codecType == 0) {
*error = "Could not detect codec";
return SCE_ERROR_ATRAC_UNKNOWN_FORMAT;
}
if (!bfoundData) {
*error = "AnalyzeTrack: No data chunk found";
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
}
// set the loopStartSample_ and loopEndSample_ by loopinfo_
if (track->loopinfo.size() > 0) {
track->loopStartSample = track->loopinfo[0].startSample + track->FirstOffsetExtra() + sampleOffsetAdjust;
track->loopEndSample = track->loopinfo[0].endSample + track->FirstOffsetExtra() + sampleOffsetAdjust;
} else {
track->loopStartSample = -1;
track->loopEndSample = -1;
}
// if there is no correct endsample, try to guess it
if (track->endSample <= 0 && track->bytesPerFrame != 0) {
track->endSample = (dataChunkSize / track->bytesPerFrame) * track->SamplesPerFrame();
track->endSample -= track->FirstSampleOffsetFull();
}
track->endSample -= 1;
if (track->loopEndSample != -1 && track->loopEndSample > track->endSample + track->FirstSampleOffsetFull()) {
*error = "AnalyzeTrack: loop after end of data";
return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS;
}
return 0;
}
int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 fileSize, Track *track, std::string *error) {
if (size < 10) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
// TODO: Make sure this validation is correct, more testing.
if (buffer[0] != 'e' || buffer[1] != 'a' || buffer[2] != '3') {
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
// It starts with an id3 header (replaced with ea3.) This is the size.
u32 tagSize = buffer[9] | (buffer[8] << 7) | (buffer[7] << 14) | (buffer[6] << 21);
if (size < tagSize + 36) {
return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL;
}
// EA3 header starts at id3 header (10) + tagSize.
buffer = buffer + 10 + tagSize;
if (buffer[0] != 'E' || buffer[1] != 'A' || buffer[2] != '3') {
ERROR_LOG(Log::ME, "AnalyzeAA3Track: Invalid EA3 magic bytes");
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
track->fileSize = fileSize;
// Based on FFmpeg's code.
u32 codecParams = buffer[33] | (buffer[34] << 8) | (buffer[35] << 16);
const u32 at3SampleRates[8] = { 32000, 44100, 48000, 88200, 96000, 0 };
switch (buffer[32]) {
case 0:
track->codecType = PSP_MODE_AT_3;
track->bytesPerFrame = (codecParams & 0x03FF) * 8;
track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 1024;
track->channels = 2;
track->jointStereo = (codecParams >> 17) & 1;
break;
case 1:
track->codecType = PSP_MODE_AT_3_PLUS;
track->bytesPerFrame = ((codecParams & 0x03FF) * 8) + 8;
track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 2048;
track->channels = (codecParams >> 10) & 7;
break;
case 3:
case 4:
case 5:
ERROR_LOG(Log::ME, "AnalyzeAA3Track: unsupported codec type %d", buffer[32]);
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
default:
ERROR_LOG(Log::ME, "AnalyzeAA3Track: invalid codec type %d", buffer[32]);
return SCE_ERROR_ATRAC_AA3_INVALID_DATA;
}
track->dataByteOffset = 10 + tagSize + 96;
track->firstSampleOffset = 0;
if (track->endSample < 0 && track->bytesPerFrame != 0) {
track->endSample = ((track->fileSize - track->dataByteOffset) / track->bytesPerFrame) * track->SamplesPerFrame();
}
track->endSample -= 1;
return 0;
}

114
Core/Util/AtracTrack.h Normal file
View file

@ -0,0 +1,114 @@
#pragma once
#include "Common/CommonTypes.h"
#include <vector>
#include <string>
// Atrac file parsing.
#define AT3_MAGIC 0x0270
#define AT3_PLUS_MAGIC 0xFFFE
#define PSP_MODE_AT_3_PLUS 0x00001000
#define PSP_MODE_AT_3 0x00001001
constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024
constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048
struct AtracLoopInfo {
int cuePointID;
int type;
int startSample;
int endSample;
int fraction;
int playCount;
};
// This is (mostly) constant info, once a track has been loaded.
struct Track {
// This both does and doesn't belong in Track - it's fixed for an Atrac instance. Oh well.
u32 codecType = 0;
// Size of the full track being streamed or played. Can be a lot larger than the in-memory buffer in the streaming modes.
u32 fileSize = 0;
// Not really used for much except queries, this keeps track of the bitrate of the track (kbps).
u32 bitrate = 64;
// Signifies whether to use a more efficient coding mode with less stereo separation. For our purposes, just metadata,
// not actually used in decoding.
int jointStereo = 0;
// Number of audio channels in the track.
u16 channels = 2;
// The size of an encoded frame in bytes.
u16 bytesPerFrame = 0;
// Byte offset of the first encoded frame in the input buffer. Note: Some samples may be skipped according to firstSampleOffset.
int dataByteOffset = 0;
// How many samples to skip from the beginning of a track when decoding.
// Actually, the real number is this added to FirstOffsetExtra(codecType). You can call
// FirstSampleOffset2() to get that.
// Some use of these offsets around the code seem to be inconsistent, sometimes the extra is included,
// sometimes not.
int firstSampleOffset = 0;
// Last sample number. Inclusive. Though, we made it so that in Analyze, it's exclusive in the file.
// Does not take firstSampleOffset into account.
int endSample = -1;
// NOTE: The below CAN be written.
// Loop configuration. The PSP only supports one loop but we store them all.
std::vector<AtracLoopInfo> loopinfo;
// The actual used loop offsets. These appear to be raw offsets, not taking FirstSampleOffset2() into account.
int loopStartSample = -1;
int loopEndSample = -1;
// Input frame size
int BytesPerFrame() const {
return bytesPerFrame;
}
inline int FirstOffsetExtra() const {
// These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
return codecType == PSP_MODE_AT_3_PLUS ? 0x170 : 0x45;
}
// Includes the extra offset. See firstSampleOffset comment above.
int FirstSampleOffsetFull() const {
return FirstOffsetExtra() + firstSampleOffset;
}
// Output frame size, different between the two supported codecs.
int SamplesPerFrame() const {
return codecType == PSP_MODE_AT_3_PLUS ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
}
int Bitrate() const {
int bitrate = (bytesPerFrame * 352800) / 1000;
if (codecType == PSP_MODE_AT_3_PLUS)
bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
else
bitrate = (bitrate + 511) >> 10;
return bitrate;
}
// This appears to be buggy, should probably include FirstOffsetExtra?
// Actually the units don't even make sense here.
int DecodePosBySample(int sample) const {
return (u32)(firstSampleOffset + sample / (int)SamplesPerFrame() * bytesPerFrame);
}
// This appears to be buggy, should probably include FirstOffsetExtra?
int FileOffsetBySample(int sample) const {
int offsetSample = sample + firstSampleOffset;
int frameOffset = offsetSample / (int)SamplesPerFrame();
return (u32)(dataByteOffset + bytesPerFrame + frameOffset * bytesPerFrame);
}
void DebugLog() const;
};
int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 filesize, Track *track, std::string *error);
int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error);

View file

@ -326,6 +326,9 @@ bool MainUI::HandleCustomEvent(QEvent *e) {
case BrowseFileType::ZIP:
filter = "ZIP files (*.zip)";
break;
case BrowseFileType::ATRAC3:
filter = "AT3 files (*.at3)";
break;
case BrowseFileType::ANY:
break;
}

View file

@ -90,6 +90,9 @@ void DarwinFileSystemServices::presentDirectoryPanel(
case BrowseFileType::SOUND_EFFECT:
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"wav"]];
break;
case BrowseFileType::ATRAC3:
[panel setAllowedFileTypes:[NSArray arrayWithObject:@"at3"]];
break;
default:
break;
}

View file

@ -33,6 +33,7 @@
#include "Core/HLE/sceNetAdhocMatching.h"
#include "Common/System/Request.h"
#include "Core/Util/AtracTrack.h"
#include "Core/HLE/sceAtrac.h"
#include "Core/HLE/sceAudio.h"
#include "Core/HLE/sceAudiocodec.h"
@ -1000,7 +1001,7 @@ void DrawAudioDecodersView(ImConfig &cfg, ImControl &control) {
ImGui::TableNextColumn();
ImGui::Text("in:%d out:%d", ctx->Channels(), ctx->GetOutputChannels());
ImGui::TableNextColumn();
if (ctx->BufferState() != ATRAC_STATUS_LOW_LEVEL) {
if (AtracStatusIsNormal(ctx->BufferState())) {
int pos;
ctx->GetNextDecodePosition(&pos);
ImGui::Text("%d", pos);
@ -1008,7 +1009,7 @@ void DrawAudioDecodersView(ImConfig &cfg, ImControl &control) {
ImGui::TextUnformatted("N/A");
}
ImGui::TableNextColumn();
if (ctx->BufferState() <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER) {
if (AtracStatusIsNormal(ctx->BufferState())) {
ImGui::Text("%d", ctx->RemainingFrames());
} else {
ImGui::TextUnformatted("N/A");
@ -1479,6 +1480,44 @@ static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImContro
ImGui::End();
}
void ImAtracToolWindow::Draw(ImConfig &cfg) {
if (!ImGui::Begin("Atrac Tool", &cfg.atracToolOpen) || !g_symbolMap) {
ImGui::End();
return;
}
ImGui::InputText("File", atracPath_, sizeof(atracPath_));
ImGui::SameLine();
if (ImGui::Button("Choose...")) {
System_BrowseForFile(cfg.requesterToken, "Choose AT3 file", BrowseFileType::ATRAC3, [&](const std::string &filename, int) {
truncate_cpy(atracPath_, filename);
}, nullptr);
}
if (strlen(atracPath_) > 0) {
if (ImGui::Button("Load")) {
track_.reset(new Track());
std::string data;
if (File::ReadBinaryFileToString(Path(atracPath_), &data)) {
AnalyzeAtracTrack((const u8 *)data.data(), (u32)data.size(), track_.get(), &error_);
}
}
}
if (track_.get() != 0) {
ImGui::Text("Codec: %s", track_->codecType != PSP_CODEC_AT3 ? "at3+" : "at3");
ImGui::Text("Bitrate: %d kbps Channels: %d", track_->Bitrate(), track_->channels);
ImGui::Text("Frame size in bytes: %d Output frame in samples: %d", track_->BytesPerFrame(), track_->SamplesPerFrame());
ImGui::Text("First valid sample: %08x", track_->FirstSampleOffsetFull());
}
if (!error_.empty()) {
ImGui::TextUnformatted(error_.c_str());
}
ImGui::End();
}
void DrawHLEModules(ImConfig &config) {
if (!ImGui::Begin("HLE Modules", &config.hleModulesOpen)) {
ImGui::End();
@ -1679,6 +1718,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen);
ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen);
ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen);
ImGui::MenuItem("Atrac Tool", nullptr, &cfg_.atracToolOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Misc")) {
@ -1761,6 +1801,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
DrawHLEModules(cfg_);
}
if (cfg_.atracToolOpen) {
atracToolWindow_.Draw(cfg_);
}
if (cfg_.framebuffersOpen) {
DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
}

View file

@ -150,6 +150,7 @@ struct ImConfig {
bool sasAudioOpen;
bool logConfigOpen;
bool utilityModulesOpen;
bool atracToolOpen;
bool memViewOpen[4];
// HLE explorer settings
@ -176,6 +177,7 @@ struct ImConfig {
bool sasShowAllVoices = false;
// We use a separate ini file from the main PPSSPP config.
void LoadConfig(const Path &iniFile);
void SaveConfig(const Path &iniFile);
@ -183,6 +185,16 @@ struct ImConfig {
void SyncConfig(IniFile *ini, bool save);
};
struct Track;
class ImAtracToolWindow {
public:
void Draw(ImConfig &cfg);
char atracPath_[1024]{};
std::unique_ptr<Track> track_;
std::string error_;
};
enum class ImCmd {
NONE = 0,
TRIGGER_FIND_POPUP,
@ -233,6 +245,7 @@ private:
ImStructViewer structViewer_;
ImGePixelViewerWindow pixelViewer_;
ImMemDumpWindow memDumpWindow_;
ImAtracToolWindow atracToolWindow_;
ImSnapshotState newSnapshot_;
ImSnapshotState snapshot_;

View file

@ -333,6 +333,7 @@
<ClInclude Include="..\..\Core\ThreadEventQueue.h" />
<ClInclude Include="..\..\Core\ThreadPools.h" />
<ClInclude Include="..\..\Core\TiltEventProcessor.h" />
<ClInclude Include="..\..\Core\Util\AtracTrack.h" />
<ClInclude Include="..\..\Core\Util\GameDB.h" />
<ClInclude Include="..\..\Core\Util\MemStick.h" />
<ClInclude Include="..\..\Core\Util\PortManager.h" />
@ -632,6 +633,7 @@
<ClCompile Include="..\..\Core\System.cpp" />
<ClCompile Include="..\..\Core\ThreadPools.cpp" />
<ClCompile Include="..\..\Core\TiltEventProcessor.cpp" />
<ClCompile Include="..\..\Core\Util\AtracTrack.cpp" />
<ClCompile Include="..\..\Core\Util\GameDB.cpp" />
<ClCompile Include="..\..\Core\Util\MemStick.cpp" />
<ClCompile Include="..\..\Core\Util\PortManager.cpp" />
@ -1040,4 +1042,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -1235,6 +1235,9 @@
<ClCompile Include="..\..\Core\HLE\SocketManager.cpp">
<Filter>HLE</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\Util\AtracTrack.cpp">
<Filter>Util</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -1954,10 +1957,13 @@
<ClInclude Include="..\..\Core\HLE\SocketManager.h">
<Filter>HLE</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\Util\AtracTrack.h">
<Filter>Util</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\ext\gason\LICENSE">
<Filter>Ext\gason</Filter>
</None>
</ItemGroup>
</Project>
</Project>

View file

@ -517,7 +517,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
supportedExtensions = { ".zip" };
break;
case BrowseFileType::SYMBOL_MAP:
supportedExtensions = { ".map" };
supportedExtensions = { ".ppmap" };
break;
case BrowseFileType::DB:
supportedExtensions = { ".db" };
@ -525,6 +525,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
case BrowseFileType::SOUND_EFFECT:
supportedExtensions = { ".wav", ".mp3" };
break;
case BrowseFileType::ATRAC3:
supportedExtensions = { ".at3" };
break;
case BrowseFileType::ANY:
// 'ChooseFile' will added '*' by default when there are no extensions assigned
break;

View file

@ -537,6 +537,8 @@ static std::wstring MakeWindowsFilter(BrowseFileType type) {
return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||");
case BrowseFileType::SYMBOL_MAP:
return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||");
case BrowseFileType::ATRAC3:
return FinalizeFilter(L"ATRAC3/3+ files (*.at3)|*.at3|All files (*.*)|*.*||");
case BrowseFileType::ANY:
return FinalizeFilter(L"All files (*.*)|*.*||");
default:

View file

@ -753,6 +753,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/MIPS/JitCommon/JitCommon.cpp \
$(SRC)/Core/MIPS/JitCommon/JitBlockCache.cpp \
$(SRC)/Core/MIPS/JitCommon/JitState.cpp \
$(SRC)/Core/Util/AtracTrack.cpp \
$(SRC)/Core/Util/AudioFormat.cpp \
$(SRC)/Core/Util/MemStick.cpp \
$(SRC)/Core/Util/PortManager.cpp \

View file

@ -690,8 +690,8 @@ SOURCES_CXX += \
$(COREDIR)/Font/PGF.cpp \
$(COREDIR)/HLE/HLE.cpp \
$(COREDIR)/HLE/KUBridge.cpp \
$(COREDIR)/HLE/NetInetConstants.cpp \
$(COREDIR)/HLE/SocketManager.cpp \
$(COREDIR)/HLE/NetInetConstants.cpp \
$(COREDIR)/HLE/SocketManager.cpp \
$(COREDIR)/HLE/Plugins.cpp \
$(COREDIR)/HLE/sceSha256.cpp \
$(COREDIR)/HLE/sceSircs.cpp \
@ -826,6 +826,7 @@ SOURCES_CXX += \
$(COREDIR)/Screenshot.cpp \
$(COREDIR)/System.cpp \
$(COREDIR)/ThreadPools.cpp \
$(COREDIR)/Util/AtracTrack.cpp \
$(COREDIR)/Util/BlockAllocator.cpp \
$(COREDIR)/Util/MemStick.cpp \
$(COREDIR)/Util/PPGeDraw.cpp \