// SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #include #include #include "IAudioRenderer.h" namespace skyline::service::audio::IAudioRenderer { IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters ¶meters) : systemEvent(std::make_shared(state, true)), parameters(parameters), BaseService(state, manager) { track = state.audio->OpenTrack(constant::StereoChannelCount, constant::SampleRate, [&]() { systemEvent->Signal(); }); track->Start(); memoryPools.resize(parameters.effectCount + parameters.voiceCount * 4); effects.resize(parameters.effectCount); voices.resize(parameters.voiceCount, Voice(state)); // Fill track with empty samples that we will triple buffer track->AppendBuffer(0); track->AppendBuffer(1); track->AppendBuffer(2); } IAudioRenderer::~IAudioRenderer() { state.audio->CloseTrack(track); } Result IAudioRenderer::GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { response.Push(parameters.sampleRate); return {}; } Result IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { response.Push(parameters.sampleCount); return {}; } Result IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { response.Push(parameters.subMixCount); return {}; } Result IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { response.Push(static_cast(playbackState)); return {}; } Result IAudioRenderer::RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto input{request.inputBuf.at(0).data()}; auto inputHeader{*reinterpret_cast(input)}; revisionInfo.SetUserRevision(inputHeader.revision); input += sizeof(UpdateDataHeader); input += inputHeader.behaviorSize; // Unused span memoryPoolsIn(reinterpret_cast(input), memoryPools.size()); input += inputHeader.memoryPoolSize; for (size_t i{}; i < memoryPools.size(); i++) memoryPools[i].ProcessInput(memoryPoolsIn[i]); input += inputHeader.voiceResourceSize; span voicesIn(reinterpret_cast(input), parameters.voiceCount); input += inputHeader.voiceSize; for (u32 i{}; i < voicesIn.size(); i++) voices[i].ProcessInput(voicesIn[i]); span effectsIn(reinterpret_cast(input), parameters.effectCount); for (u32 i{}; i < effectsIn.size(); i++) effects[i].ProcessInput(effectsIn[i]); if (!*state.settings->isAudioOutputDisabled) UpdateAudio(); UpdateDataHeader outputHeader{ .revision = constant::RevMagic, .behaviorSize = 0xB0, .memoryPoolSize = (parameters.effectCount + parameters.voiceCount * 4) * static_cast(sizeof(MemoryPoolOut)), .voiceSize = parameters.voiceCount * static_cast(sizeof(VoiceOut)), .effectSize = parameters.effectCount * static_cast(sizeof(EffectOut)), .sinkSize = parameters.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; auto output{request.outputBuf.at(0).data()}; std::memset(output, 0, request.outputBuf.at(0).size()); *reinterpret_cast(output) = outputHeader; output += sizeof(UpdateDataHeader); for (const auto &memoryPool : memoryPools) { *reinterpret_cast(output) = memoryPool.output; output += sizeof(MemoryPoolOut); } for (const auto &voice : voices) { *reinterpret_cast(output) = voice.output; output += sizeof(VoiceOut); } for (const auto &effect : effects) { *reinterpret_cast(output) = effect.output; output += sizeof(EffectOut); } return {}; } void IAudioRenderer::UpdateAudio() { auto released{track->GetReleasedBuffers(2)}; for (auto &tag : released) { MixFinalBuffer(); track->AppendBuffer(tag, sampleBuffer); } } void IAudioRenderer::MixFinalBuffer() { u32 writtenSamples{}; for (auto &voice : voices) { if (!voice.Playable()) continue; u32 bufferOffset{}; u32 pendingSamples{constant::MixBufferSize}; while (pendingSamples > 0) { u32 voiceBufferOffset{}; u32 voiceBufferSize{}; auto &voiceSamples{voice.GetBufferData(pendingSamples, voiceBufferOffset, voiceBufferSize)}; if (voiceBufferSize == 0) break; pendingSamples -= voiceBufferSize / constant::StereoChannelCount; for (auto index{voiceBufferOffset}; index < voiceBufferOffset + voiceBufferSize; index++) { if (writtenSamples == bufferOffset) { sampleBuffer[bufferOffset] = skyline::audio::Saturate(voiceSamples[index] * voice.volume); writtenSamples++; } else { sampleBuffer[bufferOffset] = skyline::audio::Saturate(sampleBuffer[bufferOffset] + (voiceSamples[index] * voice.volume)); } bufferOffset++; } } } } Result IAudioRenderer::Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { playbackState = skyline::audio::AudioOutState::Started; return {}; } Result IAudioRenderer::Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { playbackState = skyline::audio::AudioOutState::Stopped; return {}; } Result IAudioRenderer::QuerySystemEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto handle{state.process->InsertItem(systemEvent)}; Logger::Debug("System Event Handle: 0x{:X}", handle); response.copyHandles.push_back(handle); return {}; } }