diff --git a/Core/WaveFile.cpp b/Core/WaveFile.cpp index f789a7807e..038c66e80f 100644 --- a/Core/WaveFile.cpp +++ b/Core/WaveFile.cpp @@ -1,7 +1,7 @@ // Copyright 2008 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. -#ifndef MOBILE_DEVICE + #include #include "Core/WaveFile.h" @@ -105,4 +105,3 @@ void WaveFileWriter::AddStereoSamples(const short* sample_data, u32 count) file.WriteBytes(sample_data, count * 4); audio_size += count * 4; } -#endif diff --git a/Core/WaveFile.h b/Core/WaveFile.h index 39bcf483a4..c2ba17d802 100644 --- a/Core/WaveFile.h +++ b/Core/WaveFile.h @@ -11,7 +11,6 @@ // --------------------------------------------------------------------------------- #pragma once -#ifndef MOBILE_DEVICE #include #include @@ -40,6 +39,3 @@ private: void Write(u32 value); void Write4(const char* ptr); }; - -#endif - diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index defc30068a..89e173c510 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -4,6 +4,7 @@ #include "base/logging.h" #include "base/timeutil.h" #include "file/chunk_file.h" +#include "file/vfs.h" #include "Common/CommonTypes.h" #include "Core/HW/SimpleAudioDec.h" @@ -13,6 +14,152 @@ #include "Core/Config.h" #include "UI/BackgroundAudio.h" +struct WavData { + int num_channels = -1, sample_rate = -1, numFrames = -1, samplesPerSec = -1, avgBytesPerSec = -1, Nothing = -1; + int raw_offset_loop_start_ = 0; + int raw_offset_loop_end_ = 0; + int loop_start_offset_ = 0; + int loop_end_offset_ = 0; + int codec = 0; + int raw_bytes_per_frame_ = 0; + uint8_t *raw_data_ = nullptr; + int raw_data_size_ = 0; + u8 at3_extradata[16]; + + void Read(RIFFReader &riff); + + ~WavData() { + free(raw_data_); + raw_data_ = nullptr; + } +}; + +void WavData::Read(RIFFReader &file_) { + // If we have no loop start info, we'll just loop the entire audio. + raw_offset_loop_start_ = 0; + raw_offset_loop_end_ = 0; + + if (file_.Descend('RIFF')) { + file_.ReadInt(); //get past 'WAVE' + if (file_.Descend('fmt ')) { //enter the format chunk + int temp = file_.ReadInt(); + int format = temp & 0xFFFF; + switch (format) { + case 0xFFFE: + codec = PSP_CODEC_AT3PLUS; + break; + case 0x270: + codec = PSP_CODEC_AT3; + break; + case 1: + // Raw wave data, no codec + codec = 0; + break; + default: + ERROR_LOG(SCEAUDIO, "Unexpected wave format %04x", format); + return; + } + + num_channels = temp >> 16; + + samplesPerSec = file_.ReadInt(); + avgBytesPerSec = file_.ReadInt(); + + temp = file_.ReadInt(); + raw_bytes_per_frame_ = temp & 0xFFFF; + Nothing = temp >> 16; + + // Not currently used, but part of the format. + (void)avgBytesPerSec; + (void)Nothing; + + if (codec == PSP_CODEC_AT3) { + // The first two bytes are actually not a useful part of the extradata. + // We already read 16 bytes, so make sure there's enough left. + if (file_.GetCurrentChunkSize() >= 32) { + file_.ReadData(at3_extradata, 16); + } else { + memset(at3_extradata, 0, sizeof(at3_extradata)); + } + } + file_.Ascend(); + // ILOG("got fmt data: %i", samplesPerSec); + } else { + ELOG("Error - no format chunk in wav"); + file_.Ascend(); + return; + } + + if (file_.Descend('smpl')) { + std::vector smplData; + smplData.resize(file_.GetCurrentChunkSize()); + file_.ReadData(&smplData[0], (int)smplData.size()); + + int numLoops = *(int *)&smplData[28]; + struct AtracLoopInfo { + int cuePointID; + int type; + int startSample; + int endSample; + int fraction; + int playCount; + }; + + if (numLoops > 0 && smplData.size() >= 36 + sizeof(AtracLoopInfo) * numLoops) { + AtracLoopInfo *loops = (AtracLoopInfo *)&smplData[36]; + int samplesPerFrame = codec == PSP_CODEC_AT3PLUS ? 2048 : 1024; + + for (int i = 0; i < numLoops; ++i) { + // Only seen forward loops, so let's ignore others. + if (loops[i].type != 0) + continue; + + // We ignore loop interpolation (fraction) and play count for now. + raw_offset_loop_start_ = (loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame_; + loop_start_offset_ = loops[i].startSample % samplesPerFrame; + raw_offset_loop_end_ = (loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame_; + loop_end_offset_ = loops[i].endSample % samplesPerFrame; + + if (loops[i].playCount == 0) { + // This was an infinite loop, so ignore the rest. + // In practice, there's usually only one and it's usually infinite. + break; + } + } + } + + file_.Ascend(); + } + + // enter the data chunk + if (file_.Descend('data')) { + int numBytes = file_.GetCurrentChunkSize(); + numFrames = numBytes / raw_bytes_per_frame_; // numFrames + + raw_data_ = (uint8_t *)malloc(numBytes); + raw_data_size_ = numBytes; + if (num_channels == 1 || num_channels == 2) { + file_.ReadData(raw_data_, numBytes); + } else { + ELOG("Error - bad blockalign or channels"); + free(raw_data_); + raw_data_ = nullptr; + return; + } + file_.Ascend(); + } else { + ELOG("Error - no data chunk in wav"); + file_.Ascend(); + return; + } + file_.Ascend(); + } else { + ELOG("Could not descend into RIFF file."); + return; + } + sample_rate = samplesPerSec; +} + // Really simple looping in-memory AT3 player that also takes care of reading the file format. // Turns out that AT3 files used for this are modified WAVE files so fairly easy to parse. class AT3PlusReader { @@ -22,163 +169,39 @@ public: // Normally 8k but let's be safe. buffer_ = new short[32 * 1024]; - int codec = PSP_CODEC_AT3PLUS; - u8 at3_extradata[16]; + skip_next_samples_ = 0; - int num_channels, sample_rate, numFrames, samplesPerSec, avgBytesPerSec, Nothing; - if (file_.Descend('RIFF')) { - file_.ReadInt(); //get past 'WAVE' - if (file_.Descend('fmt ')) { //enter the format chunk - int temp = file_.ReadInt(); - int format = temp & 0xFFFF; - switch (format) { - case 0xFFFE: - codec = PSP_CODEC_AT3PLUS; - break; - case 0x270: - codec = PSP_CODEC_AT3; - break; - default: - ERROR_LOG(SCEAUDIO, "Unexpected SND0.AT3 format %04x", format); - return; - } + wave_.Read(file_); - num_channels = temp >> 16; - - samplesPerSec = file_.ReadInt(); - avgBytesPerSec = file_.ReadInt(); - - temp = file_.ReadInt(); - raw_bytes_per_frame_ = temp & 0xFFFF; - Nothing = temp >> 16; - - // Not currently used, but part of the format. - (void)avgBytesPerSec; - (void)Nothing; - - if (codec == PSP_CODEC_AT3) { - // The first two bytes are actually not a useful part of the extradata. - // We already read 16 bytes, so make sure there's enough left. - if (file_.GetCurrentChunkSize() >= 32) { - file_.ReadData(at3_extradata, 16); - } else { - memset(at3_extradata, 0, sizeof(at3_extradata)); - } - } - file_.Ascend(); - // ILOG("got fmt data: %i", samplesPerSec); - } else { - ELOG("Error - no format chunk in wav"); - file_.Ascend(); - return; - } - - // If we have no loop info, we'll just loop the entire audio. - raw_offset_loop_start_ = 0; - raw_offset_loop_end_ = 0; - skip_next_samples_ = 0; - - if (file_.Descend('smpl')) { - std::vector smplData; - smplData.resize(file_.GetCurrentChunkSize()); - file_.ReadData(&smplData[0], (int)smplData.size()); - - int numLoops = *(int *)&smplData[28]; - struct AtracLoopInfo { - int cuePointID; - int type; - int startSample; - int endSample; - int fraction; - int playCount; - }; - - if (numLoops > 0 && smplData.size() >= 36 + sizeof(AtracLoopInfo) * numLoops) { - AtracLoopInfo *loops = (AtracLoopInfo *)&smplData[36]; - int samplesPerFrame = codec == PSP_CODEC_AT3PLUS ? 2048 : 1024; - - for (int i = 0; i < numLoops; ++i) { - // Only seen forward loops, so let's ignore others. - if (loops[i].type != 0) - continue; - - // We ignore loop interpolation (fraction) and play count for now. - raw_offset_loop_start_ = (loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame_; - loop_start_offset_ = loops[i].startSample % samplesPerFrame; - raw_offset_loop_end_ = (loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame_; - loop_end_offset_ = loops[i].endSample % samplesPerFrame; - - if (loops[i].playCount == 0) { - // This was an infinite loop, so ignore the rest. - // In practice, there's usually only one and it's usually infinite. - break; - } - } - } - - file_.Ascend(); - } - - if (file_.Descend('data')) { //enter the data chunk - int numBytes = file_.GetCurrentChunkSize(); - numFrames = numBytes / raw_bytes_per_frame_; // numFrames - - raw_data_ = (uint8_t *)malloc(numBytes); - raw_data_size_ = numBytes; - if (num_channels == 1 || num_channels == 2) { - file_.ReadData(raw_data_, numBytes); - } else { - ELOG("Error - bad blockalign or channels"); - free(raw_data_); - raw_data_ = nullptr; - return; - } - file_.Ascend(); - } else { - ELOG("Error - no data chunk in wav"); - file_.Ascend(); - return; - } - file_.Ascend(); - } else { - ELOG("Could not descend into RIFF file. Data size=%d", (int32_t)data.size()); - return; + decoder_ = new SimpleAudio(wave_.codec, wave_.sample_rate, wave_.num_channels); + if (wave_.codec == PSP_CODEC_AT3) { + decoder_->SetExtraData(&wave_.at3_extradata[2], 14, wave_.raw_bytes_per_frame_); } - sample_rate = samplesPerSec; - decoder_ = new SimpleAudio(codec, sample_rate, num_channels); - if (codec == PSP_CODEC_AT3) { - decoder_->SetExtraData(&at3_extradata[2], 14, raw_bytes_per_frame_); - } - ILOG("read ATRAC, frames: %i, rate %i", numFrames, sample_rate); + ILOG("read ATRAC, frames: %d, rate %d", wave_.numFrames, wave_.sample_rate); } ~AT3PlusReader() { - } - - void Shutdown() { - free(raw_data_); - raw_data_ = nullptr; delete[] buffer_; buffer_ = nullptr; delete decoder_; decoder_ = nullptr; } - bool IsOK() { return raw_data_ != nullptr; } + bool IsOK() { return wave_.raw_data_ != nullptr; } bool Read(int *buffer, int len) { - if (!raw_data_) + if (!wave_.raw_data_) return false; while (bgQueue.size() < (size_t)(len * 2)) { int outBytes = 0; - decoder_->Decode(raw_data_ + raw_offset_, raw_bytes_per_frame_, (uint8_t *)buffer_, &outBytes); + decoder_->Decode(wave_.raw_data_ + raw_offset_, wave_.raw_bytes_per_frame_, (uint8_t *)buffer_, &outBytes); if (!outBytes) return false; - if (raw_offset_loop_end_ != 0 && raw_offset_ == raw_offset_loop_end_) { + if (wave_.raw_offset_loop_end_ != 0 && raw_offset_ == wave_.raw_offset_loop_end_) { // Only take the remaining bytes, but convert to stereo s16. - outBytes = std::min(outBytes, loop_end_offset_ * 4); + outBytes = std::min(outBytes, wave_.loop_end_offset_ * 4); } int start = skip_next_samples_; @@ -188,16 +211,16 @@ public: bgQueue.push(buffer_[i]); } - if (raw_offset_loop_end_ != 0 && raw_offset_ == raw_offset_loop_end_) { + if (wave_.raw_offset_loop_end_ != 0 && raw_offset_ == wave_.raw_offset_loop_end_) { // Time to loop. Account for the addition below. - raw_offset_ = raw_offset_loop_start_ - raw_bytes_per_frame_; + raw_offset_ = wave_.raw_offset_loop_start_ - wave_.raw_bytes_per_frame_; // This time we're counting each stereo sample. - skip_next_samples_ = loop_start_offset_ * 2; + skip_next_samples_ = wave_.loop_start_offset_ * 2; } // Handle loops when there's no loop info. - raw_offset_ += raw_bytes_per_frame_; - if (raw_offset_ >= raw_data_size_) { + raw_offset_ += wave_.raw_bytes_per_frame_; + if (raw_offset_ >= wave_.raw_data_size_) { raw_offset_ = 0; } } @@ -210,14 +233,10 @@ public: private: RIFFReader file_; - uint8_t *raw_data_ = nullptr; - int raw_data_size_ = 0; + + WavData wave_; + int raw_offset_ = 0; - int raw_bytes_per_frame_; - int raw_offset_loop_start_ = 0; - int raw_offset_loop_end_ = 0; - int loop_start_offset_ = 0; - int loop_end_offset_ = 0; int skip_next_samples_ = 0; FixedSizeQueue bgQueue; short *buffer_ = nullptr; @@ -234,6 +253,38 @@ BackgroundAudio::~BackgroundAudio() { delete[] buffer; } +BackgroundAudio::Sample *BackgroundAudio::LoadSample(const std::string &path) { + size_t bytes; + uint8_t *data = VFSReadFile(path.c_str(), &bytes); + if (!data) { + return nullptr; + } + + WavData wave; + wave.Read(RIFFReader(data, (int)bytes)); + + if (wave.num_channels != 2) { + ELOG("Wave format not supported for mixer playback. Must be 16-bit raw stereo. '%s'", path.c_str()); + return nullptr; + } + + int16_t *samples = new int16_t[2 * wave.numFrames]; + memcpy(samples, wave.raw_data_, wave.numFrames * wave.raw_bytes_per_frame_); + + return new BackgroundAudio::Sample(samples, wave.numFrames); +} + +void BackgroundAudio::LoadSamples() { + samples_.resize((size_t)MenuSFX::COUNT); + samples_[(size_t)MenuSFX::BACK] = std::unique_ptr(LoadSample("sfx_back.wav")); + samples_[(size_t)MenuSFX::SELECT] = std::unique_ptr(LoadSample("sfx_select.wav")); + samples_[(size_t)MenuSFX::CONFIRM] = std::unique_ptr(LoadSample("sfx_confirm.wav")); +} + +void BackgroundAudio::PlaySFX(MenuSFX sfx) { + plays_.push_back(PlayInstance{ sfx, 0 }); +} + void BackgroundAudio::Clear(bool hard) { if (!hard) { fadingOut = true; @@ -241,7 +292,6 @@ void BackgroundAudio::Clear(bool hard) { return; } if (at3Reader) { - at3Reader->Shutdown(); delete at3Reader; at3Reader = nullptr; } @@ -281,7 +331,7 @@ int BackgroundAudio::Play() { return 0; } - double now = time_now(); + double now = time_now_d(); if (at3Reader) { int sz = lastPlaybackTime <= 0.0 ? 44100 / 60 : (int)((now - lastPlaybackTime) * 44100); sz = std::min(BUFSIZE / 2, sz); diff --git a/UI/BackgroundAudio.h b/UI/BackgroundAudio.h index d5d8ca7f18..00afb4bab6 100644 --- a/UI/BackgroundAudio.h +++ b/UI/BackgroundAudio.h @@ -2,9 +2,17 @@ #include #include +#include class AT3PlusReader; +enum class MenuSFX { + SELECT = 0, + BACK = 1, + CONFIRM = 2, + COUNT, +}; + class BackgroundAudio { public: BackgroundAudio(); @@ -14,6 +22,10 @@ public: void SetGame(const std::string &path); void Update(); int Play(); + + void LoadSamples(); + void PlaySFX(MenuSFX sfx); + private: enum { BUFSIZE = 44100, @@ -29,6 +41,26 @@ private: bool fadingOut = true; float volume = 0.0f; float delta = -0.0001f; + + struct PlayInstance { + MenuSFX sound; + int offset; + }; + + struct Sample { + // data must be new-ed. + Sample(int16_t *data, int length) : data_(data), length_(length) {} + ~Sample() { + delete[] data_; + } + int16_t *data_; + int length_; // stereo samples. + }; + + static Sample *LoadSample(const std::string &path); + + std::vector plays_; + std::vector> samples_; }; extern BackgroundAudio g_BackgroundAudio; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 1ee82a3620..9e113e8369 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -446,6 +446,9 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch g_Discord.SetPresenceMenu(); + // TODO: Load these in the background instead of synchronously. + g_BackgroundAudio.LoadSamples(); + // Make sure UI state is MENU. ResetUIState(); diff --git a/ext/native/file/zip_read.cpp b/ext/native/file/zip_read.cpp index 5069cfc223..8c12860246 100644 --- a/ext/native/file/zip_read.cpp +++ b/ext/native/file/zip_read.cpp @@ -285,6 +285,7 @@ static bool IsLocalPath(const char *path) { return isUnixLocal || isWindowsLocal; } +// The returned data should be free'd with delete[]. uint8_t *VFSReadFile(const char *filename, size_t *size) { if (IsLocalPath(filename)) { // Local path, not VFS. diff --git a/ext/native/file/zip_read.h b/ext/native/file/zip_read.h index 3607ddb926..750a71a154 100644 --- a/ext/native/file/zip_read.h +++ b/ext/native/file/zip_read.h @@ -48,7 +48,7 @@ private: class DirectoryAssetReader : public AssetReader { public: - DirectoryAssetReader(const char *path) { + explicit DirectoryAssetReader(const char *path) { strncpy(path_, path, ARRAY_SIZE(path_)); path_[ARRAY_SIZE(path_) - 1] = '\0'; } @@ -61,6 +61,6 @@ public: } private: - char path_[512]; + char path_[512]{}; };