diff options
author | Maribel <MerryMage@users.noreply.github.com> | 2016-05-15 04:04:03 +0200 |
---|---|---|
committer | bunnei <bunneidev@gmail.com> | 2016-05-15 04:04:03 +0200 |
commit | 6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd (patch) | |
tree | c857bb669cb13a0358ec6f5bee504963254534c6 /src/audio_core/time_stretch.cpp | |
parent | Merge pull request #1794 from Subv/regression_fix (diff) | |
download | yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.tar yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.tar.gz yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.tar.bz2 yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.tar.lz yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.tar.xz yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.tar.zst yuzu-6f6af6928fdff8c807e4a4d03cfd8906e0c7c7cd.zip |
Diffstat (limited to 'src/audio_core/time_stretch.cpp')
-rw-r--r-- | src/audio_core/time_stretch.cpp | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp new file mode 100644 index 000000000..ea38f40d0 --- /dev/null +++ b/src/audio_core/time_stretch.cpp @@ -0,0 +1,144 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <cmath> +#include <vector> + +#include <SoundTouch.h> + +#include "audio_core/audio_core.h" +#include "audio_core/time_stretch.h" + +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/math_util.h" + +using steady_clock = std::chrono::steady_clock; + +namespace AudioCore { + +constexpr double MIN_RATIO = 0.1; +constexpr double MAX_RATIO = 100.0; + +static double ClampRatio(double ratio) { + return MathUtil::Clamp(ratio, MIN_RATIO, MAX_RATIO); +} + +constexpr double MIN_DELAY_TIME = 0.05; // Units: seconds +constexpr double MAX_DELAY_TIME = 0.25; // Units: seconds +constexpr size_t DROP_FRAMES_SAMPLE_DELAY = 16000; // Units: samples + +constexpr double SMOOTHING_FACTOR = 0.007; + +struct TimeStretcher::Impl { + soundtouch::SoundTouch soundtouch; + + steady_clock::time_point frame_timer = steady_clock::now(); + size_t samples_queued = 0; + + double smoothed_ratio = 1.0; + + double sample_rate = static_cast<double>(native_sample_rate); +}; + +std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) { + // This is a very simple algorithm without any fancy control theory. It works and is stable. + + double ratio = CalculateCurrentRatio(); + ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue); + impl->smoothed_ratio = (1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio; + impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio); + + // SoundTouch's tempo definition the inverse of our ratio definition. + impl->soundtouch.setTempo(1.0 / impl->smoothed_ratio); + + std::vector<s16> samples = GetSamples(); + if (samples_in_queue >= DROP_FRAMES_SAMPLE_DELAY) { + samples.clear(); + LOG_DEBUG(Audio, "Dropping frames!"); + } + return samples; +} + +TimeStretcher::TimeStretcher() : impl(std::make_unique<Impl>()) { + impl->soundtouch.setPitch(1.0); + impl->soundtouch.setChannels(2); + impl->soundtouch.setSampleRate(native_sample_rate); + Reset(); +} + +TimeStretcher::~TimeStretcher() { + impl->soundtouch.clear(); +} + +void TimeStretcher::SetOutputSampleRate(unsigned int sample_rate) { + impl->sample_rate = static_cast<double>(sample_rate); + impl->soundtouch.setRate(static_cast<double>(native_sample_rate) / impl->sample_rate); +} + +void TimeStretcher::AddSamples(const s16* buffer, size_t num_samples) { + impl->soundtouch.putSamples(buffer, static_cast<uint>(num_samples)); + impl->samples_queued += num_samples; +} + +void TimeStretcher::Flush() { + impl->soundtouch.flush(); +} + +void TimeStretcher::Reset() { + impl->soundtouch.setTempo(1.0); + impl->soundtouch.clear(); + impl->smoothed_ratio = 1.0; + impl->frame_timer = steady_clock::now(); + impl->samples_queued = 0; + SetOutputSampleRate(native_sample_rate); +} + +double TimeStretcher::CalculateCurrentRatio() { + const steady_clock::time_point now = steady_clock::now(); + const std::chrono::duration<double> duration = now - impl->frame_timer; + + const double expected_time = static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate); + const double actual_time = duration.count(); + + double ratio; + if (expected_time != 0) { + ratio = ClampRatio(actual_time / expected_time); + } else { + ratio = impl->smoothed_ratio; + } + + impl->frame_timer = now; + impl->samples_queued = 0; + + return ratio; +} + +double TimeStretcher::CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const { + const size_t min_sample_delay = static_cast<size_t>(MIN_DELAY_TIME * impl->sample_rate); + const size_t max_sample_delay = static_cast<size_t>(MAX_DELAY_TIME * impl->sample_rate); + + if (sample_delay < min_sample_delay) { + // Make the ratio bigger. + ratio = ratio > 1.0 ? ratio * ratio : sqrt(ratio); + } else if (sample_delay > max_sample_delay) { + // Make the ratio smaller. + ratio = ratio > 1.0 ? sqrt(ratio) : ratio * ratio; + } + + return ClampRatio(ratio); +} + +std::vector<s16> TimeStretcher::GetSamples() { + uint available = impl->soundtouch.numSamples(); + + std::vector<s16> output(static_cast<size_t>(available) * 2); + + impl->soundtouch.receiveSamples(output.data(), available); + + return output; +} + +} // namespace AudioCore |