mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-08-20 21:33:40 -07:00
store sfx as raw data, removes explicit dr_wav dependency
This commit is contained in:
parent
4ffc47c237
commit
3ec02994fb
11 changed files with 124 additions and 136 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -7,9 +7,6 @@
|
||||||
[submodule "OTRExporter"]
|
[submodule "OTRExporter"]
|
||||||
path = OTRExporter
|
path = OTRExporter
|
||||||
url = https://github.com/harbourmasters/OTRExporter
|
url = https://github.com/harbourmasters/OTRExporter
|
||||||
[submodule "dr_libs"]
|
|
||||||
path = soh/include/dr_libs
|
|
||||||
url = https://github.com/mackron/dr_libs
|
|
||||||
[submodule "miniaudio"]
|
[submodule "miniaudio"]
|
||||||
path = soh/include/miniaudio
|
path = soh/include/miniaudio
|
||||||
url = https://github.com/mackron/miniaudio
|
url = https://github.com/mackron/miniaudio
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 9cb7092ac8c75a82b5c6ea72652ca8d0091d7ffa
|
|
|
@ -1,10 +1,3 @@
|
||||||
#define MINIAUDIO_IMPLEMENTATION
|
|
||||||
#define MA_NO_THREADING
|
|
||||||
#define MA_NO_DEVICE_IO
|
|
||||||
#define MA_NO_GENERATION
|
|
||||||
#define MA_NO_FLAC
|
|
||||||
#define MA_NO_MP3
|
|
||||||
#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS
|
|
||||||
#define AAE_CHANNELS 2
|
#define AAE_CHANNELS 2
|
||||||
#define AAE_SAMPLE_RATE 44100
|
#define AAE_SAMPLE_RATE 44100
|
||||||
#define AAE_MAX_BUFFER_SIZE AAE_SAMPLE_RATE / 10
|
#define AAE_MAX_BUFFER_SIZE AAE_SAMPLE_RATE / 10
|
||||||
|
@ -23,6 +16,8 @@ int AudioPlayer_GetDesiredBuffered();
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
enum AAE_COMMANDS {
|
enum AAE_COMMANDS {
|
||||||
AAE_START = 0,
|
AAE_START = 0,
|
||||||
AAE_STOP,
|
AAE_STOP,
|
||||||
|
@ -123,10 +118,9 @@ uint32_t AccessibleAudioEngine::retrieve(float* buffer, uint32_t nFrames) {
|
||||||
if (nFrames == 0)
|
if (nFrames == 0)
|
||||||
return 0;
|
return 0;
|
||||||
uint32_t ogNFrames = nFrames;
|
uint32_t ogNFrames = nFrames;
|
||||||
uint32_t framesObtained = 0;
|
|
||||||
while (nFrames > 0) {
|
while (nFrames > 0) {
|
||||||
void* readBuffer;
|
void* readBuffer;
|
||||||
framesObtained = nFrames;
|
uint32_t framesObtained = nFrames;
|
||||||
ma_pcm_rb_acquire_read(&preparedOutput, &framesObtained, (void**)&readBuffer);
|
ma_pcm_rb_acquire_read(&preparedOutput, &framesObtained, (void**)&readBuffer);
|
||||||
if (framesObtained > nFrames)
|
if (framesObtained > nFrames)
|
||||||
framesObtained = nFrames;
|
framesObtained = nFrames;
|
||||||
|
@ -246,6 +240,7 @@ void AccessibleAudioEngine::runThread() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SoundSlot* AccessibleAudioEngine::findSound(SoundAction& action) {
|
SoundSlot* AccessibleAudioEngine::findSound(SoundAction& action) {
|
||||||
if (action.slot < 0 || action.slot >= AAE_SLOTS_PER_HANDLE)
|
if (action.slot < 0 || action.slot >= AAE_SLOTS_PER_HANDLE)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -257,6 +252,7 @@ SoundSlot* AccessibleAudioEngine::findSound(SoundAction& action) {
|
||||||
return NULL;
|
return NULL;
|
||||||
return ⌖
|
return ⌖
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessibleAudioEngine::doPlaySound(SoundAction& action) {
|
void AccessibleAudioEngine::doPlaySound(SoundAction& action) {
|
||||||
SoundSlot* sound;
|
SoundSlot* sound;
|
||||||
if (sounds.contains(action.handle)) {
|
if (sounds.contains(action.handle)) {
|
||||||
|
@ -266,7 +262,6 @@ void AccessibleAudioEngine::doPlaySound(SoundAction& action) {
|
||||||
destroySound(sound);
|
destroySound(sound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
SoundSlots temp;
|
SoundSlots temp;
|
||||||
for (int i = 0; i < AAE_SLOTS_PER_HANDLE; i++)
|
for (int i = 0; i < AAE_SLOTS_PER_HANDLE; i++)
|
||||||
|
@ -275,10 +270,14 @@ void AccessibleAudioEngine::doPlaySound(SoundAction& action) {
|
||||||
sounds[action.handle] = temp;
|
sounds[action.handle] = temp;
|
||||||
sound = &sounds[action.handle][action.slot];
|
sound = &sounds[action.handle][action.slot];
|
||||||
}
|
}
|
||||||
if (ma_sound_init_from_file(&engine, action.path.c_str(),
|
|
||||||
|
ma_result result = ma_sound_init_from_file(&engine, action.path.c_str(),
|
||||||
MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT, NULL, NULL,
|
MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT, NULL, NULL,
|
||||||
&sound->sound) != MA_SUCCESS)
|
&sound->sound);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
SPDLOG_ERROR("failed to play sound: {}", ma_result_description(result));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
initSoundExtras(sound);
|
initSoundExtras(sound);
|
||||||
ma_sound_set_looping(&sound->sound, action.looping);
|
ma_sound_set_looping(&sound->sound, action.looping);
|
||||||
|
@ -289,6 +288,7 @@ void AccessibleAudioEngine::doPlaySound(SoundAction& action) {
|
||||||
|
|
||||||
sound->active = true;
|
sound->active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessibleAudioEngine::doStopSound(SoundAction& action) {
|
void AccessibleAudioEngine::doStopSound(SoundAction& action) {
|
||||||
SoundSlot* slot = findSound(action);
|
SoundSlot* slot = findSound(action);
|
||||||
if (slot == NULL)
|
if (slot == NULL)
|
||||||
|
@ -416,6 +416,7 @@ bool AccessibleAudioEngine::initSoundExtras(SoundSlot* slot) {
|
||||||
ma_node_attach_output_bus(&slot->sound, 0, &slot->extras, 0);
|
ma_node_attach_output_bus(&slot->sound, 0, &slot->extras, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessibleAudioEngine::destroySound(SoundSlot* slot) {
|
void AccessibleAudioEngine::destroySound(SoundSlot* slot) {
|
||||||
ma_node_detach_all_output_buses(&slot->extras);
|
ma_node_detach_all_output_buses(&slot->extras);
|
||||||
ma_sound_uninit(&slot->sound);
|
ma_sound_uninit(&slot->sound);
|
||||||
|
@ -461,7 +462,6 @@ AccessibleAudioEngine::~AccessibleAudioEngine() {
|
||||||
destroy();
|
destroy();
|
||||||
}
|
}
|
||||||
void AccessibleAudioEngine::mix(int16_t* ogBuffer, uint32_t nFrames) {
|
void AccessibleAudioEngine::mix(int16_t* ogBuffer, uint32_t nFrames) {
|
||||||
uint32_t framesAvailable = ma_pcm_rb_available_read(&preparedOutput);
|
|
||||||
float sourceChunk[AAE_MIX_CHUNK_SIZE * AAE_CHANNELS];
|
float sourceChunk[AAE_MIX_CHUNK_SIZE * AAE_CHANNELS];
|
||||||
float mixedChunk[AAE_MIX_CHUNK_SIZE * AAE_CHANNELS];
|
float mixedChunk[AAE_MIX_CHUNK_SIZE * AAE_CHANNELS];
|
||||||
while (nFrames > 0) {
|
while (nFrames > 0) {
|
||||||
|
@ -597,8 +597,3 @@ void AccessibleAudioEngine::prepare() {
|
||||||
// This is called once at the end of every frame, so now is the time to post all of the accumulated commands.
|
// This is called once at the end of every frame, so now is the time to post all of the accumulated commands.
|
||||||
postSoundActions();
|
postSoundActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessibleAudioEngine::cacheDecodedSample(const char* path, void* data, size_t size) {
|
|
||||||
// data stored as wave, so we register it with MiniAudio as an encoded asset as opposed to a decoded one
|
|
||||||
ma_resource_manager_register_encoded_data(&resourceManager, path, data, size);
|
|
||||||
}
|
|
|
@ -1,11 +1,4 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MA_NO_FLAC
|
|
||||||
#define MA_NO_MP3
|
|
||||||
#define MA_NO_THREADING
|
|
||||||
#define MA_NO_DEVICE_IO
|
|
||||||
#define MA_NO_GENERATION
|
|
||||||
#include "miniaudio/miniaudio.h"
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -14,6 +7,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
|
#include "soh/Enhancements/audio/miniaudio.h"
|
||||||
|
|
||||||
#define AAE_SOUND_ACTION_BATCH_SIZE 64
|
#define AAE_SOUND_ACTION_BATCH_SIZE 64
|
||||||
#define AAE_SLOTS_PER_HANDLE 16
|
#define AAE_SLOTS_PER_HANDLE 16
|
||||||
class IResource;
|
class IResource;
|
||||||
|
@ -71,7 +67,6 @@ typedef std::array<SoundSlot, AAE_SLOTS_PER_HANDLE> SoundSlots;
|
||||||
|
|
||||||
class AccessibleAudioEngine {
|
class AccessibleAudioEngine {
|
||||||
int initialized;
|
int initialized;
|
||||||
ma_resource_manager resourceManager;
|
|
||||||
ma_engine engine;
|
ma_engine engine;
|
||||||
ma_pcm_rb preparedOutput; // Lock-free single producer single consumer.
|
ma_pcm_rb preparedOutput; // Lock-free single producer single consumer.
|
||||||
std::deque<SoundAction> soundActions; // A command cue.
|
std::deque<SoundAction> soundActions; // A command cue.
|
||||||
|
@ -152,5 +147,6 @@ class AccessibleAudioEngine {
|
||||||
float maxDistance);
|
float maxDistance);
|
||||||
// Schedule the preparation of output for delivery.
|
// Schedule the preparation of output for delivery.
|
||||||
void prepare();
|
void prepare();
|
||||||
void cacheDecodedSample(const char* path, void* data, size_t size);
|
|
||||||
|
ma_resource_manager resourceManager;
|
||||||
};
|
};
|
||||||
|
|
|
@ -90,7 +90,7 @@ class ActorAccessibility {
|
||||||
// Maps internal sfx to external (prerendered) resources.
|
// Maps internal sfx to external (prerendered) resources.
|
||||||
std::unordered_map<s16, SfxRecord> sfxMap;
|
std::unordered_map<s16, SfxRecord> sfxMap;
|
||||||
// Similar to above, but this one maps raw audio samples as opposed to SFX.
|
// Similar to above, but this one maps raw audio samples as opposed to SFX.
|
||||||
std::unordered_set<const char*> sampleMap;
|
std::unordered_map<const char*, std::vector<uint8_t>> sampleMap;
|
||||||
int extractSfx = 0;
|
int extractSfx = 0;
|
||||||
s16 currentScene = -1;
|
s16 currentScene = -1;
|
||||||
s8 currentRoom = -1;
|
s8 currentRoom = -1;
|
||||||
|
@ -706,16 +706,17 @@ const char* ActorAccessibility_MapSfxToExternalAudio(s16 sfxId) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::setw(4) << std::setfill('0') << std::hex << sfxId;
|
ss << std::setw(4) << std::setfill('0') << std::hex << sfxId;
|
||||||
tempRecord.path = ss.str();
|
tempRecord.path = ss.str();
|
||||||
aa->sfxMap[sfxId] = tempRecord;
|
auto pair = aa->sfxMap.insert({ sfxId, tempRecord });
|
||||||
record = &aa->sfxMap[sfxId];
|
record = &pair.first->second;
|
||||||
aa->audioEngine->cacheDecodedSample(record->path.c_str(), record->resource->Buffer->data(),
|
ma_resource_manager_register_decoded_data(&aa->audioEngine->resourceManager, record->path.c_str(),
|
||||||
record->resource->Buffer->size());
|
record->resource->Buffer->data(), record->resource->Buffer->size() / 2, ma_format_s16, 1, 44100);
|
||||||
} else {
|
} else {
|
||||||
record = &it->second;
|
record = &it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return record->path.c_str();
|
return record->path.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the path to a raw sample to the external audio engine.
|
// Map the path to a raw sample to the external audio engine.
|
||||||
const char* ActorAccessibility_MapRawSampleToExternalAudio(const char* name) {
|
const char* ActorAccessibility_MapRawSampleToExternalAudio(const char* name) {
|
||||||
auto it = aa->sampleMap.find(name);
|
auto it = aa->sampleMap.find(name);
|
||||||
|
@ -728,11 +729,9 @@ const char* ActorAccessibility_MapRawSampleToExternalAudio(const char* name) {
|
||||||
return NULL; // Resource doesn't exist, user's gotta run the extractor.
|
return NULL; // Resource doesn't exist, user's gotta run the extractor.
|
||||||
AudioDecoder decoder;
|
AudioDecoder decoder;
|
||||||
decoder.setSample((SOH::AudioSample*)res.get());
|
decoder.setSample((SOH::AudioSample*)res.get());
|
||||||
// TODO track wav somehow & free it with drwav_free
|
auto pair = aa->sampleMap.insert({ name, decoder.decodeToWav() });
|
||||||
s16* wav;
|
ma_resource_manager_register_encoded_data(&aa->audioEngine->resourceManager, name,
|
||||||
size_t wavSize = decoder.decodeToWav(&wav);
|
pair.first->second.data(), pair.first->second.size());
|
||||||
aa->sampleMap.insert(name);
|
|
||||||
aa->audioEngine->cacheDecodedSample(name, wav, wavSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
#include "SfxExtractor.h"
|
#include "SfxExtractor.h"
|
||||||
|
#include "soh/Enhancements/audio/AudioDecoder.h"
|
||||||
|
#include "soh/Enhancements/audio/miniaudio.h"
|
||||||
#include "soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h"
|
#include "soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h"
|
||||||
#include "soh/Enhancements/tts/tts.h"
|
#include "soh/Enhancements/tts/tts.h"
|
||||||
#include "dr_libs/dr_wav.h"
|
|
||||||
#define MA_NO_FLAC
|
|
||||||
#define MA_NO_MP3
|
|
||||||
#define MA_NO_THREADING
|
|
||||||
#define MA_NO_DEVICE_IO
|
|
||||||
#define MA_NO_GENERATION
|
|
||||||
#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS
|
|
||||||
#include "miniaudio/miniaudio.h"
|
|
||||||
#include "soh/OTRGlobals.h"
|
#include "soh/OTRGlobals.h"
|
||||||
#include "SfxTable.h"
|
#include "SfxTable.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
@ -19,23 +13,7 @@ extern "C" {
|
||||||
void AudioMgr_CreateNextAudioBuffer(s16* samples, u32 num_samples);
|
void AudioMgr_CreateNextAudioBuffer(s16* samples, u32 num_samples);
|
||||||
extern bool freezeGame;
|
extern bool freezeGame;
|
||||||
}
|
}
|
||||||
enum {
|
|
||||||
STEP_SETUP = 0,
|
|
||||||
STEP_MAIN,
|
|
||||||
STEP_FINISHED,
|
|
||||||
STEP_ERROR,
|
|
||||||
STEP_ERROR_OTR, // File exists.
|
|
||||||
|
|
||||||
} SFX_EXTRACTION_STEPS;
|
|
||||||
enum {
|
|
||||||
CT_WAITING, // for a sound to start ripping.
|
|
||||||
CT_PRIMING,
|
|
||||||
CT_READY, // to start ripping a sound.
|
|
||||||
CT_FINISHED, // ripping the current sound.
|
|
||||||
CT_SHUTDOWN,
|
|
||||||
} CAPTURE_THREAD_STATES;
|
|
||||||
#define SFX_EXTRACTION_BUFFER_SIZE 44100 * 15
|
|
||||||
#define SFX_EXTRACTION_ONE_FRAME 736
|
|
||||||
bool SfxExtractor::isAllZero(int16_t* buffer, size_t count) {
|
bool SfxExtractor::isAllZero(int16_t* buffer, size_t count) {
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
if (buffer[i] != 0)
|
if (buffer[i] != 0)
|
||||||
|
@ -71,34 +49,22 @@ bool SfxExtractor::renderOutput(size_t endOfInput) {
|
||||||
ma_channel_converter_config config =
|
ma_channel_converter_config config =
|
||||||
ma_channel_converter_config_init(ma_format_s16, 2, NULL, 1, NULL, ma_channel_mix_mode_default);
|
ma_channel_converter_config_init(ma_format_s16, 2, NULL, 1, NULL, ma_channel_mix_mode_default);
|
||||||
ma_channel_converter converter;
|
ma_channel_converter converter;
|
||||||
if (ma_channel_converter_init(&config, NULL, &converter) != MA_SUCCESS)
|
if (ma_channel_converter_init(&config, NULL, &converter) != MA_SUCCESS) {
|
||||||
throw std::runtime_error("SfxExtractor: Unable to initialize channel converter.");
|
return false;
|
||||||
drwav_data_format format;
|
}
|
||||||
format.bitsPerSample = 16;
|
std::vector<uint8_t> fileData;
|
||||||
format.channels = 1;
|
|
||||||
format.container = drwav_container_riff;
|
|
||||||
format.format = DR_WAVE_FORMAT_PCM;
|
|
||||||
format.sampleRate = 44100;
|
|
||||||
drwav wav;
|
|
||||||
std::string fileName = getExternalFileName(currentSfx);
|
std::string fileName = getExternalFileName(currentSfx);
|
||||||
void* mem = NULL;
|
|
||||||
size_t size = 0;
|
|
||||||
|
|
||||||
if (!drwav_init_memory_write(&wav, &mem, &size, &format, NULL))
|
|
||||||
throw std::runtime_error("SfxExtractor: Unable to initialize wave writer.");
|
|
||||||
int16_t chunk[64];
|
int16_t chunk[64];
|
||||||
int16_t* mark = tempBuffer + startOfInput;
|
int16_t* mark = tempBuffer + startOfInput;
|
||||||
size_t samplesLeft = endOfInput - startOfInput;
|
while (mark < tempBuffer + endOfInput) {
|
||||||
while (samplesLeft > 0) {
|
size_t chunkSize = std::min<size_t>(64, ((tempBuffer + endOfInput) - mark) / 2);
|
||||||
size_t thisChunk = std::min<size_t>(64, samplesLeft);
|
ma_result converter_result = ma_channel_converter_process_pcm_frames(&converter, chunk, mark, chunkSize);
|
||||||
ma_channel_converter_process_pcm_frames(&converter, chunk, mark, thisChunk / 2);
|
if (converter_result != MA_SUCCESS) {
|
||||||
drwav_write_pcm_frames(&wav, thisChunk / 2, chunk);
|
return false;
|
||||||
samplesLeft -= thisChunk;
|
}
|
||||||
mark += thisChunk;
|
fileData.insert(fileData.end(), (uint8_t*)chunk, (uint8_t*)(chunk + chunkSize));
|
||||||
|
mark += chunkSize * 2;
|
||||||
}
|
}
|
||||||
drwav_uninit(&wav);
|
|
||||||
std::vector<uint8_t> fileData((uint8_t*)mem, (uint8_t*)mem + size);
|
|
||||||
drwav_free(mem, nullptr);
|
|
||||||
return archive->WriteFile(fileName.c_str(), fileData);
|
return archive->WriteFile(fileName.c_str(), fileData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,18 +76,15 @@ void SfxExtractor::setup() {
|
||||||
captureThreadState = CT_WAITING;
|
captureThreadState = CT_WAITING;
|
||||||
OTRAudio_InstallSfxCaptureThread();
|
OTRAudio_InstallSfxCaptureThread();
|
||||||
// Make sure we're starting from a clean slate.
|
// Make sure we're starting from a clean slate.
|
||||||
std::string sohAccessibilityPath = Ship::Context::GetPathRelativeToAppDirectory("accessibility.o2r");
|
std::string sohAccessibilityPath = Ship::Context::GetPathRelativeToAppBundle("accessibility.o2r");
|
||||||
if (std::filesystem::exists(sohAccessibilityPath)) {
|
if (std::filesystem::exists(sohAccessibilityPath)) {
|
||||||
currentStep = STEP_ERROR_OTR;
|
currentStep = STEP_ERROR_FILE_EXISTS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Over-allocated just a tad because otherwise we'll overrun if the last frame is short.
|
|
||||||
tempStorage.resize((SFX_EXTRACTION_BUFFER_SIZE + (SFX_EXTRACTION_ONE_FRAME * 3)) * 2, 0);
|
|
||||||
tempBuffer = tempStorage.data();
|
|
||||||
|
|
||||||
sfxToRip = 0;
|
sfxToRip = 0;
|
||||||
currentStep = STEP_MAIN;
|
currentStep = STEP_MAIN;
|
||||||
archive = std::make_shared<Ship::O2rArchive>("accessibility.o2r");
|
archive = std::make_shared<Ship::O2rArchive>(sohAccessibilityPath);
|
||||||
archive->Open();
|
archive->Open();
|
||||||
} catch (...) { currentStep = STEP_ERROR; }
|
} catch (...) { currentStep = STEP_ERROR; }
|
||||||
}
|
}
|
||||||
|
@ -166,7 +129,7 @@ void SfxExtractor::finished() {
|
||||||
|
|
||||||
Audio_QueueSeqCmd(NA_BGM_TITLE);
|
Audio_QueueSeqCmd(NA_BGM_TITLE);
|
||||||
|
|
||||||
if (currentStep == STEP_ERROR || currentStep == STEP_ERROR_OTR) {
|
if (currentStep >= STEP_ERROR) {
|
||||||
Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
|
Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
|
||||||
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
||||||
Audio_PlaySoundGeneral(NA_SE_EN_GANON_LAUGH, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
|
Audio_PlaySoundGeneral(NA_SE_EN_GANON_LAUGH, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
|
||||||
|
@ -174,7 +137,7 @@ void SfxExtractor::finished() {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Sorry, we tried to extract the sound effects, but Ganondorf overruled us with an iron fist."
|
ss << "Sorry, we tried to extract the sound effects, but Ganondorf overruled us with an iron fist."
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
if (currentStep == STEP_ERROR_OTR)
|
if (currentStep == STEP_ERROR_FILE_EXISTS)
|
||||||
ss << "In all seriousness, please delete accessibility.o2r and try again.";
|
ss << "In all seriousness, please delete accessibility.o2r and try again.";
|
||||||
SpeechSynthesizer::Instance->Speak(ss.str().c_str(), "en-US");
|
SpeechSynthesizer::Instance->Speak(ss.str().c_str(), "en-US");
|
||||||
} else
|
} else
|
||||||
|
@ -209,8 +172,8 @@ void SfxExtractor::frameCallback() {
|
||||||
|
|
||||||
void SfxExtractor::prime() {
|
void SfxExtractor::prime() {
|
||||||
while (true) {
|
while (true) {
|
||||||
AudioMgr_CreateNextAudioBuffer(tempBuffer, SFX_EXTRACTION_ONE_FRAME);
|
AudioMgr_CreateNextAudioBuffer(tempBuffer + 0, SFX_EXTRACTION_ONE_FRAME);
|
||||||
if (isAllZero(tempBuffer, SFX_EXTRACTION_ONE_FRAME * 2))
|
if (isAllZero(tempBuffer + 0, SFX_EXTRACTION_ONE_FRAME * 2))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
captureThreadState = CT_FINISHED;
|
captureThreadState = CT_FINISHED;
|
||||||
|
@ -222,10 +185,9 @@ void SfxExtractor::captureCallback() {
|
||||||
if (captureThreadState != CT_READY)
|
if (captureThreadState != CT_READY)
|
||||||
return; // No work to do at the moment.
|
return; // No work to do at the moment.
|
||||||
memset(tempBuffer, 0, SFX_EXTRACTION_BUFFER_SIZE * 4);
|
memset(tempBuffer, 0, SFX_EXTRACTION_BUFFER_SIZE * 4);
|
||||||
int16_t* mark = tempBuffer;
|
int16_t* mark = tempBuffer + 0;
|
||||||
size_t samplesLeft = SFX_EXTRACTION_BUFFER_SIZE;
|
size_t samplesLeft = SFX_EXTRACTION_BUFFER_SIZE;
|
||||||
bool outputStarted = false;
|
bool outputStarted = false;
|
||||||
size_t endOfInput = 0;
|
|
||||||
int waitTime = 0;
|
int waitTime = 0;
|
||||||
while (samplesLeft > 0) {
|
while (samplesLeft > 0) {
|
||||||
AudioMgr_CreateNextAudioBuffer(mark, SFX_EXTRACTION_ONE_FRAME);
|
AudioMgr_CreateNextAudioBuffer(mark, SFX_EXTRACTION_ONE_FRAME);
|
||||||
|
@ -241,19 +203,19 @@ void SfxExtractor::captureCallback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
outputStarted = true;
|
outputStarted = true;
|
||||||
mark += (SFX_EXTRACTION_ONE_FRAME * 2);
|
size_t samples = std::min<size_t>(SFX_EXTRACTION_ONE_FRAME, samplesLeft);
|
||||||
endOfInput += (SFX_EXTRACTION_ONE_FRAME * 2);
|
mark += samples * 2;
|
||||||
samplesLeft -= std::min<size_t>(SFX_EXTRACTION_ONE_FRAME, samplesLeft);
|
samplesLeft -= samples;
|
||||||
}
|
}
|
||||||
if (renderOutput(endOfInput)) {
|
if (renderOutput(mark - tempBuffer)) {
|
||||||
captureThreadState = CT_FINISHED;
|
captureThreadState = CT_FINISHED;
|
||||||
} else {
|
} else {
|
||||||
SPDLOG_ERROR("failed to write file to archive, trying again");
|
SPDLOG_ERROR("failed to write file to archive, trying again");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SfxExtractor::getExternalFileName(int16_t sfxId) {
|
std::string SfxExtractor::getExternalFileName(int16_t sfxId) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "accessibility/audio/";
|
ss << "accessibility/audio/" << std::hex << std::setw(4) << std::setfill('0') << sfxId << ".wav";
|
||||||
ss << std::hex << std::setw(4) << std::setfill('0') << sfxId << ".wav";
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,33 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "libultraship/libultraship.h"
|
#include "libultraship/libultraship.h"
|
||||||
|
|
||||||
|
#define SFX_EXTRACTION_BUFFER_SIZE 44100 * 15
|
||||||
|
#define SFX_EXTRACTION_ONE_FRAME 736
|
||||||
|
|
||||||
|
enum CaptureThreadStates {
|
||||||
|
CT_WAITING, // for a sound to start ripping.
|
||||||
|
CT_PRIMING,
|
||||||
|
CT_READY, // to start ripping a sound.
|
||||||
|
CT_FINISHED, // ripping the current sound.
|
||||||
|
CT_SHUTDOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SfxExtractionSteps {
|
||||||
|
STEP_SETUP = 0,
|
||||||
|
STEP_MAIN,
|
||||||
|
STEP_FINISHED,
|
||||||
|
STEP_ERROR,
|
||||||
|
STEP_ERROR_FILE_EXISTS,
|
||||||
|
};
|
||||||
|
|
||||||
class SfxExtractor {
|
class SfxExtractor {
|
||||||
std::shared_ptr<Ship::Archive> archive;
|
std::shared_ptr<Ship::Archive> archive;
|
||||||
int currentStep;
|
SfxExtractionSteps currentStep;
|
||||||
int captureThreadState;
|
CaptureThreadStates captureThreadState;
|
||||||
int sfxToRip;
|
int sfxToRip;
|
||||||
s16 currentSfx;
|
s16 currentSfx;
|
||||||
std::vector<int16_t> tempStorage; // Stores raw audio data for the sfx currently being ripped.
|
// Stores raw audio data for the sfx currently being ripped.
|
||||||
int16_t* tempBuffer; // Raw pointer to the above vector.
|
int16_t tempBuffer[(SFX_EXTRACTION_BUFFER_SIZE + SFX_EXTRACTION_ONE_FRAME * 3) * 2];
|
||||||
// Check if a buffer contains meaningful audio output.
|
// Check if a buffer contains meaningful audio output.
|
||||||
bool isAllZero(int16_t* buffer, size_t count);
|
bool isAllZero(int16_t* buffer, size_t count);
|
||||||
size_t adjustedStartOfInput();
|
size_t adjustedStartOfInput();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
#define MINIAUDIO_IMPLEMENTATION
|
||||||
#include "AudioDecoder.h"
|
#include "AudioDecoder.h"
|
||||||
#include "z64audio.h"
|
#include "z64audio.h"
|
||||||
#define DR_WAV_IMPLEMENTATION
|
|
||||||
#include "dr_libs/dr_wav.h"
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#define WAV_DECODE_CHUNK_SIZE 64
|
#define WAV_DECODE_CHUNK_SIZE 64
|
||||||
// A handful of definitions need to be copied from mixer.c.
|
// A handful of definitions need to be copied from mixer.c.
|
||||||
|
@ -23,6 +22,7 @@ AudioDecoder::AudioDecoder() {
|
||||||
}
|
}
|
||||||
AudioDecoder::~AudioDecoder() {
|
AudioDecoder::~AudioDecoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDecoder::setSample(SOH::AudioSample* sample) {
|
void AudioDecoder::setSample(SOH::AudioSample* sample) {
|
||||||
this->sample.codec = sample->sample.codec;
|
this->sample.codec = sample->sample.codec;
|
||||||
this->sample.loop.start = sample->sample.loop->start;
|
this->sample.loop.start = sample->sample.loop->start;
|
||||||
|
@ -40,6 +40,7 @@ void AudioDecoder::setSample(SOH::AudioSample* sample) {
|
||||||
inStart = in;
|
inStart = in;
|
||||||
inEnd = in + sample->sample.size;
|
inEnd = in + sample->sample.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDecoder::setSample(SoundFontSample* sample) {
|
void AudioDecoder::setSample(SoundFontSample* sample) {
|
||||||
this->sample.codec = sample->codec;
|
this->sample.codec = sample->codec;
|
||||||
this->sample.loop.start = sample->loop->start;
|
this->sample.loop.start = sample->loop->start;
|
||||||
|
@ -56,6 +57,7 @@ void AudioDecoder::setSample(SoundFontSample* sample) {
|
||||||
inStart = in;
|
inStart = in;
|
||||||
inEnd = in + sample->size;
|
inEnd = in + sample->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioDecoder::decode(int16_t* out, size_t nSamples) {
|
size_t AudioDecoder::decode(int16_t* out, size_t nSamples) {
|
||||||
size_t samplesOut = 0;
|
size_t samplesOut = 0;
|
||||||
size_t nbytes = nSamples * 2;
|
size_t nbytes = nSamples * 2;
|
||||||
|
@ -101,26 +103,39 @@ size_t AudioDecoder::decode(int16_t* out, size_t nSamples) {
|
||||||
return samplesOut;
|
return samplesOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AudioDecoder::decodeToWav(int16_t** buffer) {
|
ma_result wavWrite(ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite, size_t* pBytesWritten) {
|
||||||
int16_t* wavOut = nullptr;
|
auto fileData = (std::vector<uint8_t>*)pEncoder->pUserData;
|
||||||
|
fileData->insert(fileData->end(), (uint8_t*)pBufferIn, (uint8_t*)pBufferIn + bytesToWrite);
|
||||||
|
if (pBytesWritten != NULL) {
|
||||||
|
*pBytesWritten = bytesToWrite;
|
||||||
|
}
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
drwav_data_format format;
|
ma_result wavSeek(ma_encoder* pEncoder, ma_int64 offset, ma_seek_origin origin) {
|
||||||
format.bitsPerSample = 16;
|
return MA_ERROR;
|
||||||
format.channels = 1;
|
}
|
||||||
format.container = drwav_container_riff;
|
|
||||||
format.format = DR_WAVE_FORMAT_PCM;
|
std::vector<uint8_t> AudioDecoder::decodeToWav() {
|
||||||
|
std::vector<uint8_t> fileData;
|
||||||
|
|
||||||
|
ma_uint32 sampleRate;
|
||||||
// Todo: figure out how to really determine the sample rate. CODEC_ADPCM tends to stream at higher rates (usually
|
// Todo: figure out how to really determine the sample rate. CODEC_ADPCM tends to stream at higher rates (usually
|
||||||
// 20KHZ) while CODEC_SMALL_ADPCM is usually around 14000. They're still not consistent though.
|
// 20KHZ) while CODEC_SMALL_ADPCM is usually around 14000. They're still not consistent though.
|
||||||
if (sample.codec == CODEC_ADPCM)
|
if (sample.codec == CODEC_ADPCM)
|
||||||
format.sampleRate = 20000;
|
sampleRate = 20000;
|
||||||
else if (sample.codec = CODEC_SMALL_ADPCM)
|
else if (sample.codec = CODEC_SMALL_ADPCM)
|
||||||
format.sampleRate = 14000;
|
sampleRate = 14000;
|
||||||
else
|
else
|
||||||
throw std::runtime_error("AudioDecoder: Unsupported codec.");
|
throw std::runtime_error("AudioDecoder: Unsupported codec.");
|
||||||
drwav wav;
|
|
||||||
size_t wavSize;
|
ma_encoder_config maconfig = ma_encoder_config_init(ma_encoding_format_wav, ma_format_s16, 1, sampleRate);
|
||||||
if (!drwav_init_memory_write(&wav, (void**)&wavOut, &wavSize, &format, nullptr))
|
ma_encoder wavEncoder;
|
||||||
throw std::runtime_error("AudioDecoder: Unable to initialize wave writer.");
|
ma_result init_result = ma_encoder_init(wavWrite, wavSeek, &fileData, &maconfig, &wavEncoder);
|
||||||
|
if (init_result != MA_SUCCESS) {
|
||||||
|
return fileData;
|
||||||
|
}
|
||||||
|
|
||||||
int16_t chunk[WAV_DECODE_CHUNK_SIZE];
|
int16_t chunk[WAV_DECODE_CHUNK_SIZE];
|
||||||
// Don't decode past the end of the loop.
|
// Don't decode past the end of the loop.
|
||||||
size_t samplesLeft = sample.loop.end;
|
size_t samplesLeft = sample.loop.end;
|
||||||
|
@ -135,14 +150,9 @@ size_t AudioDecoder::decodeToWav(int16_t** buffer) {
|
||||||
|
|
||||||
if (samplesRead == 0)
|
if (samplesRead == 0)
|
||||||
break;
|
break;
|
||||||
if (drwav_write_pcm_frames(&wav, samplesRead, chunk) != samplesRead) {
|
ma_encoder_write_pcm_frames(&wavEncoder, chunk, samplesRead, NULL);
|
||||||
drwav_uninit(&wav);
|
|
||||||
drwav_free(wavOut, nullptr);
|
|
||||||
throw std::runtime_error("AudioDecoder: Unable to write wave data.");
|
|
||||||
}
|
|
||||||
samplesLeft -= samplesRead;
|
samplesLeft -= samplesRead;
|
||||||
}
|
}
|
||||||
drwav_uninit(&wav);
|
ma_encoder_uninit(&wavEncoder);
|
||||||
*buffer = wavOut;
|
return fileData;
|
||||||
return wavSize;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// A standalone, incremental audio sample decoder.
|
// A standalone, incremental audio sample decoder.
|
||||||
// Based on the ADPCM decoding routines in mixer.c.
|
// Based on the ADPCM decoding routines in mixer.c.
|
||||||
|
|
||||||
|
#include "miniaudio.h"
|
||||||
#include "libultraship/libultraship.h"
|
#include "libultraship/libultraship.h"
|
||||||
#include "soh/resource/type/AudioSample.h"
|
#include "soh/resource/type/AudioSample.h"
|
||||||
#include "z64audio.h"
|
#include "z64audio.h"
|
||||||
|
@ -28,5 +29,5 @@ class AudioDecoder {
|
||||||
void setSample(SoundFontSample* sample);
|
void setSample(SoundFontSample* sample);
|
||||||
|
|
||||||
size_t decode(int16_t* out, size_t nSamples);
|
size_t decode(int16_t* out, size_t nSamples);
|
||||||
size_t decodeToWav(int16_t** buffer);
|
std::vector<uint8_t> decodeToWav();
|
||||||
};
|
};
|
9
soh/soh/Enhancements/audio/miniaudio.h
Normal file
9
soh/soh/Enhancements/audio/miniaudio.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
#define MA_NO_FLAC
|
||||||
|
#define MA_NO_MP3
|
||||||
|
#define MA_NO_THREADING
|
||||||
|
#define MA_NO_DEVICE_IO
|
||||||
|
#define MA_NO_GENERATION
|
||||||
|
#define MA_NO_STDIO
|
||||||
|
#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS
|
||||||
|
#include "miniaudio/miniaudio.h"
|
|
@ -294,7 +294,7 @@ OTRGlobals::OTRGlobals() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sohAccessibilityPath = Ship::Context::GetPathRelativeToAppDirectory("accessibility.o2r");
|
std::string sohAccessibilityPath = Ship::Context::GetPathRelativeToAppBundle("accessibility.o2r");
|
||||||
if (std::filesystem::exists(sohAccessibilityPath)) {
|
if (std::filesystem::exists(sohAccessibilityPath)) {
|
||||||
OTRFiles.push_back(sohAccessibilityPath);
|
OTRFiles.push_back(sohAccessibilityPath);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue