From bbe61e37f2f82f2ba14fd9b5676d42212b3c2e4d Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Fri, 24 Jan 2020 22:04:16 +0000 Subject: [PATCH] Add support for audio renderer services Audren is used by many games and homebrews to make use of more advanced audio features than audout. --- app/CMakeLists.txt | 4 + .../services/audren/IAudioRenderer.cpp | 186 ++++++++++++++++++ .../skyline/services/audren/IAudioRenderer.h | 135 +++++++++++++ .../services/audren/IAudioRendererManager.cpp | 108 ++++++++++ .../services/audren/IAudioRendererManager.h | 26 +++ .../main/cpp/skyline/services/audren/effect.h | 45 +++++ .../skyline/services/audren/memoryPool.cpp | 11 ++ .../cpp/skyline/services/audren/memoryPool.h | 54 +++++ .../skyline/services/audren/revisionInfo.h | 83 ++++++++ .../cpp/skyline/services/audren/voice.cpp | 110 +++++++++++ .../main/cpp/skyline/services/audren/voice.h | 138 +++++++++++++ .../main/cpp/skyline/services/base_service.h | 3 + .../main/cpp/skyline/services/serviceman.cpp | 4 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences.xml | 12 ++ 15 files changed, 922 insertions(+) create mode 100644 app/src/main/cpp/skyline/services/audren/IAudioRenderer.cpp create mode 100644 app/src/main/cpp/skyline/services/audren/IAudioRenderer.h create mode 100644 app/src/main/cpp/skyline/services/audren/IAudioRendererManager.cpp create mode 100644 app/src/main/cpp/skyline/services/audren/IAudioRendererManager.h create mode 100644 app/src/main/cpp/skyline/services/audren/effect.h create mode 100644 app/src/main/cpp/skyline/services/audren/memoryPool.cpp create mode 100644 app/src/main/cpp/skyline/services/audren/memoryPool.h create mode 100644 app/src/main/cpp/skyline/services/audren/revisionInfo.h create mode 100644 app/src/main/cpp/skyline/services/audren/voice.cpp create mode 100644 app/src/main/cpp/skyline/services/audren/voice.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b030c5ce..544eb243 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -53,6 +53,10 @@ add_library(skyline SHARED ${source_DIR}/skyline/services/sm/sm.cpp ${source_DIR}/skyline/services/fatal/fatal.cpp ${source_DIR}/skyline/services/audout/audout.cpp + ${source_DIR}/skyline/services/audren/IAudioRendererManager.cpp + ${source_DIR}/skyline/services/audren/IAudioRenderer.cpp + ${source_DIR}/skyline/services/audren/voice.cpp + ${source_DIR}/skyline/services/audren/memoryPool.cpp ${source_DIR}/skyline/services/set/sys.cpp ${source_DIR}/skyline/services/apm/apm.cpp ${source_DIR}/skyline/services/am/applet.cpp diff --git a/app/src/main/cpp/skyline/services/audren/IAudioRenderer.cpp b/app/src/main/cpp/skyline/services/audren/IAudioRenderer.cpp new file mode 100644 index 00000000..f1431bc8 --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/IAudioRenderer.cpp @@ -0,0 +1,186 @@ +#include "IAudioRenderer.h" +#include + +namespace skyline::service::audren { + IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParams ¶ms) : releaseEvent(std::make_shared(state)), rendererParams(params), BaseService(state, manager, false, Service::IAudioRenderer, { + {0x0, SFUNC(IAudioRenderer::GetSampleRate)}, + {0x1, SFUNC(IAudioRenderer::GetSampleCount)}, + {0x2, SFUNC(IAudioRenderer::GetMixBufferCount)}, + {0x3, SFUNC(IAudioRenderer::GetState)}, + {0x4, SFUNC(IAudioRenderer::RequestUpdate)}, + {0x5, SFUNC(IAudioRenderer::Start)}, + {0x6, SFUNC(IAudioRenderer::Stop)}, + {0x7, SFUNC(IAudioRenderer::QuerySystemEvent)}, + }) { + track = state.audio->OpenTrack(audio::constant::ChannelCount, params.sampleRate, [this]() { this->releaseEvent->Signal(); }); + track->Start(); + + samplesPerBuffer = state.settings->GetInt("audren_buffer_size"); + memoryPoolCount = rendererParams.effectCount + rendererParams.voiceCount * 4; + memoryPools.resize(memoryPoolCount); + effects.resize(rendererParams.effectCount); + voices.resize(rendererParams.voiceCount, Voice(state)); + + // Fill track with empty samples that we will triple buffer + track->AppendBuffer(std::vector(), 0); + track->AppendBuffer(std::vector(), 1); + track->AppendBuffer(std::vector(), 2); + } + + IAudioRenderer::~IAudioRenderer() { + state.audio->CloseTrack(track); + } + + void IAudioRenderer::GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.Push(rendererParams.sampleRate); + } + + void IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.Push(rendererParams.sampleCount); + } + + void IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.Push(rendererParams.subMixCount); + } + + void IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.Push(static_cast(playbackState)); + } + + void IAudioRenderer::RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + u64 inputAddress = request.inputBuf.at(0).address; + + auto inputHeader = state.process->GetObject(inputAddress); + revisionInfo.SetUserRevision(inputHeader.revision); + inputAddress += sizeof(UpdateDataHeader); + inputAddress += inputHeader.behaviorSize; // Unused + + std::vector memoryPoolsIn(memoryPoolCount); + state.process->ReadMemory(memoryPoolsIn.data(), inputAddress, memoryPoolCount * sizeof(MemoryPoolIn)); + inputAddress += inputHeader.memoryPoolSize; + + for (int i = 0; i < memoryPoolsIn.size(); i++) + memoryPools[i].ProcessInput(memoryPoolsIn[i]); + + inputAddress += inputHeader.voiceResourceSize; + std::vector voicesIn(rendererParams.voiceCount); + state.process->ReadMemory(voicesIn.data(), inputAddress, rendererParams.voiceCount * sizeof(VoiceIn)); + inputAddress += inputHeader.voiceSize; + + for (int i = 0; i < voicesIn.size(); i++) + voices[i].ProcessInput(voicesIn[i]); + + std::vector effectsIn(rendererParams.effectCount); + state.process->ReadMemory(effectsIn.data(), inputAddress, rendererParams.effectCount * sizeof(EffectIn)); + + for (int i = 0; i < effectsIn.size(); i++) + effects[i].ProcessInput(effectsIn[i]); + + UpdateAudio(); + + UpdateDataHeader outputHeader { + .revision = constant::RevMagic, + .behaviorSize = 0xb0, + .memoryPoolSize = (rendererParams.effectCount + rendererParams.voiceCount * 4) * static_cast(sizeof(MemoryPoolOut)), + .voiceSize = rendererParams.voiceCount * static_cast(sizeof(VoiceOut)), + .effectSize = rendererParams.effectCount * static_cast(sizeof(EffectOut)), + .sinkSize = rendererParams.sinkCount * 0x20, + .performanceManagerSize = 0x10, + .elapsedFrameCountInfoSize = 0x0 + }; + + if (revisionInfo.ElapsedFrameCountSupported()) + outputHeader.elapsedFrameCountInfoSize = 0x10; + + outputHeader.totalSize = sizeof(UpdateDataHeader) + + outputHeader.behaviorSize + + outputHeader.memoryPoolSize + + outputHeader.voiceSize + + outputHeader.effectSize + + outputHeader.sinkSize + + outputHeader.performanceManagerSize + + outputHeader.elapsedFrameCountInfoSize; + + u64 outputAddress = request.outputBuf.at(0).address; + + state.process->WriteMemory(outputHeader, outputAddress); + outputAddress += sizeof(UpdateDataHeader); + + for (auto &memoryPool : memoryPools) { + state.process->WriteMemory(memoryPool.output, outputAddress); + outputAddress += sizeof(MemoryPoolOut); + } + + for (auto &voice : voices) { + state.process->WriteMemory(voice.output, outputAddress); + outputAddress += sizeof(VoiceOut); + } + + for (auto &effect : effects) { + state.process->WriteMemory(effect.output, outputAddress); + outputAddress += sizeof(EffectOut); + } + } + + void IAudioRenderer::UpdateAudio() { + std::vector released = track->GetReleasedBuffers(2); + + for (auto &tag : released) { + MixFinalBuffer(); + track->AppendBuffer(sampleBuffer, tag); + } + } + + void IAudioRenderer::MixFinalBuffer() { + int setIndex = 0; + sampleBuffer.resize(samplesPerBuffer * audio::constant::ChannelCount); + + for (auto &voice : voices) { + if (!voice.Playable()) + continue; + + int bufferOffset = 0; + int pendingSamples = samplesPerBuffer; + + while (pendingSamples > 0) { + int voiceBufferSize = 0; + int voiceBufferOffset = 0; + std::vector &voiceSamples = voice.GetBufferData(pendingSamples, voiceBufferOffset, voiceBufferSize); + + if (voiceBufferSize == 0) + break; + + pendingSamples -= voiceBufferSize / audio::constant::ChannelCount; + + for (int i = voiceBufferOffset; i < voiceBufferOffset + voiceBufferSize; i++) { + if (setIndex == bufferOffset) { + sampleBuffer[bufferOffset] = static_cast(std::clamp(static_cast(static_cast(voiceSamples[i]) * + voice.volume), static_cast(std::numeric_limits::min()), static_cast(std::numeric_limits::max()))); + + setIndex++; + } else { + sampleBuffer[bufferOffset] += static_cast(std::clamp(static_cast(sampleBuffer[voiceSamples[i]]) + + static_cast(static_cast(voiceSamples[i]) * voice.volume), + static_cast(std::numeric_limits::min()), static_cast(std::numeric_limits::max()))); + } + + bufferOffset++; + } + } + } + } + + void IAudioRenderer::Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + playbackState = audio::AudioOutState::Started; + } + + void IAudioRenderer::Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + playbackState = audio::AudioOutState::Stopped; + } + + void IAudioRenderer::QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto handle = state.process->InsertItem(releaseEvent); + state.logger->Debug("Audren Buffer Release Event Handle: 0x{:X}", handle); + response.copyHandles.push_back(handle); + } +} diff --git a/app/src/main/cpp/skyline/services/audren/IAudioRenderer.h b/app/src/main/cpp/skyline/services/audren/IAudioRenderer.h new file mode 100644 index 00000000..90720de6 --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/IAudioRenderer.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include +#include +#include +#include "memoryPool.h" +#include "effect.h" +#include "voice.h" +#include "revisionInfo.h" + +namespace skyline::service::audren { + namespace constant { + constexpr int BufferAlignment = 0x40; //!< The alignment for all audren buffers + } + /** + * @brief The parameters used to configure an IAudioRenderer + */ + struct AudioRendererParams { + u32 sampleRate; //!< The sample rate to use for the renderer + u32 sampleCount; //!< The buffer sample count + u32 mixBufferCount; //!< The amount of mix buffers to use + u32 subMixCount; //!< The amount of sub mixes to use + u32 voiceCount; //!< The amount of voices to use + u32 sinkCount; //!< The amount of sinks to use + u32 effectCount; //!< The amount of effects to use + u32 performanceManagerCount; //!< The amount of performance managers to use + u32 voiceDropEnable; //!< Whether to enable voice drop + u32 splitterCount; //!< The amount of splitters to use + u32 splitterDestinationDataCount; //!< The amount of splitter destination outputs to use + u32 _unk0_; + u32 revision; //!< The revision of audren to use + }; + static_assert(sizeof(AudioRendererParams) == 0x34); + + /** + * @brief Header containing information about the software side audren implementation + */ + struct UpdateDataHeader { + u32 revision; //!< Revision of the software implementation + u32 behaviorSize; //!< The total size of the behaviour info + u32 memoryPoolSize; //!< The total size of all MemoryPoolIn structs + u32 voiceSize; //!< The total size of all VoiceIn structs + u32 voiceResourceSize; //!< The total size of the voice resources + u32 effectSize; //!< The total size of all EffectIn structs + u32 mixSize; //!< The total size of all mixer descriptors in the input + u32 sinkSize; //!< The total size of all sink descriptors in the input + u32 performanceManagerSize; //!< The total size of all performance manager descriptors in the input + u32 _unk0_; + u32 elapsedFrameCountInfoSize; //!< The total size of all the elapsed frame info + u32 _unk1_[4]; + u32 totalSize; //!< The total size of the whole input + }; + static_assert(sizeof(UpdateDataHeader) == 0x40); + + /** + * @brief IAudioRenderer is used to control an audio renderer output (https://switchbrew.org/wiki/Audio_services#IAudioRenderer) + */ + class IAudioRenderer : public BaseService { + private: + std::shared_ptr track; //!< The audio track associated with the audio renderer + AudioRendererParams rendererParams; //!< The parameters to use for the renderer + std::shared_ptr releaseEvent; //!< The KEvent that is signalled when a buffer has been released + std::vector memoryPools; //!< An vector of all memory pools that the guest may need + std::vector effects; //!< An vector of all effects that the guest may need + std::vector voices; //!< An vector of all voices that the guest may need + std::vector sampleBuffer; //!< The final output data that is appended to the stream + RevisionInfo revisionInfo{}; //!< Stores info about supported features for the audren revision used + + audio::AudioOutState playbackState{audio::AudioOutState::Stopped}; //!< The current state of playback + size_t memoryPoolCount{}; //!< The amount of memory pools the guest may need + int samplesPerBuffer{}; //!< The amount of samples each appended buffer should contain + + /** + * @brief Obtains new sample data from voices and mixes it together into the sample buffer + */ + void MixFinalBuffer(); + + /** + * @brief Appends all released buffers with new mixed sample data + */ + void UpdateAudio(); + + public: + /** + * @param params The parameters to use for rendering + */ + IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParams ¶ms); + + /** + * @brief Closes the audio track + */ + ~IAudioRenderer(); + + /** + * @brief Returns the sample rate (https://switchbrew.org/wiki/Audio_services#GetSampleRate) + */ + void GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Returns the sample count (https://switchbrew.org/wiki/Audio_services#GetSampleCount) + */ + void GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Returns the number of mix buffers (https://switchbrew.org/wiki/Audio_services#GetMixBufferCount) + */ + void GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Returns the state of the renderer (https://switchbrew.org/wiki/Audio_services#GetAudioRendererState) (stubbed)? + */ + void GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Updates the audio renderer state and appends new data to playback buffers + */ + void RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Start the audio stream from the renderer + */ + void Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Stop the audio stream from the renderer + */ + void Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Returns a handle to the sample release KEvent + */ + void QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + }; +} diff --git a/app/src/main/cpp/skyline/services/audren/IAudioRendererManager.cpp b/app/src/main/cpp/skyline/services/audren/IAudioRendererManager.cpp new file mode 100644 index 00000000..0aded39e --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/IAudioRendererManager.cpp @@ -0,0 +1,108 @@ +#include "IAudioRenderer.h" +#include "IAudioRendererManager.h" +#include + +namespace skyline::service::audren { + IAudioRendererManager::IAudioRendererManager(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::IAudioRendererManager, { + {0x0, SFUNC(IAudioRendererManager::OpenAudioRenderer)}, + {0x1, SFUNC(IAudioRendererManager::GetAudioRendererWorkBufferSize)} + }) {} + + void IAudioRendererManager::OpenAudioRenderer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + AudioRendererParams params = request.Pop(); + + state.logger->Debug("IAudioRendererManager: Opening a rev {} IAudioRenderer with sample rate: {}, voice count: {}, effect count: {}", + ExtractVersionFromRevision(params.revision), params.sampleRate, params.voiceCount, params.effectCount); + + manager.RegisterService(std::make_shared(state, manager, params), session, response); + + } + + void IAudioRendererManager::GetAudioRendererWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + AudioRendererParams params = request.Pop(); + + RevisionInfo revisionInfo; + revisionInfo.SetUserRevision(params.revision); + + u32 totalMixCount = params.subMixCount + 1; + + i64 size = utils::AlignUp(params.mixBufferCount * 4, constant::BufferAlignment) + + params.subMixCount * 0x400 + + totalMixCount * 0x940 + + params.voiceCount * 0x3F0 + + utils::AlignUp(totalMixCount * 8, 16) + + utils::AlignUp(params.voiceCount * 8, 16) + + utils::AlignUp(((params.sinkCount + params.subMixCount) * 0x3C0 + params.sampleCount * 4) * (params.mixBufferCount + 6), constant::BufferAlignment) + + (params.sinkCount + params.subMixCount) * 0x2C0 + + (params.effectCount + params.voiceCount * 4) * 0x30 + + 0x50; + + if (revisionInfo.SplitterSupported()) { + i32 nodeStateWorkSize = utils::AlignUp(totalMixCount, constant::BufferAlignment); + + if (nodeStateWorkSize < 0) + nodeStateWorkSize |= 7; + + nodeStateWorkSize = 4 * (totalMixCount * totalMixCount) + 12 * totalMixCount + 2 * (nodeStateWorkSize / 8); + + i32 edgeMatrixWorkSize = utils::AlignUp(totalMixCount * totalMixCount, constant::BufferAlignment); + + if (edgeMatrixWorkSize < 0) + edgeMatrixWorkSize |= 7; + + edgeMatrixWorkSize /= 8; + size += utils::AlignUp(edgeMatrixWorkSize + nodeStateWorkSize, 16); + } + + i64 splitterWorkSize = 0; + + if (revisionInfo.SplitterSupported()) { + splitterWorkSize += params.splitterDestinationDataCount * 0xE0 + + params.splitterCount * 0x20; + + if (revisionInfo.SplitterBugFixed()) + splitterWorkSize += utils::AlignUp(4 * params.splitterDestinationDataCount, 16); + } + size = params.sinkCount * 0x170 + + (params.sinkCount + params.subMixCount) * 0x280 + + params.effectCount * 0x4C0 + + ((size + splitterWorkSize + 0x30 * params.effectCount + (4 * params.voiceCount) + 0x8F) & ~0x3Fl) + + ((params.voiceCount << 8) | 0x40); + + if (params.performanceManagerCount > 0) { + i64 performanceMetricsBufferSize; + + if (revisionInfo.UsesPerformanceMetricDataFormatV2()) { + performanceMetricsBufferSize = (params.voiceCount + + params.effectCount + + totalMixCount + + params.sinkCount) + 0x990; + } else { + performanceMetricsBufferSize = ((static_cast((params.voiceCount + + params.effectCount + + totalMixCount + + params.sinkCount)) << 32) >> 0x1C) + 0x658; + } + + size += (performanceMetricsBufferSize * (params.performanceManagerCount + 1) + 0xFF) & ~0x3Fl; + } + + if (revisionInfo.VaradicCommandBufferSizeSupported()) { + size += params.effectCount * 0x840 + + params.subMixCount * 0x5A38 + + params.sinkCount * 0x148 + + params.splitterDestinationDataCount * 0x540 + + (params.splitterCount * 0x68 + 0x2E0) * params.voiceCount + + ((params.voiceCount + params.subMixCount + params.effectCount + params.sinkCount + 0x65) << 6) + + 0x3F8 + + 0x7E; + } else { + size += 0x1807E; + } + + size = utils::AlignUp(size, 0x1000); + + state.logger->Debug("IAudioRendererManager: Work buffer size: 0x{:X}", size); + response.Push(size); + } +} diff --git a/app/src/main/cpp/skyline/services/audren/IAudioRendererManager.h b/app/src/main/cpp/skyline/services/audren/IAudioRendererManager.h new file mode 100644 index 00000000..e3e7ca5a --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/IAudioRendererManager.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +namespace skyline::service::audren { + /** + * @brief audren:u or IAudioRendererManager is used to manage audio renderer outputs (https://switchbrew.org/wiki/Audio_services#audren:u) + */ + class IAudioRendererManager : public BaseService { + public: + IAudioRendererManager(const DeviceState &state, ServiceManager &manager); + + /** + * @brief Creates a new audrenU::IAudioRenderer object and returns a handle to it + */ + void OpenAudioRenderer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Calculates the size of the buffer the guest needs to allocate for audren + */ + void GetAudioRendererWorkBufferSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + }; +} diff --git a/app/src/main/cpp/skyline/services/audren/effect.h b/app/src/main/cpp/skyline/services/audren/effect.h new file mode 100644 index 00000000..b53ba25a --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/effect.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace skyline::service::audren { + /** + * @brief This enumerates various states an effect can be in + */ + enum class EffectState : u8 { + None = 0, //!< The effect isn't being used + New = 1 //!< The effect is new + }; + + /** + * @brief This is in input containing information on what effects to use on an audio stream + */ + struct EffectIn { + u8 _unk0_; + u8 isNew; //!< Whether the effect was used in the previous samples + u8 _unk1_[0xbe]; + }; + static_assert(sizeof(EffectIn) == 0xc0); + + /** + * @brief This is returned to inform the guest of the state of an effect + */ + struct EffectOut { + EffectState state; //!< The state of the effect + u8 _pad0_[15]; + }; + static_assert(sizeof(EffectOut) == 0x10); + + /** + * @brief The Effect class stores the state of audio post processing effects + */ + class Effect { + public: + EffectOut output{}; //!< The current output state + + inline void ProcessInput(const EffectIn &input) { + if (input.isNew) + output.state = EffectState::New; + } + }; +} diff --git a/app/src/main/cpp/skyline/services/audren/memoryPool.cpp b/app/src/main/cpp/skyline/services/audren/memoryPool.cpp new file mode 100644 index 00000000..79b2da1a --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/memoryPool.cpp @@ -0,0 +1,11 @@ +#include +#include "memoryPool.h" + +namespace skyline::service::audren { + void MemoryPool::ProcessInput(const MemoryPoolIn &input) { + if (input.state == MemoryPoolState::RequestAttach) + output.state = MemoryPoolState::Attached; + else if (input.state == MemoryPoolState::RequestDetach) + output.state = MemoryPoolState::Detached; + } +} diff --git a/app/src/main/cpp/skyline/services/audren/memoryPool.h b/app/src/main/cpp/skyline/services/audren/memoryPool.h new file mode 100644 index 00000000..ba7f83d6 --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/memoryPool.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +namespace skyline::service::audren { + /** + * @brief This enumerates various states a memory pool can be in + */ + enum class MemoryPoolState : u32 { + Invalid = 0, //!< The memory pool is invalid + Unknown = 1, //!< The memory pool is in an unknown state + RequestDetach = 2, //!< The memory pool should be detached from + Detached = 3, //!< The memory pool has been detached from + RequestAttach = 4, //!< The memory pool should be attached to + Attached = 5, //!< The memory pool has been attached to + Released = 6 //!< The memory pool has been released + }; + + /** + * @brief This is in input containing information about a memory pool for use by the dsp + */ + struct MemoryPoolIn { + u64 address; //!< The address of the memory pool + u64 size; //!< The size of the memory pool + MemoryPoolState state; //!< The state that is requested for the memory pool + u32 _unk0_; + u64 _unk1_; + }; + static_assert(sizeof(MemoryPoolIn) == 0x20); + + /** + * @brief This is returned to inform the guest of the state of a memory pool + */ + struct MemoryPoolOut { + MemoryPoolState state = MemoryPoolState::Detached; //!< The state of the memory pool + u32 _unk0_; + u64 _unk1_; + }; + static_assert(sizeof(MemoryPoolOut) == 0x10); + + /** + * @brief The MemoryPool class stores the state of a memory pool + */ + class MemoryPool { + public: + MemoryPoolOut output{}; //!< The current output state + + /** + * @brief Processes the input memory pool data from the guest and sets internal data based off it + * @param input The input data struct from guest + */ + void ProcessInput(const MemoryPoolIn &input); + }; +} diff --git a/app/src/main/cpp/skyline/services/audren/revisionInfo.h b/app/src/main/cpp/skyline/services/audren/revisionInfo.h new file mode 100644 index 00000000..b5fc6a5b --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/revisionInfo.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +namespace skyline::service::audren { + namespace constant { + constexpr u32 SupportedRevision = 7; //!< The audren revision our implementation supports + constexpr u32 Rev0Magic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); //!< The HOS 1.0 revision magic + constexpr u32 RevMagic = Rev0Magic + (SupportedRevision << 24); //!< The revision magic for our supported revision + + namespace supportTags { + constexpr u32 Splitter = 2; //!< The revision splitter support was added + constexpr u32 SplitterBugFix = 5; //!< The revision the splitter buffer was made aligned + constexpr u32 PerformanceMetricsDataFormatV2 = 5; //!< The revision a new performance metrics format is used + constexpr u32 VaradicCommandBufferSize = 5; //!< The revision support for varying command buffer sizes was added + constexpr u32 ElapsedFrameCount = 5; //!< The revision support for counting elapsed frames was added + }; + } + + /** + * @brief Extracts the audren version from a revision magic + * @param revision The revision magic + * @return The revision number from the magic + */ + inline u32 ExtractVersionFromRevision(u32 revision) { + return (revision - constant::Rev0Magic) >> 24; + } + + /** + * @brief The RevisionInfo class is used to query the supported features of various audren revisions + */ + class RevisionInfo { + private: + u32 userRevision; //!< The current audren revision of the guest + + public: + /** + * @brief Extracts the audren revision from the magic and sets the behaviour revision to it + * @param revision The revision magic from guest + */ + inline void SetUserRevision(u32 revision) { + userRevision = ExtractVersionFromRevision(revision); + + if (userRevision > constant::SupportedRevision) + throw exception("Unsupported audren revision: {}", userRevision); + } + + /** + * @return Whether the splitter is supported + */ + inline bool SplitterSupported() { + return userRevision >= constant::supportTags::Splitter; + } + + /** + * @return Whether the splitter is fixed + */ + inline bool SplitterBugFixed() { + return userRevision >= constant::supportTags::SplitterBugFix; + } + + /** + * @return Whether the new performance metrics format is used + */ + inline bool UsesPerformanceMetricDataFormatV2() { + return userRevision >= constant::supportTags::PerformanceMetricsDataFormatV2; + } + + /** + * @return Whether varying command buffer sizes are supported + */ + inline bool VaradicCommandBufferSizeSupported() { + return userRevision >= constant::supportTags::VaradicCommandBufferSize; + } + + /** + * @return Whether elapsed frame counting is supported + */ + inline bool ElapsedFrameCountSupported() { + return userRevision >= constant::supportTags::ElapsedFrameCount; + } + }; +} diff --git a/app/src/main/cpp/skyline/services/audren/voice.cpp b/app/src/main/cpp/skyline/services/audren/voice.cpp new file mode 100644 index 00000000..b554bba1 --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/voice.cpp @@ -0,0 +1,110 @@ +#include +#include "voice.h" +#include + +namespace skyline::service::audren { + Voice::Voice(const DeviceState &state) : state(state) {} + + void Voice::ProcessInput(const VoiceIn &input) { + // Voice no longer in use, reset it + if (acquired && !input.acquired) { + bufferReload = true; + bufferIndex = 0; + sampleOffset = 0; + + output.playedSamplesCount = 0; + output.playedWaveBuffersCount = 0; + output.voiceDropsCount = 0; + } + + acquired = input.acquired; + + if (!acquired) + return; + + if (input.firstUpdate) { + if (input.pcmFormat != audio::PcmFormat::Int16) + throw exception("Unsupported voice PCM format: {}", input.pcmFormat); + + pcmFormat = input.pcmFormat; + sampleRate = input.sampleRate; + + if (input.channelCount > 2) + throw exception("Unsupported voice channel count: {}", input.channelCount); + + channelCount = input.channelCount; + SetWaveBufferIndex(input.baseWaveBufferIndex); + } + + waveBuffers = input.waveBuffers; + volume = input.volume; + playbackState = input.playbackState; + } + + void Voice::UpdateBuffers() { + WaveBuffer ¤tBuffer = waveBuffers.at(bufferIndex); + + if (currentBuffer.size == 0) + return; + + switch (pcmFormat) { + case audio::PcmFormat::Int16: + sampleBuffer.resize(currentBuffer.size / sizeof(i16)); + state.process->ReadMemory(sampleBuffer.data(), currentBuffer.position, currentBuffer.size); + break; + default: + throw exception("Unsupported voice PCM format: {}", pcmFormat); + } + + if (sampleRate != audio::constant::SampleRate) + sampleBuffer = resampler.ResampleBuffer(sampleBuffer, static_cast(sampleRate) / audio::constant::SampleRate, channelCount); + + if (channelCount == 1 && audio::constant::ChannelCount != channelCount) { + size_t originalSize = sampleBuffer.size(); + sampleBuffer.resize((originalSize / channelCount) * audio::constant::ChannelCount); + + for (size_t monoIndex = originalSize - 1, targetIndex = sampleBuffer.size(); monoIndex > 0; monoIndex--) + for (uint i = 0; i < audio::constant::ChannelCount; i++) + sampleBuffer[--targetIndex] = sampleBuffer[monoIndex]; + } + } + + std::vector &Voice::GetBufferData(int maxSamples, int &outOffset, int &outSize) { + WaveBuffer ¤tBuffer = waveBuffers.at(bufferIndex); + + if (!acquired || playbackState != audio::AudioOutState::Started) { + outSize = 0; + return sampleBuffer; + } + + if (bufferReload) { + bufferReload = false; + UpdateBuffers(); + } + + outOffset = sampleOffset; + outSize = std::min(maxSamples * audio::constant::ChannelCount, static_cast(sampleBuffer.size() - sampleOffset)); + + output.playedSamplesCount += outSize / audio::constant::ChannelCount; + sampleOffset += outSize; + + if (sampleOffset == sampleBuffer.size()) { + sampleOffset = 0; + + if (currentBuffer.lastBuffer) + playbackState = audio::AudioOutState::Paused; + + if (!currentBuffer.looping) + SetWaveBufferIndex(bufferIndex + 1); + + output.playedWaveBuffersCount++; + } + + return sampleBuffer; + } + + void Voice::SetWaveBufferIndex(uint index) { + bufferIndex = index & 3; + bufferReload = true; + } +} diff --git a/app/src/main/cpp/skyline/services/audren/voice.h b/app/src/main/cpp/skyline/services/audren/voice.h new file mode 100644 index 00000000..5388b3d2 --- /dev/null +++ b/app/src/main/cpp/skyline/services/audren/voice.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include +#include +#include