mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
libretro: call the frontend only from retro_run
In dual core the CPU thread called into the frontend while the frontend's own loop was running: every button and axis read went to input_cb from SI polling, each field's audio went to the batch callback, and rumble went to the rumble interface. retro_run now reads every input of the libretro devices once, right after poll_cb, and emulation reads those values. Rumble the emulation asks for is kept until the end of retro_run, which hands it over together with the audio the frame produced.
This commit is contained in:
1 parent
a2404c6e43
commit
49abc7d61f
15 files changed
+137
-1359
No files matched your search
@@ -1,112 +0,0 @@
|
||||
// Copyright 2009 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "AudioCommon/Mixer.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
void InitSoundStream(Core::System& system)
|
||||
{
|
||||
auto sound_stream = std::make_unique<SoundStream>();
|
||||
sound_stream->Init();
|
||||
system.SetSoundStream(std::move(sound_stream));
|
||||
}
|
||||
|
||||
void PostInitSoundStream(Core::System& system)
|
||||
{
|
||||
// This needs to be called after AudioInterface::Init and SerialInterface::Init (for GBA devices)
|
||||
// where input sample rates are set
|
||||
SetSoundStreamRunning(system, true);
|
||||
|
||||
if (Config::Get(Config::MAIN_DUMP_AUDIO) && !system.IsAudioDumpStarted())
|
||||
StartAudioDump(system);
|
||||
}
|
||||
|
||||
void ShutdownSoundStream(Core::System& system)
|
||||
{
|
||||
INFO_LOG_FMT(AUDIO, "Shutting down sound stream");
|
||||
|
||||
if (Config::Get(Config::MAIN_DUMP_AUDIO) && system.IsAudioDumpStarted())
|
||||
StopAudioDump(system);
|
||||
|
||||
SetSoundStreamRunning(system, false);
|
||||
system.SetSoundStream(nullptr);
|
||||
|
||||
INFO_LOG_FMT(AUDIO, "Done shutting down sound stream");
|
||||
}
|
||||
|
||||
DPL2Quality GetDefaultDPL2Quality()
|
||||
{
|
||||
return DPL2Quality::High;
|
||||
}
|
||||
|
||||
void SetSoundStreamRunning(Core::System& system, bool running)
|
||||
{
|
||||
if (system.GetSoundStream() && system.IsSoundStreamRunning() != running)
|
||||
system.SetSoundStreamRunning(running);
|
||||
}
|
||||
|
||||
void SendAIBuffer(Core::System& system, const short* samples, unsigned int num_samples)
|
||||
{
|
||||
SoundStream* const sound_stream = system.GetSoundStream();
|
||||
|
||||
if (!sound_stream)
|
||||
return;
|
||||
|
||||
if (Config::Get(Config::MAIN_DUMP_AUDIO) && !system.IsAudioDumpStarted())
|
||||
StartAudioDump(system);
|
||||
else if (!Config::Get(Config::MAIN_DUMP_AUDIO) && system.IsAudioDumpStarted())
|
||||
StopAudioDump(system);
|
||||
|
||||
if (samples)
|
||||
{
|
||||
sound_stream->GetMixer()->PushSamples(samples, num_samples);
|
||||
sound_stream->Update(num_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void StartAudioDump(Core::System& system)
|
||||
{
|
||||
const SoundStream* const sound_stream = system.GetSoundStream();
|
||||
|
||||
const std::time_t start_time = std::time(nullptr);
|
||||
|
||||
std::string path_prefix = File::GetUserPath(D_DUMPAUDIO_IDX) + SConfig::GetInstance().GetGameID();
|
||||
|
||||
const auto local_time = Common::LocalTime(start_time);
|
||||
if (!local_time)
|
||||
return;
|
||||
|
||||
std::string base_name = fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, *local_time);
|
||||
|
||||
const std::string audio_file_name_dtk = fmt::format("{}_dtkdump.wav", base_name);
|
||||
const std::string audio_file_name_dsp = fmt::format("{}_dspdump.wav", base_name);
|
||||
File::CreateFullPath(audio_file_name_dtk);
|
||||
File::CreateFullPath(audio_file_name_dsp);
|
||||
sound_stream->GetMixer()->StartLogDTKAudio(audio_file_name_dtk);
|
||||
sound_stream->GetMixer()->StartLogDSPAudio(audio_file_name_dsp);
|
||||
system.SetAudioDumpStarted(true);
|
||||
}
|
||||
|
||||
void StopAudioDump(Core::System& system)
|
||||
{
|
||||
const SoundStream* const sound_stream = system.GetSoundStream();
|
||||
|
||||
if (!sound_stream)
|
||||
return;
|
||||
sound_stream->GetMixer()->StopLogDTKAudio();
|
||||
sound_stream->GetMixer()->StopLogDSPAudio();
|
||||
system.SetAudioDumpStarted(false);
|
||||
}
|
||||
} // namespace AudioCommon
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2009 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AudioCommon/Enums.h"
|
||||
#include "AudioCommon/SoundStream.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
void InitSoundStream(Core::System& system);
|
||||
void PostInitSoundStream(Core::System& system);
|
||||
void ShutdownSoundStream(Core::System& system);
|
||||
DPL2Quality GetDefaultDPL2Quality();
|
||||
void SetSoundStreamRunning(Core::System& system, bool running);
|
||||
void SendAIBuffer(Core::System& system, const short* samples, unsigned int num_samples);
|
||||
void StartAudioDump(Core::System& system);
|
||||
void StopAudioDump(Core::System& system);
|
||||
} // namespace AudioCommon
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2019 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
enum class DPL2Quality : int
|
||||
{
|
||||
Lowest = 0,
|
||||
Low = 1,
|
||||
High = 2,
|
||||
Highest = 3
|
||||
};
|
||||
} // namespace AudioCommon
|
||||
@@ -1,592 +0,0 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "AudioCommon/Mixer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
#include "AudioCommon/Enums.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
static u32 DPL2QualityToFrameBlockSize(AudioCommon::DPL2Quality quality)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case AudioCommon::DPL2Quality::Lowest:
|
||||
return 512;
|
||||
case AudioCommon::DPL2Quality::Low:
|
||||
return 1024;
|
||||
case AudioCommon::DPL2Quality::Highest:
|
||||
return 4096;
|
||||
default:
|
||||
return 2048;
|
||||
}
|
||||
}
|
||||
|
||||
Mixer::Mixer(u32 BackendSampleRate)
|
||||
: m_output_sample_rate(BackendSampleRate),
|
||||
m_surround_decoder(BackendSampleRate,
|
||||
DPL2QualityToFrameBlockSize(Config::Get(Config::MAIN_DPL2_QUALITY)))
|
||||
{
|
||||
m_config_changed_callback_id = Config::AddConfigChangedCallback([this] { RefreshConfig(); });
|
||||
RefreshConfig();
|
||||
|
||||
INFO_LOG_FMT(AUDIO_INTERFACE, "Mixer is initialized");
|
||||
}
|
||||
|
||||
Mixer::~Mixer()
|
||||
{
|
||||
Config::RemoveConfigChangedCallback(m_config_changed_callback_id);
|
||||
}
|
||||
|
||||
void Mixer::DoState(PointerWrap& p)
|
||||
{
|
||||
m_dma_mixer.DoState(p);
|
||||
m_streaming_mixer.DoState(p);
|
||||
for (auto& mixer : m_wiimote_speaker_mixers)
|
||||
mixer.DoState(p);
|
||||
m_skylander_portal_mixer.DoState(p);
|
||||
for (auto& mixer : m_gba_mixers)
|
||||
mixer.DoState(p);
|
||||
}
|
||||
|
||||
// Executed from sound stream thread
|
||||
void Mixer::MixerFifo::Mix(s16* samples, std::size_t num_samples)
|
||||
{
|
||||
constexpr u32 INDEX_HALF = 0x80000000;
|
||||
constexpr DT_s FADE_IN_RC = DT_s(0.008);
|
||||
constexpr DT_s FADE_OUT_RC = DT_s(0.064);
|
||||
|
||||
// We need at least a double because the index jump has 24 bits of fractional precision.
|
||||
const double out_sample_rate = m_mixer->m_output_sample_rate;
|
||||
const double in_sample_rate = double(m_input_sample_rate_dividend) / m_input_sample_rate_divisor;
|
||||
|
||||
const double base = static_cast<double>(1 << GRANULE_FRAC_BITS);
|
||||
const u32 index_jump = std::lround(base * in_sample_rate / out_sample_rate);
|
||||
|
||||
// These fade in / out multiplier are tuned to match a constant
|
||||
// fade speed regardless of the input or the output sample rate.
|
||||
const float fade_in_mul = -std::expm1(-DT_s(1.0) / (out_sample_rate * FADE_IN_RC));
|
||||
const float fade_out_mul = -std::expm1(-DT_s(1.0) / (out_sample_rate * FADE_OUT_RC));
|
||||
|
||||
const StereoPair volume{m_LVolume.load() / 256.0f, m_RVolume.load() / 256.0f};
|
||||
|
||||
// Calculate the ideal length of the granule queue.
|
||||
const std::size_t buffer_size_ms = m_mixer->m_config_audio_buffer_ms;
|
||||
const std::size_t buffer_size_samples = std::llround(buffer_size_ms * in_sample_rate / 1000.0);
|
||||
|
||||
// Limit the possible queue sizes to any number between 4 and 64.
|
||||
const std::size_t buffer_size_granules =
|
||||
std::clamp((buffer_size_samples) / (GRANULE_SIZE >> 1), static_cast<std::size_t>(4),
|
||||
static_cast<std::size_t>(MAX_GRANULE_QUEUE_SIZE));
|
||||
|
||||
bool fade_audio = m_queue_fading.load(std::memory_order_relaxed);
|
||||
|
||||
m_granule_queue_size.store(buffer_size_granules, std::memory_order_relaxed);
|
||||
|
||||
while (num_samples-- > 0)
|
||||
{
|
||||
// The indexes for the front and back buffers are offset by 50% of the granule size.
|
||||
// We use the modular nature of 32-bit integers to wrap around the granule size.
|
||||
m_current_index += index_jump;
|
||||
const u32 front_index = m_current_index;
|
||||
const u32 back_index = m_current_index + INDEX_HALF;
|
||||
|
||||
// If either index is less than the index jump, that means we reached
|
||||
// the end of the of the buffer and need to load the next granule.
|
||||
if (front_index < index_jump)
|
||||
fade_audio = Dequeue(&m_front);
|
||||
else if (back_index < index_jump)
|
||||
fade_audio = Dequeue(&m_back);
|
||||
|
||||
// The Granules are pre-windowed, so we can just add them together
|
||||
const std::size_t ft = front_index >> GRANULE_FRAC_BITS;
|
||||
const std::size_t bt = back_index >> GRANULE_FRAC_BITS;
|
||||
const StereoPair s0 = m_front[(ft - 2) & GRANULE_MASK] + m_back[(bt - 2) & GRANULE_MASK];
|
||||
const StereoPair s1 = m_front[(ft - 1) & GRANULE_MASK] + m_back[(bt - 1) & GRANULE_MASK];
|
||||
const StereoPair s2 = m_front[(ft + 0) & GRANULE_MASK] + m_back[(bt + 0) & GRANULE_MASK];
|
||||
const StereoPair s3 = m_front[(ft + 1) & GRANULE_MASK] + m_back[(bt + 1) & GRANULE_MASK];
|
||||
const StereoPair s4 = m_front[(ft + 2) & GRANULE_MASK] + m_back[(bt + 2) & GRANULE_MASK];
|
||||
const StereoPair s5 = m_front[(ft + 3) & GRANULE_MASK] + m_back[(bt + 3) & GRANULE_MASK];
|
||||
|
||||
// Polynomial Interpolators for High-Quality Resampling of
|
||||
// Over Sampled Audio by Olli Niemitalo, October 2001.
|
||||
// Page 43 -- 6-point, 3rd-order Hermite:
|
||||
// https://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf
|
||||
const u32 t_frac = m_current_index & ((1 << GRANULE_FRAC_BITS) - 1);
|
||||
const float t1 = t_frac / static_cast<float>(1 << GRANULE_FRAC_BITS);
|
||||
const float t2 = t1 * t1;
|
||||
const float t3 = t2 * t1;
|
||||
|
||||
StereoPair sample = (s0 * StereoPair{(+0.0f + 1.0f * t1 - 2.0f * t2 + 1.0f * t3) / 12.0f} +
|
||||
s1 * StereoPair{(+0.0f - 8.0f * t1 + 15.0f * t2 - 7.0f * t3) / 12.0f} +
|
||||
s2 * StereoPair{(+3.0f + 0.0f * t1 - 7.0f * t2 + 4.0f * t3) / 3.0f} +
|
||||
s3 * StereoPair{(+0.0f + 2.0f * t1 + 5.0f * t2 - 4.0f * t3) / 3.0f} +
|
||||
s4 * StereoPair{(+0.0f - 1.0f * t1 - 6.0f * t2 + 7.0f * t3) / 12.0f} +
|
||||
s5 * StereoPair{(+0.0f + 0.0f * t1 + 1.0f * t2 - 1.0f * t3) / 12.0f});
|
||||
|
||||
// Apply Fade In / Fade Out depending on if we are looping
|
||||
if (fade_audio)
|
||||
m_fade_volume += fade_out_mul * (0.0f - m_fade_volume);
|
||||
else
|
||||
m_fade_volume += fade_in_mul * (1.0f - m_fade_volume);
|
||||
|
||||
// Apply the fade volume and the regular volume to the sample
|
||||
sample = sample * volume * StereoPair{m_fade_volume};
|
||||
|
||||
// This quantization method prevents accumulated error but does not do noise shaping.
|
||||
sample.l += samples[0] - m_quantization_error.l;
|
||||
samples[0] = MathUtil::SaturatingCast<s16>(std::lround(sample.l));
|
||||
m_quantization_error.l = std::clamp(samples[0] - sample.l, -1.0f, 1.0f);
|
||||
|
||||
sample.r += samples[1] - m_quantization_error.r;
|
||||
samples[1] = MathUtil::SaturatingCast<s16>(std::lround(sample.r));
|
||||
m_quantization_error.r = std::clamp(samples[1] - sample.r, -1.0f, 1.0f);
|
||||
|
||||
samples += 2;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t Mixer::Mix(s16* samples, std::size_t num_samples)
|
||||
{
|
||||
if (!samples)
|
||||
return 0;
|
||||
|
||||
memset(samples, 0, num_samples * 2 * sizeof(s16));
|
||||
|
||||
m_dma_mixer.Mix(samples, num_samples);
|
||||
m_streaming_mixer.Mix(samples, num_samples);
|
||||
for (std::size_t i = 0; i < m_wiimote_speaker_mixers.size(); ++i)
|
||||
{
|
||||
if (!m_config_wiimote_routing_enabled || !m_config_wiimote_output_enabled[i])
|
||||
m_wiimote_speaker_mixers[i].Mix(samples, num_samples);
|
||||
}
|
||||
m_skylander_portal_mixer.Mix(samples, num_samples);
|
||||
for (auto& mixer : m_gba_mixers)
|
||||
mixer.Mix(samples, num_samples);
|
||||
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
std::size_t Mixer::MixSurround(float* samples, std::size_t num_samples)
|
||||
{
|
||||
if (!num_samples)
|
||||
return 0;
|
||||
|
||||
memset(samples, 0, num_samples * SURROUND_CHANNELS * sizeof(float));
|
||||
|
||||
const std::size_t needed_frames =
|
||||
m_surround_decoder.QueryFramesNeededForSurroundOutput(num_samples);
|
||||
|
||||
constexpr std::size_t max_samples = 0x8000;
|
||||
ASSERT_MSG(AUDIO, needed_frames <= max_samples,
|
||||
"needed_frames would overflow m_scratch_buffer: {} -> {} > {}", num_samples,
|
||||
needed_frames, max_samples);
|
||||
|
||||
std::array<s16, max_samples> buffer;
|
||||
const std::size_t available_frames = Mix(buffer.data(), static_cast<std::size_t>(needed_frames));
|
||||
if (available_frames != needed_frames)
|
||||
{
|
||||
ERROR_LOG_FMT(AUDIO,
|
||||
"Error decoding surround frames: needed {} frames for {} samples but got {}",
|
||||
needed_frames, num_samples, available_frames);
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_surround_decoder.PutFrames(buffer.data(), needed_frames);
|
||||
m_surround_decoder.ReceiveFrames(samples, num_samples);
|
||||
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
void Mixer::PushSamples(const s16* samples, std::size_t num_samples)
|
||||
{
|
||||
if (IsOutputSampleRateValid())
|
||||
{
|
||||
// Big-endian RL-orderered stereo samples.
|
||||
|
||||
const s16* ptr = samples;
|
||||
for (std::size_t i = 0; i != num_samples; ++i)
|
||||
{
|
||||
m_dma_mixer.PushSample(Common::swap16(ptr[1]), Common::swap16(ptr[0]));
|
||||
ptr += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_log_dsp_audio)
|
||||
{
|
||||
const s32 sample_rate_divisor = m_dma_mixer.GetInputSampleRateDivisor();
|
||||
auto const volume = m_dma_mixer.GetVolume();
|
||||
m_wave_writer_dsp.AddStereoSamplesBE(samples, static_cast<u32>(num_samples),
|
||||
sample_rate_divisor, volume.first, volume.second);
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::PushStreamingSamples(const s16* samples, std::size_t num_samples)
|
||||
{
|
||||
if (IsOutputSampleRateValid())
|
||||
{
|
||||
// Big-endian RL-orderered stereo samples.
|
||||
|
||||
const s16* ptr = samples;
|
||||
for (std::size_t i = 0; i != num_samples; ++i)
|
||||
{
|
||||
m_streaming_mixer.PushSample(Common::swap16(ptr[1]), Common::swap16(ptr[0]));
|
||||
ptr += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_log_dtk_audio)
|
||||
{
|
||||
const s32 sample_rate_divisor = m_streaming_mixer.GetInputSampleRateDivisor();
|
||||
const auto volume = m_streaming_mixer.GetVolume();
|
||||
m_wave_writer_dtk.AddStereoSamplesBE(samples, static_cast<u32>(num_samples),
|
||||
sample_rate_divisor, volume.first, volume.second);
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::PushWiimoteSpeakerSamples(std::size_t wiimote_index, const s16* samples,
|
||||
std::size_t num_samples, u32 sample_rate_divisor)
|
||||
{
|
||||
if (!IsOutputSampleRateValid() || wiimote_index >= m_wiimote_speaker_mixers.size())
|
||||
return;
|
||||
|
||||
// WiimoteEmu produces host-endian mono samples.
|
||||
|
||||
m_wiimote_speaker_mixers[wiimote_index].SetInputSampleRateDivisor(sample_rate_divisor);
|
||||
|
||||
for (const s16 sample : std::span{samples, num_samples})
|
||||
{
|
||||
m_wiimote_speaker_mixers[wiimote_index].PushSample(sample, sample);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t Mixer::MixWiimoteSpeaker(std::size_t wiimote_index, s16* samples,
|
||||
std::size_t num_samples)
|
||||
{
|
||||
if (!samples || wiimote_index >= m_wiimote_speaker_mixers.size())
|
||||
return 0;
|
||||
|
||||
memset(samples, 0, num_samples * 2 * sizeof(s16));
|
||||
m_wiimote_speaker_mixers[wiimote_index].Mix(samples, num_samples);
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
void Mixer::PushSkylanderPortalSamples(const u8* samples, std::size_t num_samples)
|
||||
{
|
||||
if (!IsOutputSampleRateValid())
|
||||
return;
|
||||
|
||||
// Skylander samples are always supplied as 64 bytes, 32 x 16 bit samples
|
||||
// The portal speaker is 1 channel, so duplicate and play as stereo audio
|
||||
|
||||
while (num_samples--)
|
||||
{
|
||||
// Little-endian data.
|
||||
const s16 sample = u16(samples[0] | u16(samples[1] << 8u));
|
||||
m_skylander_portal_mixer.PushSample(sample, sample);
|
||||
samples += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::PushGBASamples(std::size_t device_number, const s16* samples, std::size_t num_samples)
|
||||
{
|
||||
if (!IsOutputSampleRateValid())
|
||||
return;
|
||||
|
||||
// Integrated GBA pushes host-endian LR-ordered stereo samples.
|
||||
|
||||
while (num_samples--)
|
||||
{
|
||||
m_gba_mixers[device_number].PushSample(samples[0], samples[1]);
|
||||
samples += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::SetDMAInputSampleRateDivisor(u32 rate_divisor)
|
||||
{
|
||||
m_dma_mixer.SetInputSampleRateDivisor(rate_divisor);
|
||||
}
|
||||
|
||||
void Mixer::SetStreamInputSampleRateDivisor(u32 rate_divisor)
|
||||
{
|
||||
m_streaming_mixer.SetInputSampleRateDivisor(rate_divisor);
|
||||
}
|
||||
|
||||
void Mixer::SetGBAInputSampleRate(std::size_t device_number, u32 sample_rate)
|
||||
{
|
||||
m_gba_mixers[device_number].SetInputSampleRateDivisor(GBA_SAMPLE_RATE_DIVIDEND / sample_rate);
|
||||
}
|
||||
|
||||
void Mixer::SetStreamingVolume(u32 lvolume, u32 rvolume)
|
||||
{
|
||||
m_streaming_mixer.SetVolume(std::clamp<u32>(lvolume, 0x00, 0xff),
|
||||
std::clamp<u32>(rvolume, 0x00, 0xff));
|
||||
}
|
||||
|
||||
void Mixer::SetWiimoteSpeakerVolume(std::size_t wiimote_index, u32 lvolume, u32 rvolume)
|
||||
{
|
||||
if (wiimote_index < m_wiimote_speaker_mixers.size())
|
||||
m_wiimote_speaker_mixers[wiimote_index].SetVolume(lvolume, rvolume);
|
||||
}
|
||||
|
||||
void Mixer::SetGBAVolume(std::size_t device_number, u32 lvolume, u32 rvolume)
|
||||
{
|
||||
m_gba_mixers[device_number].SetVolume(lvolume, rvolume);
|
||||
}
|
||||
|
||||
void Mixer::StartLogDTKAudio(const std::string& filename)
|
||||
{
|
||||
if (!m_log_dtk_audio)
|
||||
{
|
||||
const bool success =
|
||||
m_wave_writer_dtk.Start(filename, m_streaming_mixer.GetInputSampleRateDivisor());
|
||||
if (success)
|
||||
{
|
||||
m_log_dtk_audio = true;
|
||||
m_wave_writer_dtk.SetSkipSilence(false);
|
||||
NOTICE_LOG_FMT(AUDIO, "Starting DTK Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_wave_writer_dtk.Stop();
|
||||
NOTICE_LOG_FMT(AUDIO, "Unable to start DTK Audio logging");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(AUDIO, "DTK Audio logging has already been started");
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::StopLogDTKAudio()
|
||||
{
|
||||
if (m_log_dtk_audio)
|
||||
{
|
||||
m_log_dtk_audio = false;
|
||||
m_wave_writer_dtk.Stop();
|
||||
NOTICE_LOG_FMT(AUDIO, "Stopping DTK Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(AUDIO, "DTK Audio logging has already been stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::StartLogDSPAudio(const std::string& filename)
|
||||
{
|
||||
if (!m_log_dsp_audio)
|
||||
{
|
||||
const bool success = m_wave_writer_dsp.Start(filename, m_dma_mixer.GetInputSampleRateDivisor());
|
||||
if (success)
|
||||
{
|
||||
m_log_dsp_audio = true;
|
||||
m_wave_writer_dsp.SetSkipSilence(false);
|
||||
NOTICE_LOG_FMT(AUDIO, "Starting DSP Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_wave_writer_dsp.Stop();
|
||||
NOTICE_LOG_FMT(AUDIO, "Unable to start DSP Audio logging");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(AUDIO, "DSP Audio logging has already been started");
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::StopLogDSPAudio()
|
||||
{
|
||||
if (m_log_dsp_audio)
|
||||
{
|
||||
m_log_dsp_audio = false;
|
||||
m_wave_writer_dsp.Stop();
|
||||
NOTICE_LOG_FMT(AUDIO, "Stopping DSP Audio logging");
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(AUDIO, "DSP Audio logging has already been stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::RefreshConfig()
|
||||
{
|
||||
m_config_audio_preserve_pitch = Config::Get(Config::MAIN_AUDIO_PRESERVE_PITCH);
|
||||
m_config_fill_audio_gaps = Config::Get(Config::MAIN_AUDIO_FILL_GAPS);
|
||||
m_config_audio_buffer_ms = Config::Get(Config::MAIN_AUDIO_BUFFER_SIZE);
|
||||
m_config_wiimote_routing_enabled = Config::Get(Config::MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED);
|
||||
for (std::size_t i = 0; i < m_config_wiimote_output_enabled.size(); ++i)
|
||||
m_config_wiimote_output_enabled[i] = Config::Get(Config::MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED[i]);
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(m_input_sample_rate_divisor);
|
||||
p.Do(m_LVolume);
|
||||
p.Do(m_RVolume);
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::SetInputSampleRateDividend(u32 rate_dividend)
|
||||
{
|
||||
m_input_sample_rate_dividend = rate_dividend;
|
||||
}
|
||||
|
||||
u32 Mixer::MixerFifo::GetInputSampleRateDividend() const
|
||||
{
|
||||
return m_input_sample_rate_dividend;
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::SetInputSampleRateDivisor(u32 rate_divisor)
|
||||
{
|
||||
m_input_sample_rate_divisor = rate_divisor;
|
||||
}
|
||||
|
||||
u32 Mixer::MixerFifo::GetInputSampleRateDivisor() const
|
||||
{
|
||||
return m_input_sample_rate_divisor;
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::SetVolume(u32 lvolume, u32 rvolume)
|
||||
{
|
||||
m_LVolume.store(lvolume + (lvolume >> 7));
|
||||
m_RVolume.store(rvolume + (rvolume >> 7));
|
||||
}
|
||||
|
||||
std::pair<s32, s32> Mixer::MixerFifo::GetVolume() const
|
||||
{
|
||||
return std::make_pair(m_LVolume.load(), m_RVolume.load());
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::Enqueue()
|
||||
{
|
||||
// import numpy as np
|
||||
// import scipy.signal as signal
|
||||
// window = np.convolve(np.ones(128), signal.windows.dpss(128 + 1, 4))
|
||||
// window /= (window[:len(window) // 2] + window[len(window) // 2:]).max()
|
||||
// elements = ", ".join([f"{x:.10f}f" for x in window])
|
||||
// print(f'constexpr std::array<StereoPair, GRANULE_SIZE> GRANULE_WINDOW = {{ {elements}
|
||||
// }};')
|
||||
static constexpr std::array<StereoPair, GRANULE_SIZE> GRANULE_WINDOW = {
|
||||
0.0000016272f, 0.0000050749f, 0.0000113187f, 0.0000216492f, 0.0000377350f, 0.0000616906f,
|
||||
0.0000961509f, 0.0001443499f, 0.0002102045f, 0.0002984010f, 0.0004144844f, 0.0005649486f,
|
||||
0.0007573262f, 0.0010002765f, 0.0013036694f, 0.0016786636f, 0.0021377783f, 0.0026949534f,
|
||||
0.0033656000f, 0.0041666352f, 0.0051165029f, 0.0062351752f, 0.0075441359f, 0.0090663409f,
|
||||
0.0108261579f, 0.0128492811f, 0.0151626215f, 0.0177941726f, 0.0207728499f, 0.0241283062f,
|
||||
0.0278907219f, 0.0320905724f, 0.0367583739f, 0.0419244083f, 0.0476184323f, 0.0538693708f,
|
||||
0.0607049996f, 0.0681516192f, 0.0762337261f, 0.0849736833f, 0.0943913952f, 0.1045039915f,
|
||||
0.1153255250f, 0.1268666867f, 0.1391345431f, 0.1521323012f, 0.1658591025f, 0.1803098534f,
|
||||
0.1954750915f, 0.2113408944f, 0.2278888303f, 0.2450959552f, 0.2629348550f, 0.2813737361f,
|
||||
0.3003765625f, 0.3199032396f, 0.3399098438f, 0.3603488941f, 0.3811696664f, 0.4023185434f,
|
||||
0.4237393998f, 0.4453740162f, 0.4671625177f, 0.4890438330f, 0.5109561670f, 0.5328374823f,
|
||||
0.5546259838f, 0.5762606002f, 0.5976814566f, 0.6188303336f, 0.6396511059f, 0.6600901562f,
|
||||
0.6800967604f, 0.6996234375f, 0.7186262639f, 0.7370651450f, 0.7549040448f, 0.7721111697f,
|
||||
0.7886591056f, 0.8045249085f, 0.8196901466f, 0.8341408975f, 0.8478676988f, 0.8608654569f,
|
||||
0.8731333133f, 0.8846744750f, 0.8954960085f, 0.9056086048f, 0.9150263167f, 0.9237662739f,
|
||||
0.9318483808f, 0.9392950004f, 0.9461306292f, 0.9523815677f, 0.9580755917f, 0.9632416261f,
|
||||
0.9679094276f, 0.9721092781f, 0.9758716938f, 0.9792271501f, 0.9822058274f, 0.9848373785f,
|
||||
0.9871507189f, 0.9891738421f, 0.9909336591f, 0.9924558641f, 0.9937648248f, 0.9948834971f,
|
||||
0.9958333648f, 0.9966344000f, 0.9973050466f, 0.9978622217f, 0.9983213364f, 0.9986963306f,
|
||||
0.9989997235f, 0.9992426738f, 0.9994350514f, 0.9995855156f, 0.9997015990f, 0.9997897955f,
|
||||
0.9998556501f, 0.9999038491f, 0.9999383094f, 0.9999622650f, 0.9999783508f, 0.9999886813f,
|
||||
0.9999949251f, 0.9999983728f, 0.9999983728f, 0.9999949251f, 0.9999886813f, 0.9999783508f,
|
||||
0.9999622650f, 0.9999383094f, 0.9999038491f, 0.9998556501f, 0.9997897955f, 0.9997015990f,
|
||||
0.9995855156f, 0.9994350514f, 0.9992426738f, 0.9989997235f, 0.9986963306f, 0.9983213364f,
|
||||
0.9978622217f, 0.9973050466f, 0.9966344000f, 0.9958333648f, 0.9948834971f, 0.9937648248f,
|
||||
0.9924558641f, 0.9909336591f, 0.9891738421f, 0.9871507189f, 0.9848373785f, 0.9822058274f,
|
||||
0.9792271501f, 0.9758716938f, 0.9721092781f, 0.9679094276f, 0.9632416261f, 0.9580755917f,
|
||||
0.9523815677f, 0.9461306292f, 0.9392950004f, 0.9318483808f, 0.9237662739f, 0.9150263167f,
|
||||
0.9056086048f, 0.8954960085f, 0.8846744750f, 0.8731333133f, 0.8608654569f, 0.8478676988f,
|
||||
0.8341408975f, 0.8196901466f, 0.8045249085f, 0.7886591056f, 0.7721111697f, 0.7549040448f,
|
||||
0.7370651450f, 0.7186262639f, 0.6996234375f, 0.6800967604f, 0.6600901562f, 0.6396511059f,
|
||||
0.6188303336f, 0.5976814566f, 0.5762606002f, 0.5546259838f, 0.5328374823f, 0.5109561670f,
|
||||
0.4890438330f, 0.4671625177f, 0.4453740162f, 0.4237393998f, 0.4023185434f, 0.3811696664f,
|
||||
0.3603488941f, 0.3399098438f, 0.3199032396f, 0.3003765625f, 0.2813737361f, 0.2629348550f,
|
||||
0.2450959552f, 0.2278888303f, 0.2113408944f, 0.1954750915f, 0.1803098534f, 0.1658591025f,
|
||||
0.1521323012f, 0.1391345431f, 0.1268666867f, 0.1153255250f, 0.1045039915f, 0.0943913952f,
|
||||
0.0849736833f, 0.0762337261f, 0.0681516192f, 0.0607049996f, 0.0538693708f, 0.0476184323f,
|
||||
0.0419244083f, 0.0367583739f, 0.0320905724f, 0.0278907219f, 0.0241283062f, 0.0207728499f,
|
||||
0.0177941726f, 0.0151626215f, 0.0128492811f, 0.0108261579f, 0.0090663409f, 0.0075441359f,
|
||||
0.0062351752f, 0.0051165029f, 0.0041666352f, 0.0033656000f, 0.0026949534f, 0.0021377783f,
|
||||
0.0016786636f, 0.0013036694f, 0.0010002765f, 0.0007573262f, 0.0005649486f, 0.0004144844f,
|
||||
0.0002984010f, 0.0002102045f, 0.0001443499f, 0.0000961509f, 0.0000616906f, 0.0000377350f,
|
||||
0.0000216492f, 0.0000113187f, 0.0000050749f, 0.0000016272f};
|
||||
|
||||
const std::size_t head = m_queue_head.load(std::memory_order_acquire);
|
||||
|
||||
// Check if we run out of space in the circular queue. (rare)
|
||||
const std::size_t next_head = (head + 1) & GRANULE_QUEUE_MASK;
|
||||
if (next_head == m_queue_tail.load(std::memory_order_acquire))
|
||||
{
|
||||
WARN_LOG_FMT(AUDIO,
|
||||
"Granule Queue has completely filled and audio samples are being dropped. "
|
||||
"This should not happen unless the audio backend has stopped requesting audio.");
|
||||
return;
|
||||
}
|
||||
|
||||
// By preconstructing the granule window, we have the best chance of
|
||||
// the compiler optimizing this loop using SIMD instructions.
|
||||
const std::size_t start_index = m_next_buffer_index;
|
||||
for (std::size_t i = 0; i < GRANULE_SIZE; ++i)
|
||||
m_queue[head][i] = m_next_buffer[(i + start_index) & GRANULE_MASK] * GRANULE_WINDOW[i];
|
||||
|
||||
m_queue_head.store(next_head, std::memory_order_release);
|
||||
m_queue_fading.store(false, std::memory_order_relaxed);
|
||||
m_queue_looping.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool Mixer::MixerFifo::Dequeue(Granule* granule)
|
||||
{
|
||||
const std::size_t granule_queue_size = m_granule_queue_size.load(std::memory_order_relaxed);
|
||||
const std::size_t head = m_queue_head.load(std::memory_order_acquire);
|
||||
std::size_t tail = m_queue_tail.load(std::memory_order_acquire);
|
||||
|
||||
// Checks to see if the queue has gotten too long.
|
||||
if (granule_queue_size < ((head - tail) & GRANULE_QUEUE_MASK))
|
||||
{
|
||||
// Jump the playhead to half the queue size behind the head.
|
||||
const std::size_t gap = (granule_queue_size >> 1) + 1;
|
||||
tail = (head - gap) & GRANULE_QUEUE_MASK;
|
||||
}
|
||||
|
||||
// Checks to see if the queue is empty.
|
||||
std::size_t next_tail = (tail + 1) & GRANULE_QUEUE_MASK;
|
||||
if (next_tail == head)
|
||||
{
|
||||
// Only fill gaps when running to prevent stutter on pause.
|
||||
const bool is_running = Core::GetState(Core::System::GetInstance()) == Core::State::Running;
|
||||
if (m_mixer->m_config_fill_audio_gaps && is_running)
|
||||
{
|
||||
// Jump the playhead to half the queue size behind the head.
|
||||
// This provides smoother audio playback than suddenly stopping.
|
||||
const std::size_t gap = std::max<std::size_t>(2, granule_queue_size >> 1) - 1;
|
||||
next_tail = (head - gap) & GRANULE_QUEUE_MASK;
|
||||
|
||||
bool looping = m_queue_looping.load(std::memory_order_relaxed);
|
||||
m_queue_fading.store(looping, std::memory_order_relaxed);
|
||||
m_queue_looping.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(granule->begin(), granule->end(), StereoPair{0.0f, 0.0f});
|
||||
m_queue_fading.store(false, std::memory_order_relaxed);
|
||||
m_queue_looping.store(false, std::memory_order_relaxed);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*granule = m_queue[tail];
|
||||
m_queue_tail.store(next_tail, std::memory_order_release);
|
||||
|
||||
return m_queue_fading.load(std::memory_order_relaxed);
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
// Copyright 2009 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
|
||||
#include "AudioCommon/SurroundDecoder.h"
|
||||
#include "AudioCommon/WaveFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Inline.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
class Mixer final
|
||||
{
|
||||
public:
|
||||
explicit Mixer(u32 BackendSampleRate);
|
||||
~Mixer();
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
// Called from audio threads
|
||||
std::size_t Mix(s16* samples, std::size_t numSamples);
|
||||
std::size_t MixSurround(float* samples, std::size_t num_samples);
|
||||
|
||||
// Called from main thread
|
||||
void PushSamples(const s16* samples, std::size_t num_samples);
|
||||
void PushStreamingSamples(const s16* samples, std::size_t num_samples);
|
||||
void PushWiimoteSpeakerSamples(std::size_t wiimote_index, const s16* samples,
|
||||
std::size_t num_samples, u32 sample_rate_divisor);
|
||||
void PushSkylanderPortalSamples(const u8* samples, std::size_t num_samples);
|
||||
void PushGBASamples(std::size_t device_number, const s16* samples, std::size_t num_samples);
|
||||
|
||||
u32 GetSampleRate() const { return m_output_sample_rate; }
|
||||
void SetSampleRate(u32 output_sample_rate) { m_output_sample_rate = output_sample_rate; }
|
||||
|
||||
bool IsOutputSampleRateValid() const { return m_output_sample_rate != 0; }
|
||||
|
||||
void SetDMAInputSampleRateDivisor(u32 rate_divisor);
|
||||
void SetStreamInputSampleRateDivisor(u32 rate_divisor);
|
||||
void SetGBAInputSampleRate(std::size_t device_number, u32 sample_rate);
|
||||
|
||||
void SetStreamingVolume(u32 lvolume, u32 rvolume);
|
||||
void SetWiimoteSpeakerVolume(std::size_t wiimote_index, u32 lvolume, u32 rvolume);
|
||||
std::size_t MixWiimoteSpeaker(std::size_t wiimote_index, s16* samples, std::size_t num_samples);
|
||||
void SetGBAVolume(std::size_t device_number, u32 lvolume, u32 rvolume);
|
||||
|
||||
void StartLogDTKAudio(const std::string& filename);
|
||||
void StopLogDTKAudio();
|
||||
|
||||
void StartLogDSPAudio(const std::string& filename);
|
||||
void StopLogDSPAudio();
|
||||
|
||||
// 54000000 doesn't work here as it doesn't evenly divide with 32000, but 108000000 does
|
||||
static constexpr u64 FIXED_SAMPLE_RATE_DIVIDEND = 54000000 * 2;
|
||||
|
||||
private:
|
||||
const std::size_t SURROUND_CHANNELS = 6;
|
||||
|
||||
class MixerFifo final
|
||||
{
|
||||
static constexpr std::size_t MAX_GRANULE_QUEUE_SIZE = 256;
|
||||
static constexpr std::size_t GRANULE_QUEUE_MASK = MAX_GRANULE_QUEUE_SIZE - 1;
|
||||
|
||||
struct StereoPair final
|
||||
{
|
||||
float l = 0.f;
|
||||
float r = 0.f;
|
||||
|
||||
constexpr StereoPair() = default;
|
||||
constexpr StereoPair(const StereoPair&) = default;
|
||||
constexpr StereoPair& operator=(const StereoPair&) = default;
|
||||
constexpr StereoPair(StereoPair&&) = default;
|
||||
constexpr StereoPair& operator=(StereoPair&&) = default;
|
||||
|
||||
constexpr StereoPair(float mono) : l(mono), r(mono) {}
|
||||
constexpr StereoPair(float left, float right) : l(left), r(right) {}
|
||||
constexpr StereoPair(s16 left, s16 right) : l(left), r(right) {}
|
||||
|
||||
StereoPair operator+(const StereoPair& other) const
|
||||
{
|
||||
return StereoPair(l + other.l, r + other.r);
|
||||
}
|
||||
|
||||
StereoPair operator*(const StereoPair& other) const
|
||||
{
|
||||
return StereoPair(l * other.l, r * other.r);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr std::size_t GRANULE_SIZE = 256;
|
||||
static constexpr std::size_t GRANULE_OVERLAP = GRANULE_SIZE / 2;
|
||||
static constexpr std::size_t GRANULE_MASK = GRANULE_SIZE - 1;
|
||||
static constexpr std::size_t GRANULE_BITS = std::countr_one(GRANULE_MASK);
|
||||
static constexpr std::size_t GRANULE_FRAC_BITS = 32 - GRANULE_BITS;
|
||||
|
||||
using Granule = std::array<StereoPair, GRANULE_SIZE>;
|
||||
|
||||
public:
|
||||
MixerFifo(Mixer* mixer, u32 sample_rate_divisor,
|
||||
u32 sample_rate_dividend = FIXED_SAMPLE_RATE_DIVIDEND)
|
||||
: m_mixer(mixer), m_input_sample_rate_dividend(sample_rate_dividend),
|
||||
m_input_sample_rate_divisor(sample_rate_divisor)
|
||||
{
|
||||
}
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
DOLPHIN_FORCE_INLINE void PushSample(s16 left, s16 right)
|
||||
{
|
||||
m_next_buffer[m_next_buffer_index] = {left, right};
|
||||
m_next_buffer_index = (m_next_buffer_index + 1) & GRANULE_MASK;
|
||||
|
||||
// The granules overlap by 50%, so we need to enqueue the
|
||||
// next buffer every time we fill half of the samples.
|
||||
if (m_next_buffer_index == 0 || m_next_buffer_index == m_next_buffer.size() / 2)
|
||||
Enqueue();
|
||||
}
|
||||
|
||||
void Mix(s16* samples, std::size_t num_samples);
|
||||
|
||||
void SetInputSampleRateDividend(u32 rate_dividend);
|
||||
u32 GetInputSampleRateDividend() const;
|
||||
|
||||
void SetInputSampleRateDivisor(u32 rate_divisor);
|
||||
u32 GetInputSampleRateDivisor() const;
|
||||
|
||||
void SetVolume(u32 lvolume, u32 rvolume);
|
||||
std::pair<s32, s32> GetVolume() const;
|
||||
|
||||
private:
|
||||
Mixer* m_mixer;
|
||||
|
||||
// All non-GBA MixerFifo instances use FIXED_SAMPLE_RATE_DIVIDEND.
|
||||
u32 m_input_sample_rate_dividend;
|
||||
u32 m_input_sample_rate_divisor;
|
||||
|
||||
Granule m_next_buffer{};
|
||||
std::size_t m_next_buffer_index = 0;
|
||||
|
||||
u32 m_current_index = 0;
|
||||
Granule m_front, m_back;
|
||||
|
||||
std::atomic<std::size_t> m_granule_queue_size{20};
|
||||
std::array<Granule, MAX_GRANULE_QUEUE_SIZE> m_queue;
|
||||
std::atomic<std::size_t> m_queue_head{0};
|
||||
std::atomic<std::size_t> m_queue_tail{0};
|
||||
std::atomic<bool> m_queue_fading{false};
|
||||
std::atomic<bool> m_queue_looping{false};
|
||||
float m_fade_volume = 1.0;
|
||||
|
||||
void Enqueue();
|
||||
bool Dequeue(Granule* granule);
|
||||
|
||||
// Volume ranges from 0-256
|
||||
std::atomic<s32> m_LVolume{256};
|
||||
std::atomic<s32> m_RVolume{256};
|
||||
|
||||
StereoPair m_quantization_error;
|
||||
};
|
||||
|
||||
void RefreshConfig();
|
||||
|
||||
MixerFifo m_dma_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 32000};
|
||||
MixerFifo m_streaming_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000};
|
||||
std::array<MixerFifo, 4> m_wiimote_speaker_mixers{
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000},
|
||||
};
|
||||
MixerFifo m_skylander_portal_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 8000};
|
||||
|
||||
// GBAs generally use a 65536 sample rate which is not a factor of our FIXED_SAMPLE_RATE_DIVIDEND.
|
||||
static constexpr u32 GBA_SAMPLE_RATE_DIVIDEND = 0x1000000;
|
||||
|
||||
std::array<MixerFifo, 4> m_gba_mixers{
|
||||
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||
};
|
||||
u32 m_output_sample_rate;
|
||||
|
||||
AudioCommon::SurroundDecoder m_surround_decoder;
|
||||
|
||||
WaveFileWriter m_wave_writer_dtk;
|
||||
WaveFileWriter m_wave_writer_dsp;
|
||||
|
||||
bool m_log_dtk_audio = false;
|
||||
bool m_log_dsp_audio = false;
|
||||
|
||||
bool m_config_audio_preserve_pitch;
|
||||
bool m_config_fill_audio_gaps;
|
||||
int m_config_audio_buffer_ms;
|
||||
bool m_config_wiimote_routing_enabled = false;
|
||||
std::array<bool, 4> m_config_wiimote_output_enabled{};
|
||||
|
||||
Config::ConfigChangedCallbackID m_config_changed_callback_id;
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2009 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "AudioCommon/Mixer.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
// The libretro audio output: owns the mixer and hands its output to the frontend.
|
||||
// Implemented in DolphinLibretro/Audio.cpp.
|
||||
class SoundStream final
|
||||
{
|
||||
public:
|
||||
static constexpr unsigned int MIN_SAMPLES = 96;
|
||||
static constexpr unsigned int MAX_SAMPLES = 1024;
|
||||
|
||||
SoundStream();
|
||||
|
||||
Mixer* GetMixer() const { return m_mixer.get(); }
|
||||
|
||||
void Init();
|
||||
// After the DSP has pushed num_samples into the mixer
|
||||
void Update(unsigned int num_samples);
|
||||
// From the frontend's audio callback
|
||||
void ProcessCallBack();
|
||||
// On the CPU thread at each emulated field boundary
|
||||
void NewField();
|
||||
|
||||
private:
|
||||
void PushAudioForFrame();
|
||||
void MixAndPush(unsigned int num_samples);
|
||||
|
||||
std::unique_ptr<Mixer> m_mixer;
|
||||
s16 m_buffer[MAX_SAMPLES * 2];
|
||||
unsigned int m_sample_rate;
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "AudioCommon/SurroundDecoder.h"
|
||||
|
||||
#include <FreeSurround/FreeSurroundDecoder.h>
|
||||
#include <limits>
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
constexpr size_t STEREO_CHANNELS = 2;
|
||||
constexpr size_t SURROUND_CHANNELS = 6;
|
||||
|
||||
SurroundDecoder::SurroundDecoder(u32 sample_rate, u32 frame_block_size)
|
||||
: m_sample_rate(sample_rate), m_frame_block_size(frame_block_size)
|
||||
{
|
||||
m_fsdecoder = std::make_unique<DPL2FSDecoder>();
|
||||
m_fsdecoder->Init(cs_5point1, m_frame_block_size, m_sample_rate);
|
||||
}
|
||||
|
||||
SurroundDecoder::~SurroundDecoder() = default;
|
||||
|
||||
void SurroundDecoder::Clear()
|
||||
{
|
||||
m_fsdecoder->flush();
|
||||
m_decoded_fifo.clear();
|
||||
}
|
||||
|
||||
// Currently only 6 channels are supported.
|
||||
size_t SurroundDecoder::QueryFramesNeededForSurroundOutput(const size_t output_frames) const
|
||||
{
|
||||
if (m_decoded_fifo.size() < output_frames * SURROUND_CHANNELS)
|
||||
{
|
||||
// Output stereo frames needed to have at least the desired number of surround frames
|
||||
const size_t frames_needed = output_frames - m_decoded_fifo.size() / SURROUND_CHANNELS;
|
||||
return frames_needed + m_frame_block_size - frames_needed % m_frame_block_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Receive and decode samples
|
||||
void SurroundDecoder::PutFrames(const short* in, const size_t num_frames_in)
|
||||
{
|
||||
// Maybe check if it is really power-of-2?
|
||||
s64 remaining_frames = static_cast<s64>(num_frames_in);
|
||||
size_t frame_index = 0;
|
||||
|
||||
while (remaining_frames > 0)
|
||||
{
|
||||
// Convert to float
|
||||
for (size_t i = 0, end = m_frame_block_size * STEREO_CHANNELS; i < end; ++i)
|
||||
{
|
||||
m_float_conversion_buffer[i] = in[i + frame_index * STEREO_CHANNELS] /
|
||||
static_cast<float>(std::numeric_limits<short>::max());
|
||||
}
|
||||
|
||||
// Decode
|
||||
const float* dpl2_fs = m_fsdecoder->decode(m_float_conversion_buffer.data());
|
||||
|
||||
// Add to ring buffer and fix channel mapping
|
||||
// Maybe modify FreeSurround to output the correct mapping?
|
||||
// FreeSurround:
|
||||
// FL | FC | FR | BL | BR | LFE
|
||||
// Most backends:
|
||||
// FL | FR | FC | LFE | BL | BR
|
||||
for (size_t i = 0; i < m_frame_block_size; ++i)
|
||||
{
|
||||
m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 0]); // LEFTFRONT
|
||||
m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 2]); // RIGHTFRONT
|
||||
m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 1]); // CENTREFRONT
|
||||
m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 5]); // sub/lfe
|
||||
m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 3]); // LEFTREAR
|
||||
m_decoded_fifo.push(dpl2_fs[i * SURROUND_CHANNELS + 4]); // RIGHTREAR
|
||||
}
|
||||
|
||||
remaining_frames = remaining_frames - static_cast<int>(m_frame_block_size);
|
||||
frame_index = frame_index + m_frame_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void SurroundDecoder::ReceiveFrames(float* out, const size_t num_frames_out)
|
||||
{
|
||||
// Copy to output array with desired num_frames_out
|
||||
for (size_t i = 0, num_samples_output = num_frames_out * SURROUND_CHANNELS;
|
||||
i < num_samples_output; ++i)
|
||||
{
|
||||
out[i] = m_decoded_fifo.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCommon
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FixedSizeQueue.h"
|
||||
|
||||
class DPL2FSDecoder;
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
class SurroundDecoder
|
||||
{
|
||||
public:
|
||||
explicit SurroundDecoder(u32 sample_rate, u32 frame_block_size);
|
||||
~SurroundDecoder();
|
||||
size_t QueryFramesNeededForSurroundOutput(const size_t output_frames) const;
|
||||
void PutFrames(const short* in, const size_t num_frames_in);
|
||||
void ReceiveFrames(float* out, const size_t num_frames_out);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
u32 m_sample_rate;
|
||||
u32 m_frame_block_size;
|
||||
|
||||
std::unique_ptr<DPL2FSDecoder> m_fsdecoder;
|
||||
std::array<float, 32768> m_float_conversion_buffer;
|
||||
Common::FixedSizeQueue<float, 32768> m_decoded_fifo;
|
||||
};
|
||||
|
||||
} // namespace AudioCommon
|
||||
@@ -1,171 +0,0 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "AudioCommon/WaveFile.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "AudioCommon/Mixer.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
|
||||
constexpr size_t WaveFileWriter::BUFFER_SIZE;
|
||||
|
||||
WaveFileWriter::WaveFileWriter()
|
||||
{
|
||||
}
|
||||
|
||||
WaveFileWriter::~WaveFileWriter()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool WaveFileWriter::Start(const std::string& filename, u32 sample_rate_divisor)
|
||||
{
|
||||
// Ask to delete file
|
||||
if (File::Exists(filename))
|
||||
{
|
||||
if (Config::Get(Config::MAIN_DUMP_AUDIO_SILENT) ||
|
||||
AskYesNoFmtT("Delete the existing file '{0}'?", filename))
|
||||
{
|
||||
File::Delete(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop and cancel dumping the audio
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the file is already open
|
||||
if (m_file)
|
||||
{
|
||||
PanicAlertFmtT("The file {0} was already open, the file header will not be written.", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file.Open(filename, "wb");
|
||||
if (!m_file)
|
||||
{
|
||||
PanicAlertFmtT(
|
||||
"The file {0} could not be opened for writing. Please check if it's already opened "
|
||||
"by another program.",
|
||||
filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_audio_size = 0;
|
||||
|
||||
if (m_basename.empty())
|
||||
SplitPath(filename, nullptr, &m_basename, nullptr);
|
||||
|
||||
m_current_sample_rate_divisor = sample_rate_divisor;
|
||||
|
||||
// -----------------
|
||||
// Write file header
|
||||
// -----------------
|
||||
Write4("RIFF");
|
||||
Write(100 * 1000 * 1000); // write big value in case the file gets truncated
|
||||
Write4("WAVE");
|
||||
Write4("fmt ");
|
||||
|
||||
Write(16); // size of fmt block
|
||||
Write(0x00020001); // two channels, uncompressed
|
||||
|
||||
const u32 sample_rate = Mixer::FIXED_SAMPLE_RATE_DIVIDEND / sample_rate_divisor;
|
||||
Write(sample_rate);
|
||||
Write(sample_rate * 2 * 2); // two channels, 16bit
|
||||
|
||||
Write(0x00100004);
|
||||
Write4("data");
|
||||
Write(100 * 1000 * 1000 - 32);
|
||||
|
||||
// We are now at offset 44
|
||||
if (m_file.Tell() != 44)
|
||||
PanicAlertFmt("Wrong offset: {}", m_file.Tell());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaveFileWriter::Stop()
|
||||
{
|
||||
m_file.Seek(4, File::SeekOrigin::Begin);
|
||||
Write(m_audio_size + 36);
|
||||
|
||||
m_file.Seek(40, File::SeekOrigin::Begin);
|
||||
Write(m_audio_size);
|
||||
|
||||
m_file.Close();
|
||||
}
|
||||
|
||||
void WaveFileWriter::Write(u32 value)
|
||||
{
|
||||
m_file.WriteArray(&value, 1);
|
||||
}
|
||||
|
||||
void WaveFileWriter::Write4(const char* ptr)
|
||||
{
|
||||
m_file.WriteBytes(ptr, 4);
|
||||
}
|
||||
|
||||
void WaveFileWriter::AddStereoSamplesBE(const short* sample_data, u32 count,
|
||||
u32 sample_rate_divisor, int l_volume, int r_volume)
|
||||
{
|
||||
if (!m_file)
|
||||
{
|
||||
ERROR_LOG_FMT(AUDIO, "WaveFileWriter - file not open.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (count * 2 > BUFFER_SIZE)
|
||||
{
|
||||
ERROR_LOG_FMT(AUDIO, "WaveFileWriter - buffer too small (count = {}).", count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_skip_silence)
|
||||
{
|
||||
bool all_zero = true;
|
||||
|
||||
for (u32 i = 0; i < count * 2; i++)
|
||||
{
|
||||
if (sample_data[i])
|
||||
all_zero = false;
|
||||
}
|
||||
|
||||
if (all_zero)
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
// Flip the audio channels from RL to LR
|
||||
m_conv_buffer[2 * i] = Common::swap16((u16)sample_data[2 * i + 1]);
|
||||
m_conv_buffer[2 * i + 1] = Common::swap16((u16)sample_data[2 * i]);
|
||||
|
||||
// Apply volume (volume ranges from 0 to 256)
|
||||
m_conv_buffer[2 * i] = m_conv_buffer[2 * i] * l_volume / 256;
|
||||
m_conv_buffer[2 * i + 1] = m_conv_buffer[2 * i + 1] * r_volume / 256;
|
||||
}
|
||||
|
||||
if (sample_rate_divisor != m_current_sample_rate_divisor)
|
||||
{
|
||||
Stop();
|
||||
m_file_index++;
|
||||
const std::string filename =
|
||||
fmt::format("{}{}{}.wav", File::GetUserPath(D_DUMPAUDIO_IDX), m_basename, m_file_index);
|
||||
Start(filename, sample_rate_divisor);
|
||||
m_current_sample_rate_divisor = sample_rate_divisor;
|
||||
}
|
||||
|
||||
m_file.WriteBytes(m_conv_buffer.data(), count * 4);
|
||||
m_audio_size += count * 4;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// Class: WaveFileWriter
|
||||
// Description: Simple utility class to make it easy to write long 16-bit stereo
|
||||
// audio streams to disk.
|
||||
// Use Start() to start recording to a file, and AddStereoSamples to add wave data.
|
||||
// The float variant will convert from -1.0-1.0 range and clamp.
|
||||
// Alternatively, AddSamplesBE for big endian wave data.
|
||||
// If Stop is not called when it destructs, the destructor will call Stop().
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/IOFile.h"
|
||||
|
||||
class WaveFileWriter
|
||||
{
|
||||
public:
|
||||
WaveFileWriter();
|
||||
~WaveFileWriter();
|
||||
|
||||
WaveFileWriter(const WaveFileWriter&) = delete;
|
||||
WaveFileWriter& operator=(const WaveFileWriter&) = delete;
|
||||
WaveFileWriter(WaveFileWriter&&) = delete;
|
||||
WaveFileWriter& operator=(WaveFileWriter&&) = delete;
|
||||
|
||||
bool Start(const std::string& filename, u32 sample_rate_divisor);
|
||||
void Stop();
|
||||
|
||||
void SetSkipSilence(bool skip) { m_skip_silence = skip; }
|
||||
// big endian
|
||||
void AddStereoSamplesBE(const short* sample_data, u32 count, u32 sample_rate_divisor,
|
||||
int l_volume, int r_volume);
|
||||
u32 GetAudioSize() const { return m_audio_size; }
|
||||
|
||||
private:
|
||||
static constexpr size_t BUFFER_SIZE = 32 * 1024;
|
||||
|
||||
void Write(u32 value);
|
||||
void Write4(const char* ptr);
|
||||
|
||||
File::IOFile m_file;
|
||||
std::string m_basename;
|
||||
u32 m_file_index = 0;
|
||||
u32 m_audio_size = 0;
|
||||
|
||||
u32 m_current_sample_rate_divisor;
|
||||
std::array<short, BUFFER_SIZE> m_conv_buffer{};
|
||||
|
||||
bool m_skip_silence = false;
|
||||
};
|
||||
@@ -154,12 +154,28 @@ void SoundStream::Init()
|
||||
|
||||
void SoundStream::Update(unsigned int num_samples)
|
||||
{
|
||||
if (g_use_call_back_audio != CallBackMode::PUSH_SAMPLES) {
|
||||
if (g_use_call_back_audio != CallBackMode::PUSH_SAMPLES)
|
||||
return;
|
||||
}
|
||||
retro_atomic_fetch_add_int(&m_pending_samples, static_cast<int>(num_samples));
|
||||
}
|
||||
|
||||
void SoundStream::NewField()
|
||||
{
|
||||
retro_atomic_fetch_add_int(&m_pending_fields, 1);
|
||||
}
|
||||
|
||||
void SoundStream::PushPending()
|
||||
{
|
||||
if (!batch_cb)
|
||||
return;
|
||||
if (const int samples = retro_atomic_exchange_int(&m_pending_samples, 0))
|
||||
PushSamples(static_cast<unsigned int>(samples));
|
||||
for (int fields = retro_atomic_exchange_int(&m_pending_fields, 0); fields > 0; --fields)
|
||||
PushAudioForFrame();
|
||||
}
|
||||
|
||||
void SoundStream::PushSamples(unsigned int num_samples)
|
||||
{
|
||||
|
||||
static unsigned pending = 0;
|
||||
pending += num_samples;
|
||||
@@ -219,12 +235,6 @@ void SoundStream::MixAndPush(unsigned int num_samples)
|
||||
}
|
||||
}
|
||||
|
||||
// Per frame sync: each field's samples go to the frontend from the CPU thread as the field ends,
|
||||
void SoundStream::NewField()
|
||||
{
|
||||
PushAudioForFrame();
|
||||
}
|
||||
|
||||
void SoundStream::PushAudioForFrame()
|
||||
{
|
||||
if (g_use_call_back_audio != CallBackMode::SYNC_PER_FRAME || !batch_cb)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <retro_atomic.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
@@ -317,7 +318,12 @@ private:
|
||||
{
|
||||
}
|
||||
std::string GetName() const override { return m_name; }
|
||||
ControlState GetState() const override { return input_cb(m_port, m_device, m_index, m_id); }
|
||||
/* The state retro_run read; emulation threads never call the frontend themselves. */
|
||||
ControlState GetState() const override { return retro_atomic_load_acquire_int(&m_value); }
|
||||
void Poll()
|
||||
{
|
||||
retro_atomic_store_release_int(&m_value, input_cb(m_port, m_device, m_index, m_id));
|
||||
}
|
||||
|
||||
private:
|
||||
const unsigned m_port;
|
||||
@@ -325,6 +331,7 @@ private:
|
||||
const unsigned m_index;
|
||||
const unsigned m_id;
|
||||
const char* m_name;
|
||||
mutable retro_atomic_int_t m_value{};
|
||||
};
|
||||
|
||||
class Axis : public ciface::Core::Device::Input
|
||||
@@ -337,7 +344,11 @@ private:
|
||||
std::string GetName() const override { return m_name; }
|
||||
ControlState GetState() const override
|
||||
{
|
||||
return std::max(0.0, input_cb(m_port, m_device, m_index, m_id) / m_range);
|
||||
return std::max(0.0, retro_atomic_load_acquire_int(&m_value) / m_range);
|
||||
}
|
||||
void Poll()
|
||||
{
|
||||
retro_atomic_store_release_int(&m_value, input_cb(m_port, m_device, m_index, m_id));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -347,6 +358,7 @@ private:
|
||||
const unsigned m_id;
|
||||
const ControlState m_range;
|
||||
const char* m_name;
|
||||
mutable retro_atomic_int_t m_value{};
|
||||
};
|
||||
|
||||
class Motor : public ciface::Core::Device::Output
|
||||
@@ -354,32 +366,83 @@ private:
|
||||
public:
|
||||
Motor(u8 port) : m_port(port) {}
|
||||
std::string GetName() const override { return "Rumble"; }
|
||||
/* Any thread: kept until retro_run hands it to the frontend. */
|
||||
void SetState(ControlState state) override
|
||||
{
|
||||
u16 str = std::min(std::max(0.0, state), 1.0) * 0xFFFF;
|
||||
|
||||
if (rumble.set_rumble_state)
|
||||
{
|
||||
rumble.set_rumble_state(m_port, RETRO_RUMBLE_WEAK, str);
|
||||
rumble.set_rumble_state(m_port, RETRO_RUMBLE_STRONG, str);
|
||||
}
|
||||
const u16 str = (u16)(std::min(std::max(0.0, state), 1.0) * 0xFFFF);
|
||||
retro_atomic_store_release_int(&m_wanted, str);
|
||||
}
|
||||
void Flush()
|
||||
{
|
||||
const int wanted = retro_atomic_load_acquire_int(&m_wanted);
|
||||
if (wanted == m_sent || !rumble.set_rumble_state)
|
||||
return;
|
||||
m_sent = wanted;
|
||||
rumble.set_rumble_state(m_port, RETRO_RUMBLE_WEAK, (u16)wanted);
|
||||
rumble.set_rumble_state(m_port, RETRO_RUMBLE_STRONG, (u16)wanted);
|
||||
}
|
||||
|
||||
private:
|
||||
const u8 m_port;
|
||||
retro_atomic_int_t m_wanted{};
|
||||
int m_sent = 0; /* retro_run only */
|
||||
};
|
||||
/* A device has a few dozen inputs at most: a joypad 16 buttons, a pointer 20. */
|
||||
enum
|
||||
{
|
||||
MAX_BUTTONS = 32,
|
||||
MAX_AXES = 32,
|
||||
MAX_MOTORS = 2
|
||||
};
|
||||
|
||||
void AddButton(unsigned id, const char* name, unsigned index = 0)
|
||||
{
|
||||
AddInput(new Button(m_port, m_device, index, id, name));
|
||||
Button* button = new Button(m_port, m_device, index, id, name);
|
||||
if (m_num_buttons < MAX_BUTTONS)
|
||||
m_buttons[m_num_buttons++] = button;
|
||||
AddInput(button);
|
||||
}
|
||||
void AddAxis(unsigned id, s16 range, const char* name, unsigned index = 0)
|
||||
{
|
||||
AddInput(new Axis(m_port, m_device, index, id, range, name));
|
||||
Axis* axis = new Axis(m_port, m_device, index, id, range, name);
|
||||
if (m_num_axes < MAX_AXES)
|
||||
m_axes[m_num_axes++] = axis;
|
||||
AddInput(axis);
|
||||
}
|
||||
void AddMotor() { AddOutput(new Motor(m_port)); }
|
||||
void AddMotor()
|
||||
{
|
||||
Motor* motor = new Motor(m_port);
|
||||
if (m_num_motors < MAX_MOTORS)
|
||||
m_motors[m_num_motors++] = motor;
|
||||
AddOutput(motor);
|
||||
}
|
||||
|
||||
/* Owned by the base class's input and output lists. */
|
||||
Button* m_buttons[MAX_BUTTONS];
|
||||
Axis* m_axes[MAX_AXES];
|
||||
Motor* m_motors[MAX_MOTORS];
|
||||
unsigned m_num_buttons = 0;
|
||||
unsigned m_num_axes = 0;
|
||||
unsigned m_num_motors = 0;
|
||||
|
||||
public:
|
||||
Device(unsigned device, unsigned port);
|
||||
/* retro_run, after poll_cb: reads every input of this device from the frontend. */
|
||||
void Poll()
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < m_num_buttons; i++)
|
||||
m_buttons[i]->Poll();
|
||||
for (i = 0; i < m_num_axes; i++)
|
||||
m_axes[i]->Poll();
|
||||
}
|
||||
/* retro_run: hands the frontend the rumble the emulation asked for. */
|
||||
void FlushOutputs()
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < m_num_motors; i++)
|
||||
m_motors[i]->Flush();
|
||||
}
|
||||
ciface::Core::DeviceRemoval UpdateInput() override
|
||||
{
|
||||
#if 0
|
||||
@@ -903,10 +966,34 @@ static int GetRetroButtonId(const std::string& name)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Frontend thread: each libretro device reads its inputs, or flushes its outputs. */
|
||||
static void PollDevice(ciface::Core::Device* device, void*)
|
||||
{
|
||||
Device* const libretro_device = dynamic_cast<Device*>(device);
|
||||
if (libretro_device)
|
||||
libretro_device->Poll();
|
||||
}
|
||||
|
||||
static void FlushDevice(ciface::Core::Device* device, void*)
|
||||
{
|
||||
Device* const libretro_device = dynamic_cast<Device*>(device);
|
||||
if (libretro_device)
|
||||
libretro_device->FlushOutputs();
|
||||
}
|
||||
|
||||
void FlushOutputs()
|
||||
{
|
||||
g_controller_interface.ForEachDevice(FlushDevice, nullptr);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (poll_cb)
|
||||
poll_cb();
|
||||
/* Every input the emulation may read this frame, read now: the frontend is only ever called
|
||||
* from its own thread, inside retro_run. */
|
||||
if (input_cb)
|
||||
g_controller_interface.ForEachDevice(PollDevice, nullptr);
|
||||
#ifdef __ANDROID__
|
||||
/* Android doesn't support input polling on all threads by default
|
||||
* this will force the poll for this frame to happen in the main thread
|
||||
|
||||
@@ -58,6 +58,8 @@ void SetVRMotion(unsigned port, const float accel[3], const float gyro[3], const
|
||||
void UpdateAccelerometer(unsigned port);
|
||||
void UpdateGyro(unsigned port);
|
||||
void Update();
|
||||
// From retro_run after the frame: rumble the emulation asked for goes to the frontend.
|
||||
void FlushOutputs();
|
||||
void Shutdown();
|
||||
void ResetControllers(const WiimoteUpdateFlags& f);
|
||||
void BluetoothPassthroughBind();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "AudioCommon/SoundStream.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/FrameClock.h"
|
||||
@@ -552,6 +553,11 @@ void retro_run(void)
|
||||
|
||||
if (Libretro::Input::g_has_microphone_support)
|
||||
poll_microphone();
|
||||
|
||||
// What the frame produced for the frontend, handed over from its own thread.
|
||||
Libretro::Input::FlushOutputs();
|
||||
if (SoundStream* sound_stream = system.GetSoundStream())
|
||||
sound_stream->PushPending();
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void)
|
||||
|
||||
@@ -243,6 +243,18 @@ public:
|
||||
Device::Output* FindOutput(std::string_view name, const Device* def_dev) const;
|
||||
|
||||
std::vector<std::shared_ptr<Device>> GetAllDevices() const;
|
||||
|
||||
/* Calls fn(device, ctx) for every device, without copying the list. */
|
||||
void ForEachDevice(void (*fn)(Device* device, void* ctx), void* ctx) const
|
||||
{
|
||||
InputReadScope scope;
|
||||
const DeviceList* const list = PeekDevices();
|
||||
size_t i;
|
||||
if (!list)
|
||||
return;
|
||||
for (i = 0; i < list->devices.size(); i++)
|
||||
fn(list->devices[i].get(), ctx);
|
||||
}
|
||||
std::vector<std::string> GetAllDeviceStrings() const;
|
||||
bool HasDefaultDevice() const;
|
||||
std::string GetDefaultDeviceString() const;
|
||||
|
||||
Reference in new issue
Block a user