CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/BackgroundAudio.cpp
Views: 1401
#include <string>1#include <mutex>23#include "ext/minimp3/minimp3_ex.h"45#include "Common/File/VFS/VFS.h"6#include "Common/UI/Root.h"78#include "Common/Data/Text/I18n.h"9#include "Common/CommonTypes.h"10#include "Common/Data/Format/RIFF.h"11#include "Common/Log.h"12#include "Common/System/System.h"13#include "Common/System/OSD.h"14#include "Common/Serialize/SerializeFuncs.h"15#include "Common/TimeUtil.h"16#include "Common/Data/Collections/FixedSizeQueue.h"17#include "Core/HW/SimpleAudioDec.h"18#include "Core/HLE/__sceAudio.h"19#include "Core/System.h"20#include "Core/Config.h"21#include "UI/GameInfoCache.h"22#include "UI/BackgroundAudio.h"2324struct WavData {25int num_channels = -1;26int sample_rate = -1;27int numFrames = -1;28int samplesPerSec = -1;29int avgBytesPerSec = -1;30int raw_offset_loop_start = 0;31int raw_offset_loop_end = 0;32int loop_start_offset = 0;33int loop_end_offset = 0;34int codec = 0;35int raw_bytes_per_frame = 0;36uint8_t *raw_data = nullptr;37int raw_data_size = 0;38u8 at3_extradata[16];3940bool Read(RIFFReader &riff);4142~WavData() {43free(raw_data);44raw_data = nullptr;45}4647bool IsSimpleWAV() const {48bool isBad = raw_bytes_per_frame > sizeof(int16_t) * num_channels;49return !isBad && num_channels > 0 && sample_rate >= 8000 && codec == 0;50}51};5253bool WavData::Read(RIFFReader &file_) {54// If we have no loop start info, we'll just loop the entire audio.55raw_offset_loop_start = 0;56raw_offset_loop_end = 0;5758if (file_.Descend('RIFF')) {59file_.ReadInt(); //get past 'WAVE'60if (file_.Descend('fmt ')) { //enter the format chunk61int temp = file_.ReadInt();62int format = temp & 0xFFFF;63switch (format) {64case 0xFFFE:65codec = PSP_CODEC_AT3PLUS;66break;67case 0x270:68codec = PSP_CODEC_AT3;69break;70case 1:71// Raw wave data, no codec72codec = 0;73break;74default:75ERROR_LOG(Log::sceAudio, "Unexpected wave format %04x", format);76return false;77}7879num_channels = temp >> 16;8081samplesPerSec = file_.ReadInt();82/*avgBytesPerSec =*/ file_.ReadInt();8384temp = file_.ReadInt();85raw_bytes_per_frame = temp & 0xFFFF;8687if (codec == PSP_CODEC_AT3) {88// The first two bytes are actually not a useful part of the extradata.89// We already read 16 bytes, so make sure there's enough left.90if (file_.GetCurrentChunkSize() >= 32) {91file_.ReadData(at3_extradata, 16);92} else {93memset(at3_extradata, 0, sizeof(at3_extradata));94}95}96file_.Ascend();97// INFO_LOG(Log::AUDIO, "got fmt data: %i", samplesPerSec);98} else {99ERROR_LOG(Log::Audio, "Error - no format chunk in wav");100file_.Ascend();101return false;102}103104if (file_.Descend('smpl')) {105std::vector<u8> smplData;106smplData.resize(file_.GetCurrentChunkSize());107file_.ReadData(&smplData[0], (int)smplData.size());108109int numLoops = *(int *)&smplData[28];110struct AtracLoopInfo {111int cuePointID;112int type;113int startSample;114int endSample;115int fraction;116int playCount;117};118119if (numLoops > 0 && smplData.size() >= 36 + sizeof(AtracLoopInfo) * numLoops) {120AtracLoopInfo *loops = (AtracLoopInfo *)&smplData[36];121int samplesPerFrame = codec == PSP_CODEC_AT3PLUS ? 2048 : 1024;122123for (int i = 0; i < numLoops; ++i) {124// Only seen forward loops, so let's ignore others.125if (loops[i].type != 0)126continue;127128// We ignore loop interpolation (fraction) and play count for now.129raw_offset_loop_start = (loops[i].startSample / samplesPerFrame) * raw_bytes_per_frame;130loop_start_offset = loops[i].startSample % samplesPerFrame;131raw_offset_loop_end = (loops[i].endSample / samplesPerFrame) * raw_bytes_per_frame;132loop_end_offset = loops[i].endSample % samplesPerFrame;133134if (loops[i].playCount == 0) {135// This was an infinite loop, so ignore the rest.136// In practice, there's usually only one and it's usually infinite.137break;138}139}140}141142file_.Ascend();143}144145// enter the data chunk146if (file_.Descend('data')) {147int numBytes = file_.GetCurrentChunkSize();148numFrames = numBytes / raw_bytes_per_frame; // numFrames149150// It seems the atrac3 codec likes to read a little bit outside.151const int padding = 32; // 32 is the value FFMPEG uses.152raw_data = (uint8_t *)malloc(numBytes + padding);153raw_data_size = numBytes;154155if (num_channels == 1 || num_channels == 2) {156file_.ReadData(raw_data, numBytes);157} else {158ERROR_LOG(Log::Audio, "Error - bad blockalign or channels");159free(raw_data);160raw_data = nullptr;161return false;162}163file_.Ascend();164} else {165ERROR_LOG(Log::Audio, "Error - no data chunk in wav");166file_.Ascend();167return false;168}169file_.Ascend();170} else {171ERROR_LOG(Log::Audio, "Could not descend into RIFF file.");172return false;173}174sample_rate = samplesPerSec;175return true;176}177178// Really simple looping in-memory AT3 player that also takes care of reading the file format.179// Turns out that AT3 files used for this are modified WAVE files so fairly easy to parse.180class AT3PlusReader {181public:182explicit AT3PlusReader(const std::string &data)183: file_((const uint8_t *)&data[0], (int32_t)data.size()) {184// Normally 8k but let's be safe.185buffer_ = new short[32 * 1024];186187skip_next_samples_ = 0;188189wave_.Read(file_);190191uint8_t *extraData = nullptr;192size_t extraDataSize = 0;193size_t blockSize = 0;194if (wave_.codec == PSP_CODEC_AT3) {195extraData = &wave_.at3_extradata[2];196extraDataSize = 14;197blockSize = wave_.raw_bytes_per_frame;198} else if (wave_.codec == PSP_CODEC_AT3PLUS) {199blockSize = wave_.raw_bytes_per_frame;200}201decoder_ = CreateAudioDecoder((PSPAudioType)wave_.codec, wave_.sample_rate, wave_.num_channels, blockSize, extraData, extraDataSize);202INFO_LOG(Log::Audio, "read ATRAC, frames: %d, rate %d", wave_.numFrames, wave_.sample_rate);203}204205~AT3PlusReader() {206delete[] buffer_;207buffer_ = nullptr;208delete decoder_;209decoder_ = nullptr;210}211212bool IsOK() { return wave_.raw_data != nullptr; }213214bool Read(int *buffer, int len) {215if (!wave_.raw_data)216return false;217218while (bgQueue.size() < (size_t)(len * 2)) {219int outSamples = 0;220int inbytesConsumed = 0;221bool result = decoder_->Decode(wave_.raw_data + raw_offset_, wave_.raw_bytes_per_frame, &inbytesConsumed, 2, (int16_t *)buffer_, &outSamples);222if (!result || !outSamples)223return false;224int outBytes = outSamples * 2 * sizeof(int16_t);225226if (wave_.raw_offset_loop_end != 0 && raw_offset_ == wave_.raw_offset_loop_end) {227// Only take the remaining bytes, but convert to stereo s16.228outBytes = std::min(outBytes, wave_.loop_end_offset * 4);229}230231int start = skip_next_samples_;232skip_next_samples_ = 0;233234for (int i = start; i < outBytes / 2; i++) {235bgQueue.push(buffer_[i]);236}237238if (wave_.raw_offset_loop_end != 0 && raw_offset_ == wave_.raw_offset_loop_end) {239// Time to loop. Account for the addition below.240raw_offset_ = wave_.raw_offset_loop_start - wave_.raw_bytes_per_frame;241// This time we're counting each stereo sample.242skip_next_samples_ = wave_.loop_start_offset * 2;243}244245// Handle loops when there's no loop info.246raw_offset_ += wave_.raw_bytes_per_frame;247if (raw_offset_ >= wave_.raw_data_size) {248raw_offset_ = 0;249}250}251252for (int i = 0; i < len * 2; i++) {253buffer[i] = bgQueue.pop_front();254}255return true;256}257258private:259RIFFReader file_;260261WavData wave_;262263int raw_offset_ = 0;264int skip_next_samples_ = 0;265FixedSizeQueue<s16, 128 * 1024> bgQueue;266short *buffer_ = nullptr;267AudioDecoder *decoder_ = nullptr;268};269270BackgroundAudio g_BackgroundAudio;271272BackgroundAudio::BackgroundAudio() {273buffer = new int[BUFSIZE]();274sndLoadPending_.store(false);275}276277BackgroundAudio::~BackgroundAudio() {278delete at3Reader_;279delete[] buffer;280}281282void BackgroundAudio::Clear(bool hard) {283if (!hard) {284fadingOut_ = true;285volume_ = 1.0f;286return;287}288if (at3Reader_) {289delete at3Reader_;290at3Reader_ = nullptr;291}292playbackOffset_ = 0;293sndLoadPending_ = false;294}295296void BackgroundAudio::SetGame(const Path &path) {297if (path == bgGamePath_) {298// Do nothing299return;300}301302std::lock_guard<std::mutex> lock(mutex_);303if (path.empty()) {304Clear(false);305sndLoadPending_ = false;306fadingOut_ = true;307} else {308Clear(true);309gameLastChanged_ = time_now_d();310sndLoadPending_ = true;311fadingOut_ = false;312}313volume_ = 1.0f;314bgGamePath_ = path;315}316317bool BackgroundAudio::Play() {318std::lock_guard<std::mutex> lock(mutex_);319320// Immediately stop the sound if it is turned off while playing.321if (!g_Config.bEnableSound) {322Clear(true);323System_AudioClear();324return true;325}326327double now = time_now_d();328int sz = 44100 / 60;329if (lastPlaybackTime_ > 0.0 && lastPlaybackTime_ <= now) {330sz = (int)((now - lastPlaybackTime_) * 44100);331}332sz = std::min(BUFSIZE / 2, sz);333if (at3Reader_) {334if (at3Reader_->Read(buffer, sz)) {335if (fadingOut_) {336for (int i = 0; i < sz*2; i += 2) {337buffer[i] *= volume_;338buffer[i + 1] *= volume_;339volume_ += delta_;340}341}342}343} else {344for (int i = 0; i < sz * 2; i += 2) {345buffer[i] = 0;346buffer[i + 1] = 0;347}348}349350System_AudioPushSamples(buffer, sz);351352if (at3Reader_ && fadingOut_ && volume_ <= 0.0f) {353Clear(true);354fadingOut_ = false;355gameLastChanged_ = 0;356}357358lastPlaybackTime_ = now;359360return true;361}362363void BackgroundAudio::Update() {364// If there's a game, and some time has passed since the selected game365// last changed... (to prevent crazy amount of reads when skipping through a list)366if (sndLoadPending_ && (time_now_d() - gameLastChanged_ > 0.5)) {367std::lock_guard<std::mutex> lock(mutex_);368// Already loaded somehow? Or no game info cache?369if (at3Reader_ || !g_gameInfoCache)370return;371372// Grab some audio from the current game and play it.373std::shared_ptr<GameInfo> gameInfo = g_gameInfoCache->GetInfo(nullptr, bgGamePath_, GameInfoFlags::SND);374if (!gameInfo->Ready(GameInfoFlags::SND)) {375// Should try again shortly..376return;377}378379const std::string &data = gameInfo->sndFileData;380if (!data.empty()) {381at3Reader_ = new AT3PlusReader(data);382lastPlaybackTime_ = 0.0;383}384sndLoadPending_ = false;385}386}387388inline int16_t ConvertU8ToI16(uint8_t value) {389int ivalue = value - 128;390return ivalue * 255;391}392393Sample *Sample::Load(const std::string &path) {394size_t data_size = 0;395uint8_t *data = g_VFS.ReadFile(path.c_str(), &data_size);396if (!data || data_size > 100000000) {397WARN_LOG(Log::Audio, "Failed to load sample '%s'", path.c_str());398return nullptr;399}400401const char *mp3_magic = "ID3\03";402const char *wav_magic = "RIFF";403if (!memcmp(data, wav_magic, 4)) {404RIFFReader reader(data, (int)data_size);405WavData wave;406if (!wave.Read(reader)) {407delete[] data;408return nullptr;409}410// A wav file.411delete[] data;412413if (!wave.IsSimpleWAV()) {414ERROR_LOG(Log::Audio, "Wave format not supported for mixer playback. Must be 8-bit or 16-bit raw mono or stereo. '%s'", path.c_str());415return nullptr;416}417418int16_t *samples = new int16_t[wave.num_channels * wave.numFrames];419if (wave.raw_bytes_per_frame == wave.num_channels * 2) {420// 16-bit421memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame);422} else if (wave.raw_bytes_per_frame == wave.num_channels) {423// 8-bit. Convert.424for (int i = 0; i < wave.num_channels * wave.numFrames; i++) {425samples[i] = ConvertU8ToI16(wave.raw_data[i]);426}427}428429// Protect against bad metadata.430int actualFrames = std::min(wave.numFrames, wave.raw_data_size / wave.raw_bytes_per_frame);431432return new Sample(samples, wave.num_channels, actualFrames, wave.sample_rate);433}434435// Something else.436// Let's see if minimp3 can read it.437mp3dec_t mp3d;438mp3dec_init(&mp3d);439mp3dec_file_info_t mp3_info;440int retval = mp3dec_load_buf(&mp3d, data, data_size, &mp3_info, nullptr, nullptr);441442if (retval < 0 || mp3_info.samples == 0) {443ERROR_LOG(Log::Audio, "Couldn't load MP3 for sound effect from %s", path.c_str());444return nullptr;445}446447// mp3_info contains the decoded data.448int16_t *sample_data = new int16_t[mp3_info.samples];449memcpy(sample_data, mp3_info.buffer, mp3_info.samples * sizeof(int16_t));450451Sample *sample = new Sample(sample_data, mp3_info.channels, (int)mp3_info.samples / mp3_info.channels, mp3_info.hz);452free(mp3_info.buffer);453delete[] data;454return sample;455}456457static inline int16_t Clamp16(int32_t sample) {458if (sample < -32767) return -32767;459if (sample > 32767) return 32767;460return sample;461}462463void SoundEffectMixer::Mix(int16_t *buffer, int sz, int sampleRateHz) {464{465std::lock_guard<std::mutex> guard(mutex_);466if (!queue_.empty()) {467for (const auto &entry : queue_) {468plays_.push_back(entry);469}470queue_.clear();471}472if (plays_.empty()) {473return;474}475}476477for (std::vector<PlayInstance>::iterator iter = plays_.begin(); iter != plays_.end(); ) {478auto sample = samples_[(int)iter->sound].get();479if (!sample) {480// Remove playback instance if sample invalid.481iter = plays_.erase(iter);482continue;483}484485int64_t rateOfSample = sample->rateInHz_;486int64_t stride = (rateOfSample << 32) / sampleRateHz;487488for (int i = 0; i < sz * 2; i += 2) {489if ((iter->offset >> 32) >= sample->length_ - 2) {490iter->done = true;491break;492}493494int wholeOffset = iter->offset >> 32;495int frac = (iter->offset >> 20) & 0xFFF; // Use a 12 bit fraction to get away with 32-bit multiplies496497if (sample->channels_ == 2) {498int interpolatedLeft = (sample->data_[wholeOffset * 2] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2] * frac) >> 12;499int interpolatedRight = (sample->data_[wholeOffset * 2 + 1] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2 + 1] * frac) >> 12;500501// Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well.502int left = Clamp16(buffer[i] + (interpolatedLeft * iter->volume >> 8));503int right = Clamp16(buffer[i + 1] + (interpolatedRight * iter->volume >> 8));504505buffer[i] = left;506buffer[i + 1] = right;507} else if (sample->channels_ == 1) {508int interpolated = (sample->data_[wholeOffset] * (0x1000 - frac) + sample->data_[wholeOffset + 1] * frac) >> 12;509510// Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well.511int value = Clamp16(buffer[i] + (interpolated * iter->volume >> 8));512513buffer[i] = value;514buffer[i + 1] = value;515}516517iter->offset += stride;518}519520if (iter->done) {521iter = plays_.erase(iter);522} else {523iter++;524}525}526}527528void SoundEffectMixer::Play(UI::UISound sfx, float volume) {529std::lock_guard<std::mutex> guard(mutex_);530queue_.push_back(PlayInstance{ sfx, 0, (int)(255.0f * volume), false });531}532533void SoundEffectMixer::UpdateSample(UI::UISound sound, Sample *sample) {534if (sample) {535std::lock_guard<std::mutex> guard(mutex_);536samples_[(size_t)sound] = std::unique_ptr<Sample>(sample);537} else {538LoadDefaultSample(sound);539}540}541542void SoundEffectMixer::LoadDefaultSample(UI::UISound sound) {543const char *filename = nullptr;544switch (sound) {545case UI::UISound::BACK: filename = "sfx_back.wav"; break;546case UI::UISound::SELECT: filename = "sfx_select.wav"; break;547case UI::UISound::CONFIRM: filename = "sfx_confirm.wav"; break;548case UI::UISound::TOGGLE_ON: filename = "sfx_toggle_on.wav"; break;549case UI::UISound::TOGGLE_OFF: filename = "sfx_toggle_off.wav"; break;550case UI::UISound::ACHIEVEMENT_UNLOCKED: filename = "sfx_achievement_unlocked.wav"; break;551case UI::UISound::LEADERBOARD_SUBMITTED: filename = "sfx_leaderbord_submitted.wav"; break;552default:553return;554}555Sample *sample = Sample::Load(filename);556if (!sample) {557ERROR_LOG(Log::System, "Failed to load the default sample for UI sound %d", (int)sound);558}559std::lock_guard<std::mutex> guard(mutex_);560samples_[(size_t)sound] = std::unique_ptr<Sample>(sample);561}562563void SoundEffectMixer::LoadSamples() {564samples_.resize((size_t)UI::UISound::COUNT);565LoadDefaultSample(UI::UISound::BACK);566LoadDefaultSample(UI::UISound::SELECT);567LoadDefaultSample(UI::UISound::CONFIRM);568LoadDefaultSample(UI::UISound::TOGGLE_ON);569LoadDefaultSample(UI::UISound::TOGGLE_OFF);570571if (!g_Config.sAchievementsUnlockAudioFile.empty()) {572UpdateSample(UI::UISound::ACHIEVEMENT_UNLOCKED, Sample::Load(g_Config.sAchievementsUnlockAudioFile));573} else {574LoadDefaultSample(UI::UISound::ACHIEVEMENT_UNLOCKED);575}576if (!g_Config.sAchievementsLeaderboardSubmitAudioFile.empty()) {577UpdateSample(UI::UISound::LEADERBOARD_SUBMITTED, Sample::Load(g_Config.sAchievementsLeaderboardSubmitAudioFile));578} else {579LoadDefaultSample(UI::UISound::LEADERBOARD_SUBMITTED);580}581582UI::SetSoundCallback([](UI::UISound sound, float volume) {583g_BackgroundAudio.SFX().Play(sound, volume);584});585}586587588