Path: blob/master/RSDKv5/RSDK/Audio/Audio.cpp
1163 views
#include "RSDK/Core/RetroEngine.hpp"12using namespace RSDK;34#if RETRO_REV0U5#include "Legacy/AudioLegacy.cpp"6#endif78#define STB_VORBIS_NO_PUSHDATA_API9#define STB_VORBIS_NO_STDIO10#define STB_VORBIS_NO_INTEGER_CONVERSION11#include "stb_vorbis/stb_vorbis.c"1213stb_vorbis *vorbisInfo = NULL;14stb_vorbis_alloc vorbisAlloc;1516SFXInfo RSDK::sfxList[SFX_COUNT];17ChannelInfo RSDK::channels[CHANNEL_COUNT];1819char streamFilePath[0x40];20uint8 *streamBuffer = NULL;21int32 streamBufferSize = 0;22uint32 streamStartPos = 0;23int32 streamLoopPoint = 0;2425#define LINEAR_INTERPOLATION_LOOKUP_DIVISOR 0x40 // Determines the 'resolution' of the lookup table.26#define LINEAR_INTERPOLATION_LOOKUP_LENGTH (TO_FIXED(1) / LINEAR_INTERPOLATION_LOOKUP_DIVISOR)2728float linearInterpolationLookup[LINEAR_INTERPOLATION_LOOKUP_LENGTH];2930#if RETRO_AUDIODEVICE_XAUDIO31#include "XAudio/XAudioDevice.cpp"32#elif RETRO_AUDIODEVICE_SDL233#include "SDL2/SDL2AudioDevice.cpp"34#elif RETRO_AUDIODEVICE_PORT35#include "PortAudio/PortAudioDevice.cpp"36#elif RETRO_AUDIODEVICE_MINI37#include "MiniAudio/MiniAudioDevice.cpp"38#elif RETRO_AUDIODEVICE_OBOE39#include "Oboe/OboeAudioDevice.cpp"40#endif4142uint8 AudioDeviceBase::initializedAudioChannels = false;43uint8 AudioDeviceBase::audioState = 0;44uint8 AudioDeviceBase::audioFocus = 0;4546void AudioDeviceBase::Release()47{48// This is missing, meaning that the garbage collector will never reclaim stb_vorbis's buffer.49#if !RETRO_USE_ORIGINAL_CODE50stb_vorbis_close(vorbisInfo);51vorbisInfo = NULL;52#endif53}5455void AudioDeviceBase::ProcessAudioMixing(void *stream, int32 length)56{57SAMPLE_FORMAT *streamF = (SAMPLE_FORMAT *)stream;58SAMPLE_FORMAT *streamEndF = ((SAMPLE_FORMAT *)stream) + length;5960memset(stream, 0, length * sizeof(SAMPLE_FORMAT));6162for (int32 c = 0; c < CHANNEL_COUNT; ++c) {63ChannelInfo *channel = &channels[c];6465switch (channel->state) {66default:67case CHANNEL_IDLE: break;6869case CHANNEL_SFX: {70SAMPLE_FORMAT *sfxBuffer = &channel->samplePtr[channel->bufferPos];7172float volL = channel->volume, volR = channel->volume;73if (channel->pan < 0.0f)74volR = (1.0f + channel->pan) * channel->volume;75else76volL = (1.0f - channel->pan) * channel->volume;7778float panL = volL * engine.soundFXVolume;79float panR = volR * engine.soundFXVolume;8081uint32 speedPercent = 0;82SAMPLE_FORMAT *curStreamF = streamF;83while (curStreamF < streamEndF && streamF < streamEndF) {84// Perform linear interpolation.85SAMPLE_FORMAT sample;86#if !RETRO_USE_ORIGINAL_CODE87if (!sfxBuffer) // PROTECTION FOR v5U (and other mysterious crashes 👻)88sample = 0;89else90#endif91sample = (sfxBuffer[1] - sfxBuffer[0]) * linearInterpolationLookup[speedPercent / LINEAR_INTERPOLATION_LOOKUP_DIVISOR]92+ sfxBuffer[0];9394speedPercent += channel->speed;95sfxBuffer += FROM_FIXED(speedPercent);96channel->bufferPos += FROM_FIXED(speedPercent);97speedPercent %= TO_FIXED(1);9899curStreamF[0] += sample * panL;100curStreamF[1] += sample * panR;101curStreamF += 2;102103if (channel->bufferPos >= channel->sampleLength) {104if (channel->loop == (uint32)-1) {105channel->state = CHANNEL_IDLE;106channel->soundID = -1;107break;108}109else {110channel->bufferPos -= (uint32)channel->sampleLength;111channel->bufferPos += channel->loop;112113sfxBuffer = &channel->samplePtr[channel->bufferPos];114}115}116}117118break;119}120121case CHANNEL_STREAM: {122SAMPLE_FORMAT *streamBuffer = &channel->samplePtr[channel->bufferPos];123124float volL = channel->volume, volR = channel->volume;125if (channel->pan < 0.0f)126volR = (1.0f + channel->pan) * channel->volume;127else128volL = (1.0f - channel->pan) * channel->volume;129130float panL = volL * engine.streamVolume;131float panR = volR * engine.streamVolume;132133uint32 speedPercent = 0;134SAMPLE_FORMAT *curStreamF = streamF;135while (curStreamF < streamEndF && streamF < streamEndF) {136speedPercent += channel->speed;137int32 next = FROM_FIXED(speedPercent);138speedPercent %= TO_FIXED(1);139140curStreamF[0] += streamBuffer[0] * panL;141curStreamF[1] += streamBuffer[1] * panR;142curStreamF += 2;143144streamBuffer += next * 2;145channel->bufferPos += next * 2;146147if (channel->bufferPos >= channel->sampleLength) {148channel->bufferPos -= (uint32)channel->sampleLength;149150streamBuffer = &channel->samplePtr[channel->bufferPos];151152UpdateStreamBuffer(channel);153}154}155break;156}157158case CHANNEL_LOADING_STREAM: break;159}160}161}162163void AudioDeviceBase::InitAudioChannels()164{165for (int32 i = 0; i < CHANNEL_COUNT; ++i) {166channels[i].soundID = -1;167channels[i].state = CHANNEL_IDLE;168}169170// Compute a lookup table of floating-point linear interpolation delta scales,171// to speed-up the process of converting from fixed-point to floating-point.172for (int32 i = 0; i < LINEAR_INTERPOLATION_LOOKUP_LENGTH; ++i) linearInterpolationLookup[i] = i / (float)LINEAR_INTERPOLATION_LOOKUP_LENGTH;173174GEN_HASH_MD5("Stream Channel 0", sfxList[SFX_COUNT - 1].hash);175sfxList[SFX_COUNT - 1].scope = SCOPE_GLOBAL;176sfxList[SFX_COUNT - 1].maxConcurrentPlays = 1;177sfxList[SFX_COUNT - 1].length = MIX_BUFFER_SIZE;178AllocateStorage((void **)&sfxList[SFX_COUNT - 1].buffer, MIX_BUFFER_SIZE * sizeof(SAMPLE_FORMAT), DATASET_MUS, false);179180initializedAudioChannels = true;181}182183void RSDK::UpdateStreamBuffer(ChannelInfo *channel)184{185int32 bufferRemaining = MIX_BUFFER_SIZE;186float *buffer = channel->samplePtr;187188for (int32 s = 0; s < MIX_BUFFER_SIZE;) {189int32 samples = stb_vorbis_get_samples_float_interleaved(vorbisInfo, 2, buffer, bufferRemaining) * 2;190if (!samples) {191if (channel->loop == 1 && stb_vorbis_seek_frame(vorbisInfo, streamLoopPoint)) {192// we're looping & the seek was successful, get more samples193}194else {195channel->state = CHANNEL_IDLE;196channel->soundID = -1;197memset(buffer, 0, sizeof(float) * bufferRemaining);198199break;200}201}202203s += samples;204buffer += samples;205bufferRemaining = MIX_BUFFER_SIZE - s;206}207208for (int32 i = 0; i < MIX_BUFFER_SIZE; ++i) channel->samplePtr[i] *= 0.5f;209}210211void RSDK::LoadStream(ChannelInfo *channel)212{213if (channel->state != CHANNEL_LOADING_STREAM)214return;215216stb_vorbis_close(vorbisInfo);217218FileInfo info;219InitFileInfo(&info);220221if (LoadFile(&info, streamFilePath, FMODE_RB)) {222streamBufferSize = info.fileSize;223streamBuffer = NULL;224AllocateStorage((void **)&streamBuffer, info.fileSize, DATASET_MUS, false);225ReadBytes(&info, streamBuffer, streamBufferSize);226CloseFile(&info);227228if (streamBufferSize > 0) {229vorbisAlloc.alloc_buffer_length_in_bytes = 512 * 1024; // 512KiB230AllocateStorage((void **)&vorbisAlloc.alloc_buffer, 512 * 1024, DATASET_MUS, false);231232vorbisInfo = stb_vorbis_open_memory(streamBuffer, streamBufferSize, NULL, &vorbisAlloc);233if (vorbisInfo) {234if (streamStartPos)235stb_vorbis_seek(vorbisInfo, streamStartPos);236UpdateStreamBuffer(channel);237238channel->state = CHANNEL_STREAM;239}240}241}242243if (channel->state == CHANNEL_LOADING_STREAM)244channel->state = CHANNEL_IDLE;245}246247int32 RSDK::PlayStream(const char *filename, uint32 slot, uint32 startPos, uint32 loopPoint, bool32 loadASync)248{249if (!engine.streamsEnabled)250return -1;251252if (slot >= CHANNEL_COUNT) {253for (int32 c = 0; c < CHANNEL_COUNT && slot >= CHANNEL_COUNT; ++c) {254if (channels[c].soundID == -1 && channels[c].state != CHANNEL_LOADING_STREAM) {255slot = c;256}257}258259// as a last resort, run through all channels260// pick the channel closest to being finished261if (slot >= CHANNEL_COUNT) {262uint32 len = 0xFFFFFFFF;263for (int32 c = 0; c < CHANNEL_COUNT; ++c) {264if (channels[c].sampleLength < len && channels[c].state != CHANNEL_LOADING_STREAM) {265slot = c;266len = (uint32)channels[c].sampleLength;267}268}269}270}271272if (slot >= CHANNEL_COUNT)273return -1;274275ChannelInfo *channel = &channels[slot];276277LockAudioDevice();278279channel->soundID = 0xFF;280channel->loop = loopPoint != 0;281channel->priority = 0xFF;282channel->state = CHANNEL_LOADING_STREAM;283channel->pan = 0.0f;284channel->volume = 1.0f;285channel->sampleLength = sfxList[SFX_COUNT - 1].length;286channel->samplePtr = sfxList[SFX_COUNT - 1].buffer;287channel->bufferPos = 0;288channel->speed = TO_FIXED(1);289290sprintf_s(streamFilePath, sizeof(streamFilePath), "Data/Music/%s", filename);291streamStartPos = startPos;292streamLoopPoint = loopPoint;293294AudioDevice::HandleStreamLoad(channel, loadASync);295296UnlockAudioDevice();297298return slot;299}300301#define WAV_SIG_HEADER (0x46464952) // RIFF302#define WAV_SIG_DATA (0x61746164) // data303304void RSDK::LoadSfxToSlot(char *filename, uint8 slot, uint8 plays, uint8 scope)305{306FileInfo info;307InitFileInfo(&info);308309char fullFilePath[0x80];310sprintf_s(fullFilePath, sizeof(fullFilePath), "Data/SoundFX/%s", filename);311312RETRO_HASH_MD5(hash);313GEN_HASH_MD5(filename, hash);314315if (LoadFile(&info, fullFilePath, FMODE_RB)) {316HASH_COPY_MD5(sfxList[slot].hash, hash);317sfxList[slot].scope = scope;318sfxList[slot].maxConcurrentPlays = plays;319320uint8 type = fullFilePath[strlen(fullFilePath) - 1];321if (type == 'v' || type == 'V') { // A very loose way of checking that we're trying to load a '.wav' file.322uint32 signature = ReadInt32(&info, false);323324if (signature == WAV_SIG_HEADER) {325ReadInt32(&info, false); // chunk size326ReadInt32(&info, false); // WAVE327ReadInt32(&info, false); // FMT328#if !RETRO_USE_ORIGINAL_CODE329int32 chunkSize = ReadInt32(&info, false); // chunk size330#else331ReadInt32(&info, false); // chunk size332#endif333ReadInt16(&info); // audio format334ReadInt16(&info); // channels335ReadInt32(&info, false); // sample rate336ReadInt32(&info, false); // bytes per sec337ReadInt16(&info); // block align338ReadInt16(&info); // format339340Seek_Set(&info, 34);341uint16 sampleBits = ReadInt16(&info);342343#if !RETRO_USE_ORIGINAL_CODE344// Original code added to help fix some issues345Seek_Set(&info, 20 + chunkSize);346#endif347348// Find the data header349int32 loop = 0;350while (true) {351signature = ReadInt32(&info, false);352if (signature == WAV_SIG_DATA)353break;354355loop += 4;356if (loop >= 0x40) {357if (loop != 0x100) {358CloseFile(&info);359// There's a bug here: `sfxList[id].scope` is not reset to `SCOPE_NONE`,360// meaning that the game will consider the SFX valid and allow it to be played.361// This can cause a crash because the SFX is incomplete.362#if !RETRO_USE_ORIGINAL_CODE363PrintLog(PRINT_ERROR, "Unable to read sfx: %s", filename);364#endif365return;366}367else {368break;369}370}371}372373uint32 length = ReadInt32(&info, false);374if (sampleBits == 16)375length /= 2;376377AllocateStorage((void **)&sfxList[slot].buffer, sizeof(float) * length, DATASET_SFX, false);378sfxList[slot].length = length;379380// Convert the sample data to F32 format381float *buffer = (float *)sfxList[slot].buffer;382if (sampleBits == 8) {383// 8-bit sample. Convert from U8 to S8, and then from S8 to F32.384for (int32 s = 0; s < length; ++s) {385int32 sample = ReadInt8(&info);386*buffer++ = (sample - 0x80) / (float)0x80;387}388}389else {390// 16-bit sample. Convert from S16 to F32.391for (int32 s = 0; s < length; ++s) {392// For some reason, the game performs sign-extension manually here.393// Note that this is different from the 8-bit format's unsigned-to-signed conversion.394int32 sample = (uint16)ReadInt16(&info);395396if (sample > 0x7FFF)397sample = (sample & 0x7FFF) - 0x8000;398399*buffer++ = (sample / (float)0x8000) * 0.75f;400}401}402}403#if !RETRO_USE_ORIGINAL_CODE404else {405PrintLog(PRINT_ERROR, "Invalid header in sfx: %s", filename);406}407#endif408}409#if !RETRO_USE_ORIGINAL_CODE410else {411// what the412PrintLog(PRINT_ERROR, "Could not find header in sfx: %s", filename);413}414#endif415}416#if !RETRO_USE_ORIGINAL_CODE417else {418PrintLog(PRINT_ERROR, "Unable to open sfx: %s", filename);419}420#endif421422CloseFile(&info);423}424425void RSDK::LoadSfx(char *filename, uint8 plays, uint8 scope)426{427// Find an empty sound slot.428uint16 id = -1;429for (uint32 i = 0; i < SFX_COUNT; ++i) {430if (sfxList[i].scope == SCOPE_NONE) {431id = i;432break;433}434}435436if (id != (uint16)-1)437LoadSfxToSlot(filename, id, plays, scope);438}439440int32 RSDK::PlaySfx(uint16 sfx, uint32 loopPoint, uint32 priority)441{442if (sfx >= SFX_COUNT || !sfxList[sfx].scope)443return -1;444445uint8 count = 0;446for (int32 c = 0; c < CHANNEL_COUNT; ++c) {447if (channels[c].soundID == sfx)448++count;449}450451int8 slot = -1;452// if we've hit the max, replace the oldest one453if (count >= sfxList[sfx].maxConcurrentPlays) {454int32 highestStackID = 0;455for (int32 c = 0; c < CHANNEL_COUNT; ++c) {456int32 stackID = sfxList[sfx].playCount - channels[c].playIndex;457if (stackID > highestStackID && channels[c].soundID == sfx) {458slot = c;459highestStackID = stackID;460}461}462}463464// if we don't have a slot yet, try to pick any channel that's not currently playing465for (int32 c = 0; c < CHANNEL_COUNT && slot < 0; ++c) {466if (channels[c].soundID == -1 && channels[c].state != CHANNEL_LOADING_STREAM) {467slot = c;468}469}470471// as a last resort, run through all channels472// pick the channel closest to being finished AND with lower priority473if (slot < 0) {474uint32 len = 0xFFFFFFFF;475for (int32 c = 0; c < CHANNEL_COUNT; ++c) {476if (channels[c].sampleLength < len && priority > channels[c].priority && channels[c].state != CHANNEL_LOADING_STREAM) {477slot = c;478len = (uint32)channels[c].sampleLength;479}480}481}482483if (slot == -1)484return -1;485486LockAudioDevice();487488channels[slot].state = CHANNEL_SFX;489channels[slot].bufferPos = 0;490channels[slot].samplePtr = sfxList[sfx].buffer;491channels[slot].sampleLength = sfxList[sfx].length;492channels[slot].volume = 1.0f;493channels[slot].pan = 0.0f;494channels[slot].speed = TO_FIXED(1);495channels[slot].soundID = sfx;496if (loopPoint >= 2)497channels[slot].loop = loopPoint;498else499channels[slot].loop = loopPoint - 1;500channels[slot].priority = priority;501channels[slot].playIndex = sfxList[sfx].playCount++;502503UnlockAudioDevice();504505return slot;506}507508void RSDK::SetChannelAttributes(uint8 channel, float volume, float panning, float speed)509{510if (channel < CHANNEL_COUNT) {511volume = fminf(4.0f, volume);512volume = fmaxf(0.0f, volume);513channels[channel].volume = volume;514515panning = fminf(1.0f, panning);516panning = fmaxf(-1.0f, panning);517channels[channel].pan = panning;518519if (speed > 0.0f)520channels[channel].speed = (int32)(speed * TO_FIXED(1));521else if (speed == 1.0f)522channels[channel].speed = TO_FIXED(1);523}524}525526uint32 RSDK::GetChannelPos(uint32 channel)527{528if (channel >= CHANNEL_COUNT)529return 0;530531if (channels[channel].state == CHANNEL_SFX)532return channels[channel].bufferPos;533534if (channels[channel].state == CHANNEL_STREAM) {535if (!vorbisInfo->current_loc_valid || vorbisInfo->current_loc < 0)536return 0;537538return vorbisInfo->current_loc;539}540541return 0;542}543544double RSDK::GetVideoStreamPos()545{546if (channels[0].state == CHANNEL_STREAM && AudioDevice::audioState && AudioDevice::initializedAudioChannels && vorbisInfo->current_loc_valid) {547return vorbisInfo->current_loc / (double)AUDIO_FREQUENCY;548}549550return -1.0;551}552553void RSDK::ClearStageSfx()554{555LockAudioDevice();556557for (int32 c = 0; c < CHANNEL_COUNT; ++c) {558if (channels[c].state == CHANNEL_SFX || channels[c].state == (CHANNEL_SFX | CHANNEL_PAUSED)) {559channels[c].soundID = -1;560channels[c].state = CHANNEL_IDLE;561}562}563564// Unload stage SFX565for (int32 s = 0; s < SFX_COUNT; ++s) {566if (sfxList[s].scope >= SCOPE_STAGE) {567MEM_ZERO(sfxList[s]);568sfxList[s].scope = SCOPE_NONE;569}570}571572UnlockAudioDevice();573}574575#if RETRO_USE_MOD_LOADER576void RSDK::ClearGlobalSfx()577{578LockAudioDevice();579580for (int32 c = 0; c < CHANNEL_COUNT; ++c) {581if (channels[c].state == CHANNEL_SFX || channels[c].state == (CHANNEL_SFX | CHANNEL_PAUSED)) {582channels[c].soundID = -1;583channels[c].state = CHANNEL_IDLE;584}585}586587// Unload global SFX588for (int32 s = 0; s < SFX_COUNT; ++s) {589// clear global sfx (do NOT clear the stream channel 0 slot)590if (sfxList[s].scope == SCOPE_GLOBAL && s != SFX_COUNT - 1) {591MEM_ZERO(sfxList[s]);592sfxList[s].scope = SCOPE_NONE;593}594}595596UnlockAudioDevice();597}598#endif599600601