183 lines
7.0 KiB
C++
183 lines
7.0 KiB
C++
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
|
|
|
#include <common/settings.h>
|
|
#include <kernel/types/KProcess.h>
|
|
#include "IAudioRenderer.h"
|
|
|
|
namespace skyline::service::audio::IAudioRenderer {
|
|
IAudioRenderer::IAudioRenderer(const DeviceState &state, ServiceManager &manager, AudioRendererParameters ¶meters)
|
|
: systemEvent(std::make_shared<type::KEvent>(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<u32>(parameters.sampleRate);
|
|
return {};
|
|
}
|
|
|
|
Result IAudioRenderer::GetSampleCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
|
response.Push<u32>(parameters.sampleCount);
|
|
return {};
|
|
}
|
|
|
|
Result IAudioRenderer::GetMixBufferCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
|
response.Push<u32>(parameters.subMixCount);
|
|
return {};
|
|
}
|
|
|
|
Result IAudioRenderer::GetState(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
|
response.Push(static_cast<u32>(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<UpdateDataHeader *>(input)};
|
|
revisionInfo.SetUserRevision(inputHeader.revision);
|
|
input += sizeof(UpdateDataHeader);
|
|
input += inputHeader.behaviorSize; // Unused
|
|
|
|
span memoryPoolsIn(reinterpret_cast<MemoryPoolIn *>(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<VoiceIn *>(input), parameters.voiceCount);
|
|
input += inputHeader.voiceSize;
|
|
for (u32 i{}; i < voicesIn.size(); i++)
|
|
voices[i].ProcessInput(voicesIn[i]);
|
|
|
|
span effectsIn(reinterpret_cast<EffectIn *>(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<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;
|
|
|
|
auto output{request.outputBuf.at(0).data()};
|
|
std::memset(output, 0, request.outputBuf.at(0).size());
|
|
|
|
*reinterpret_cast<UpdateDataHeader *>(output) = outputHeader;
|
|
output += sizeof(UpdateDataHeader);
|
|
|
|
for (const auto &memoryPool : memoryPools) {
|
|
*reinterpret_cast<MemoryPoolOut *>(output) = memoryPool.output;
|
|
output += sizeof(MemoryPoolOut);
|
|
}
|
|
|
|
for (const auto &voice : voices) {
|
|
*reinterpret_cast<VoiceOut *>(output) = voice.output;
|
|
output += sizeof(VoiceOut);
|
|
}
|
|
|
|
for (const auto &effect : effects) {
|
|
*reinterpret_cast<EffectOut *>(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<i16, i32>(voiceSamples[index] * voice.volume);
|
|
|
|
writtenSamples++;
|
|
} else {
|
|
sampleBuffer[bufferOffset] = skyline::audio::Saturate<i16, i32>(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 {};
|
|
}
|
|
}
|