audio_core: remove time stretcher
Also drop the SoundTouch dependency
This commit is contained in:
parent
550844e5e8
commit
faf6a9876c
|
@ -7,9 +7,6 @@
|
||||||
[submodule "dynarmic"]
|
[submodule "dynarmic"]
|
||||||
path = externals/dynarmic
|
path = externals/dynarmic
|
||||||
url = https://github.com/MerryMage/dynarmic.git
|
url = https://github.com/MerryMage/dynarmic.git
|
||||||
[submodule "soundtouch"]
|
|
||||||
path = externals/soundtouch
|
|
||||||
url = https://github.com/citra-emu/ext-soundtouch.git
|
|
||||||
[submodule "libressl"]
|
[submodule "libressl"]
|
||||||
path = externals/libressl
|
path = externals/libressl
|
||||||
url = https://github.com/citra-emu/ext-libressl-portable.git
|
url = https://github.com/citra-emu/ext-libressl-portable.git
|
||||||
|
|
|
@ -68,9 +68,6 @@ if (YUZU_USE_EXTERNAL_SDL2)
|
||||||
add_library(SDL2 ALIAS SDL2-static)
|
add_library(SDL2 ALIAS SDL2-static)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# SoundTouch
|
|
||||||
add_subdirectory(soundtouch)
|
|
||||||
|
|
||||||
# Cubeb
|
# Cubeb
|
||||||
if(ENABLE_CUBEB)
|
if(ENABLE_CUBEB)
|
||||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 060181eaf273180d3a7e87349895bd0cb6ccbf4a
|
|
|
@ -36,8 +36,6 @@ add_library(audio_core STATIC
|
||||||
splitter_context.h
|
splitter_context.h
|
||||||
stream.cpp
|
stream.cpp
|
||||||
stream.h
|
stream.h
|
||||||
time_stretch.cpp
|
|
||||||
time_stretch.h
|
|
||||||
voice_context.cpp
|
voice_context.cpp
|
||||||
voice_context.h
|
voice_context.h
|
||||||
|
|
||||||
|
@ -63,7 +61,6 @@ if (NOT MSVC)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(audio_core PUBLIC common core)
|
target_link_libraries(audio_core PUBLIC common core)
|
||||||
target_link_libraries(audio_core PRIVATE SoundTouch)
|
|
||||||
|
|
||||||
if(ENABLE_CUBEB)
|
if(ENABLE_CUBEB)
|
||||||
target_link_libraries(audio_core PRIVATE cubeb)
|
target_link_libraries(audio_core PRIVATE cubeb)
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "audio_core/cubeb_sink.h"
|
#include "audio_core/cubeb_sink.h"
|
||||||
#include "audio_core/stream.h"
|
#include "audio_core/stream.h"
|
||||||
#include "audio_core/time_stretch.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/ring_buffer.h"
|
#include "common/ring_buffer.h"
|
||||||
|
@ -23,8 +22,7 @@ class CubebSinkStream final : public SinkStream {
|
||||||
public:
|
public:
|
||||||
CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
|
CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
|
||||||
const std::string& name)
|
const std::string& name)
|
||||||
: ctx{ctx_}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
|
: ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} {
|
||||||
num_channels} {
|
|
||||||
|
|
||||||
cubeb_stream_params params{};
|
cubeb_stream_params params{};
|
||||||
params.rate = sample_rate;
|
params.rate = sample_rate;
|
||||||
|
@ -131,7 +129,6 @@ private:
|
||||||
Common::RingBuffer<s16, 0x10000> queue;
|
Common::RingBuffer<s16, 0x10000> queue;
|
||||||
std::array<s16, 2> last_frame{};
|
std::array<s16, 2> last_frame{};
|
||||||
std::atomic<bool> should_flush{};
|
std::atomic<bool> should_flush{};
|
||||||
TimeStretcher time_stretch;
|
|
||||||
|
|
||||||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||||
void* output_buffer, long num_frames);
|
void* output_buffer, long num_frames);
|
||||||
|
@ -205,25 +202,7 @@ long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void*
|
||||||
|
|
||||||
const std::size_t num_channels = impl->GetNumChannels();
|
const std::size_t num_channels = impl->GetNumChannels();
|
||||||
const std::size_t samples_to_write = num_channels * num_frames;
|
const std::size_t samples_to_write = num_channels * num_frames;
|
||||||
std::size_t samples_written;
|
const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write);
|
||||||
|
|
||||||
/*
|
|
||||||
if (Settings::values.enable_audio_stretching.GetValue()) {
|
|
||||||
const std::vector<s16> in{impl->queue.Pop()};
|
|
||||||
const std::size_t num_in{in.size() / num_channels};
|
|
||||||
s16* const out{reinterpret_cast<s16*>(buffer)};
|
|
||||||
const std::size_t out_frames =
|
|
||||||
impl->time_stretch.Process(in.data(), num_in, out, num_frames);
|
|
||||||
samples_written = out_frames * num_channels;
|
|
||||||
|
|
||||||
if (impl->should_flush) {
|
|
||||||
impl->time_stretch.Flush();
|
|
||||||
impl->should_flush = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
samples_written = impl->queue.Pop(buffer, samples_to_write);
|
|
||||||
}*/
|
|
||||||
samples_written = impl->queue.Pop(buffer, samples_to_write);
|
|
||||||
|
|
||||||
if (samples_written >= num_channels) {
|
if (samples_written >= num_channels) {
|
||||||
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
|
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "audio_core/sdl2_sink.h"
|
#include "audio_core/sdl2_sink.h"
|
||||||
#include "audio_core/stream.h"
|
#include "audio_core/stream.h"
|
||||||
#include "audio_core/time_stretch.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
//#include "common/settings.h"
|
//#include "common/settings.h"
|
||||||
|
@ -27,7 +26,7 @@ namespace AudioCore {
|
||||||
class SDLSinkStream final : public SinkStream {
|
class SDLSinkStream final : public SinkStream {
|
||||||
public:
|
public:
|
||||||
SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device)
|
SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device)
|
||||||
: num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, num_channels} {
|
: num_channels{std::min(num_channels_, 6u)} {
|
||||||
|
|
||||||
SDL_AudioSpec spec;
|
SDL_AudioSpec spec;
|
||||||
spec.freq = sample_rate;
|
spec.freq = sample_rate;
|
||||||
|
@ -116,7 +115,6 @@ private:
|
||||||
SDL_AudioDeviceID dev = 0;
|
SDL_AudioDeviceID dev = 0;
|
||||||
u32 num_channels{};
|
u32 num_channels{};
|
||||||
std::atomic<bool> should_flush{};
|
std::atomic<bool> should_flush{};
|
||||||
TimeStretcher time_stretch;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SDLSink::SDLSink(std::string_view target_device_name) {
|
SDLSink::SDLSink(std::string_view target_device_name) {
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
// Copyright 2018 yuzu Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstddef>
|
|
||||||
#include "audio_core/time_stretch.h"
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
|
|
||||||
namespace AudioCore {
|
|
||||||
|
|
||||||
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) : m_sample_rate{sample_rate} {
|
|
||||||
m_sound_touch.setChannels(channel_count);
|
|
||||||
m_sound_touch.setSampleRate(sample_rate);
|
|
||||||
m_sound_touch.setPitch(1.0);
|
|
||||||
m_sound_touch.setTempo(1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeStretcher::Clear() {
|
|
||||||
m_sound_touch.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeStretcher::Flush() {
|
|
||||||
m_sound_touch.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
|
|
||||||
std::size_t num_out) {
|
|
||||||
const double time_delta = static_cast<double>(num_out) / m_sample_rate; // seconds
|
|
||||||
|
|
||||||
// We were given actual_samples number of samples, and num_samples were requested from us.
|
|
||||||
double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out);
|
|
||||||
|
|
||||||
const double max_latency = 0.25; // seconds
|
|
||||||
const double max_backlog = m_sample_rate * max_latency;
|
|
||||||
const double backlog_fullness = m_sound_touch.numSamples() / max_backlog;
|
|
||||||
if (backlog_fullness > 4.0) {
|
|
||||||
// Too many samples in backlog: Don't push anymore on
|
|
||||||
num_in = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We ideally want the backlog to be about 50% full.
|
|
||||||
// This gives some headroom both ways to prevent underflow and overflow.
|
|
||||||
// We tweak current_ratio to encourage this.
|
|
||||||
constexpr double tweak_time_scale = 0.05; // seconds
|
|
||||||
const double tweak_correction = (backlog_fullness - 0.5) * (time_delta / tweak_time_scale);
|
|
||||||
current_ratio *= std::pow(1.0 + 2.0 * tweak_correction, tweak_correction < 0 ? 3.0 : 1.0);
|
|
||||||
|
|
||||||
// This low-pass filter smoothes out variance in the calculated stretch ratio.
|
|
||||||
// The time-scale determines how responsive this filter is.
|
|
||||||
constexpr double lpf_time_scale = 0.712; // seconds
|
|
||||||
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
|
|
||||||
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
|
|
||||||
|
|
||||||
// Place a lower limit of 5% speed. When a game boots up, there will be
|
|
||||||
// many silence samples. These do not need to be timestretched.
|
|
||||||
m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
|
|
||||||
m_sound_touch.setTempo(m_stretch_ratio);
|
|
||||||
|
|
||||||
LOG_TRACE(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio,
|
|
||||||
backlog_fullness);
|
|
||||||
|
|
||||||
m_sound_touch.putSamples(in, static_cast<u32>(num_in));
|
|
||||||
return m_sound_touch.receiveSamples(out, static_cast<u32>(num_out));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore
|
|
|
@ -1,34 +0,0 @@
|
||||||
// Copyright 2018 yuzu Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <SoundTouch.h>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace AudioCore {
|
|
||||||
|
|
||||||
class TimeStretcher {
|
|
||||||
public:
|
|
||||||
TimeStretcher(u32 sample_rate, u32 channel_count);
|
|
||||||
|
|
||||||
/// @param in Input sample buffer
|
|
||||||
/// @param num_in Number of input frames in `in`
|
|
||||||
/// @param out Output sample buffer
|
|
||||||
/// @param num_out Desired number of output frames in `out`
|
|
||||||
/// @returns Actual number of frames written to `out`
|
|
||||||
std::size_t Process(const s16* in, std::size_t num_in, s16* out, std::size_t num_out);
|
|
||||||
|
|
||||||
void Clear();
|
|
||||||
|
|
||||||
void Flush();
|
|
||||||
|
|
||||||
private:
|
|
||||||
u32 m_sample_rate;
|
|
||||||
soundtouch::SoundTouch m_sound_touch;
|
|
||||||
double m_stretch_ratio = 1.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace AudioCore
|
|
|
@ -342,12 +342,6 @@ fps_cap =
|
||||||
# null: No audio output
|
# null: No audio output
|
||||||
output_engine =
|
output_engine =
|
||||||
|
|
||||||
# Whether or not to enable the audio-stretching post-processing effect.
|
|
||||||
# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
|
|
||||||
# at the cost of increasing audio latency.
|
|
||||||
# 0: No, 1 (default): Yes
|
|
||||||
enable_audio_stretching =
|
|
||||||
|
|
||||||
# Which audio device to use.
|
# Which audio device to use.
|
||||||
# auto (default): Auto-select
|
# auto (default): Auto-select
|
||||||
output_device =
|
output_device =
|
||||||
|
|
Loading…
Reference in New Issue