mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-02-13 10:24:39 +00:00
Read in WAV files for UI sounds
This commit is contained in:
parent
a0922e7bc7
commit
b30be913c0
@ -1,7 +1,7 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
#ifndef MOBILE_DEVICE
|
||||
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
|
@ -11,7 +11,6 @@
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
#ifndef MOBILE_DEVICE
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
@ -40,6 +39,3 @@ private:
|
||||
void Write(u32 value);
|
||||
void Write4(const char* ptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -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<u8> 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<u8> 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<s16, 128 * 1024> 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<Sample>(LoadSample("sfx_back.wav"));
|
||||
samples_[(size_t)MenuSFX::SELECT] = std::unique_ptr<Sample>(LoadSample("sfx_select.wav"));
|
||||
samples_[(size_t)MenuSFX::CONFIRM] = std::unique_ptr<Sample>(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);
|
||||
|
@ -2,9 +2,17 @@
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
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<PlayInstance> plays_;
|
||||
std::vector<std::unique_ptr<Sample>> samples_;
|
||||
};
|
||||
|
||||
extern BackgroundAudio g_BackgroundAudio;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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]{};
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user