feat(audio): formalize runtime effects and wav loading

This commit is contained in:
2026-04-19 00:26:28 +08:00
parent 8164baba0a
commit d2017b251c
13 changed files with 518 additions and 254 deletions

View File

@@ -13,52 +13,89 @@ struct WAVHeader {
uint32_t channels = 2;
uint32_t bitsPerSample = 16;
uint32_t dataSize = 0;
uint32_t dataOffset = 44;
uint32_t dataOffset = 0;
};
uint16_t ReadLE16(const uint8_t* data) {
return static_cast<uint16_t>(data[0]) |
(static_cast<uint16_t>(data[1]) << 8);
}
uint32_t ReadLE32(const uint8_t* data) {
return static_cast<uint32_t>(data[0]) |
(static_cast<uint32_t>(data[1]) << 8) |
(static_cast<uint32_t>(data[2]) << 16) |
(static_cast<uint32_t>(data[3]) << 24);
}
bool ParseWAVHeader(const uint8_t* data, size_t size, WAVHeader& header) {
if (size < 44) {
if (size < 12) {
return false;
}
if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') {
if (std::memcmp(data, "RIFF", 4) != 0) {
return false;
}
if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') {
if (std::memcmp(data + 8, "WAVE", 4) != 0) {
return false;
}
if (data[12] != 'f' || data[13] != 'm' || data[14] != 't' || data[15] != ' ') {
bool foundFormatChunk = false;
bool foundDataChunk = false;
size_t offset = 12;
while (offset + 8 <= size) {
const uint8_t* chunk = data + offset;
const uint32_t chunkSize = ReadLE32(chunk + 4);
const size_t chunkDataOffset = offset + 8;
if (chunkDataOffset > size || chunkSize > size - chunkDataOffset) {
return false;
}
if (std::memcmp(chunk, "fmt ", 4) == 0) {
if (chunkSize < 16) {
return false;
}
const uint16_t audioFormat = ReadLE16(chunk + 8);
if (audioFormat != 1) {
return false;
}
header.channels = ReadLE16(chunk + 10);
header.sampleRate = ReadLE32(chunk + 12);
header.bitsPerSample = ReadLE16(chunk + 22);
foundFormatChunk = true;
} else if (std::memcmp(chunk, "data", 4) == 0) {
header.dataOffset = static_cast<uint32_t>(chunkDataOffset);
header.dataSize = chunkSize;
foundDataChunk = true;
}
offset = chunkDataOffset + chunkSize + (chunkSize & 1u);
}
if (!foundFormatChunk || !foundDataChunk) {
return false;
}
uint32_t subchunk1Size = *reinterpret_cast<const uint32_t*>(&data[16]);
if (subchunk1Size < 16) {
if (header.channels == 0 || header.sampleRate == 0 || header.bitsPerSample == 0) {
return false;
}
uint16_t audioFormat = *reinterpret_cast<const uint16_t*>(&data[20]);
if (audioFormat != 1) {
if ((header.bitsPerSample % 8u) != 0u) {
return false;
}
header.channels = *reinterpret_cast<const uint16_t*>(&data[22]);
header.sampleRate = *reinterpret_cast<const uint32_t*>(&data[24]);
header.bitsPerSample = *reinterpret_cast<const uint16_t*>(&data[34]);
if (data[36] != 'd' || data[37] != 'a' || data[38] != 't' || data[39] != 'a') {
const uint32_t bytesPerFrame =
static_cast<uint32_t>(header.channels) * (header.bitsPerSample / 8u);
if (bytesPerFrame == 0 || (header.dataSize % bytesPerFrame) != 0u) {
return false;
}
header.dataSize = *reinterpret_cast<const uint32_t*>(&data[40]);
header.dataOffset = 44;
if (header.dataOffset + header.dataSize > size) {
return false;
}
return true;
return header.dataOffset + header.dataSize <= size;
}
} // namespace
@@ -70,18 +107,12 @@ AudioLoader::~AudioLoader() = default;
Containers::Array<Containers::String> AudioLoader::GetSupportedExtensions() const {
Containers::Array<Containers::String> extensions;
extensions.PushBack("wav");
extensions.PushBack("ogg");
extensions.PushBack("mp3");
extensions.PushBack("flac");
extensions.PushBack("aiff");
extensions.PushBack("aif");
return extensions;
}
bool AudioLoader::CanLoad(const Containers::String& path) const {
Containers::String ext = GetExtension(path);
return ext == "wav" || ext == "ogg" || ext == "mp3" ||
ext == "flac" || ext == "aiff" || ext == "aif";
return ext == "wav";
}
LoadResult AudioLoader::Load(const Containers::String& path, const ImportSettings* settings) {
@@ -96,18 +127,21 @@ LoadResult AudioLoader::Load(const Containers::String& path, const ImportSetting
audioClip->m_guid = ResourceGUID::Generate(path);
AudioFormat format = DetectAudioFormat(path, data);
if (format == AudioFormat::WAV) {
if (!ParseWAVData(data, audioClip)) {
delete audioClip;
return LoadResult("Failed to parse WAV data");
}
if (format != AudioFormat::WAV) {
delete audioClip;
return LoadResult("Unsupported audio format: " + path);
}
if (!ParseWAVData(data, audioClip)) {
delete audioClip;
return LoadResult("Failed to parse WAV data");
}
audioClip->SetAudioFormat(format);
audioClip->SetAudioData(data);
audioClip->m_isValid = true;
audioClip->m_memorySize = sizeof(AudioClip) + audioClip->m_name.Length() +
audioClip->m_path.Length() + audioClip->GetAudioData().Size();
audioClip->m_path.Length() + audioClip->GetPCMData().Size();
return LoadResult(audioClip);
}
@@ -117,6 +151,10 @@ ImportSettings* AudioLoader::GetDefaultSettings() const {
}
bool AudioLoader::ParseWAVData(const Containers::Array<Core::uint8>& data, AudioClip* audioClip) {
if (audioClip == nullptr) {
return false;
}
WAVHeader header;
if (!ParseWAVHeader(data.Data(), data.Size(), header)) {
return false;
@@ -126,10 +164,15 @@ bool AudioLoader::ParseWAVData(const Containers::Array<Core::uint8>& data, Audio
audioClip->SetChannels(header.channels);
audioClip->SetBitsPerSample(header.bitsPerSample);
uint32_t bytesPerSample = header.bitsPerSample / 8;
uint32_t totalSamples = header.dataSize / (bytesPerSample * header.channels);
float duration = static_cast<float>(totalSamples) / (header.sampleRate * header.channels);
audioClip->SetDuration(duration);
Containers::Array<Core::uint8> pcmData;
pcmData.ResizeUninitialized(header.dataSize);
if (header.dataSize > 0) {
std::memcpy(
pcmData.Data(),
data.Data() + header.dataOffset,
header.dataSize);
}
audioClip->SetPCMData(pcmData);
return true;
}