skyline/app/src/main/cpp/skyline/services/audio/IAudioRenderer/IAudioRenderer.cpp

186 lines
7.6 KiB
C++

// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include "IAudioRenderer.h"
namespace skyline::service::audio::IAudioRenderer {
IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters &parameters)
: releaseEvent(std::make_shared<type::KEvent>(state)), parameters(parameters), BaseService(state, manager, Service::audio_IAudioRenderer, "audio: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(constant::ChannelCount, parameters.sampleRate, [this]() { releaseEvent->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);
}
void IAudioRenderer::GetSampleRate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u32>(parameters.sampleRate);
}
void IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u32>(parameters.sampleCount);
}
void IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u32>(parameters.subMixCount);
}
void IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(static_cast<u32>(playbackState));
}
void IAudioRenderer::RequestUpdate(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto inputAddress = request.inputBuf.at(0).address;
auto inputHeader = state.process->GetObject<UpdateDataHeader>(inputAddress);
revisionInfo.SetUserRevision(inputHeader.revision);
inputAddress += sizeof(UpdateDataHeader);
inputAddress += inputHeader.behaviorSize; // Unused
auto memoryPoolCount = memoryPools.size();
std::vector<MemoryPoolIn> memoryPoolsIn(memoryPoolCount);
state.process->ReadMemory(memoryPoolsIn.data(), inputAddress, memoryPoolCount * sizeof(MemoryPoolIn));
inputAddress += inputHeader.memoryPoolSize;
for (auto i = 0; i < memoryPoolsIn.size(); i++)
memoryPools[i].ProcessInput(memoryPoolsIn[i]);
inputAddress += inputHeader.voiceResourceSize;
std::vector<VoiceIn> voicesIn(parameters.voiceCount);
state.process->ReadMemory(voicesIn.data(), inputAddress, parameters.voiceCount * sizeof(VoiceIn));
inputAddress += inputHeader.voiceSize;
for (auto i = 0; i < voicesIn.size(); i++)
voices[i].ProcessInput(voicesIn[i]);
std::vector<EffectIn> effectsIn(parameters.effectCount);
state.process->ReadMemory(effectsIn.data(), inputAddress, parameters.effectCount * sizeof(EffectIn));
for (auto i = 0; i < effectsIn.size(); i++)
effects[i].ProcessInput(effectsIn[i]);
UpdateAudio();
UpdateDataHeader outputHeader{
.revision = constant::RevMagic,
.behaviorSize = 0xb0,
.memoryPoolSize = (parameters.effectCount + parameters.voiceCount * 4) * static_cast<u32>(sizeof(MemoryPoolOut)),
.voiceSize = parameters.voiceCount * static_cast<u32>(sizeof(VoiceOut)),
.effectSize = parameters.effectCount * static_cast<u32>(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;
u64 outputAddress = request.outputBuf.at(0).address;
state.process->WriteMemory(outputHeader, outputAddress);
outputAddress += sizeof(UpdateDataHeader);
for (const auto &memoryPool : memoryPools) {
state.process->WriteMemory(memoryPool.output, outputAddress);
outputAddress += sizeof(MemoryPoolOut);
}
for (const auto &voice : voices) {
state.process->WriteMemory(voice.output, outputAddress);
outputAddress += sizeof(VoiceOut);
}
for (const auto &effect : effects) {
state.process->WriteMemory(effect.output, outputAddress);
outputAddress += sizeof(EffectOut);
}
}
void IAudioRenderer::UpdateAudio() {
auto released = track->GetReleasedBuffers(2);
for (auto &tag : released) {
MixFinalBuffer();
track->AppendBuffer(tag, sampleBuffer.data(), sampleBuffer.size());
}
}
void IAudioRenderer::MixFinalBuffer() {
u32 writtenSamples = 0;
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::ChannelCount;
for (auto index = voiceBufferOffset; index < voiceBufferOffset + voiceBufferSize; index++) {
if (writtenSamples == bufferOffset) {
sampleBuffer[bufferOffset] = skyline::audio::Saturate<i16, i32>(voiceSamples[index] * voice.volume);
writtenSamples++;
} else {
sampleBuffer[bufferOffset] = skyline::audio::Saturate<i16, i32>(sampleBuffer[bufferOffset] + (voiceSamples[index] * voice.volume));
}
bufferOffset++;
}
}
}
}
void IAudioRenderer::Start(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
playbackState = skyline::audio::AudioOutState::Started;
}
void IAudioRenderer::Stop(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
playbackState = skyline::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);
}
}