summaryrefslogtreecommitdiff
path: root/src/audio_core/time_stretch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core/time_stretch.cpp')
-rw-r--r--src/audio_core/time_stretch.cpp143
1 files changed, 0 insertions, 143 deletions
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
deleted file mode 100644
index 437cf9752..000000000
--- a/src/audio_core/time_stretch.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cmath>
7#include <vector>
8#include <SoundTouch.h>
9#include "audio_core/audio_core.h"
10#include "audio_core/time_stretch.h"
11#include "common/common_types.h"
12#include "common/logging/log.h"
13#include "common/math_util.h"
14
15using steady_clock = std::chrono::steady_clock;
16
17namespace AudioCore {
18
19constexpr double MIN_RATIO = 0.1;
20constexpr double MAX_RATIO = 100.0;
21
22static double ClampRatio(double ratio) {
23 return MathUtil::Clamp(ratio, MIN_RATIO, MAX_RATIO);
24}
25
26constexpr double MIN_DELAY_TIME = 0.05; // Units: seconds
27constexpr double MAX_DELAY_TIME = 0.25; // Units: seconds
28constexpr size_t DROP_FRAMES_SAMPLE_DELAY = 16000; // Units: samples
29
30constexpr double SMOOTHING_FACTOR = 0.007;
31
32struct TimeStretcher::Impl {
33 soundtouch::SoundTouch soundtouch;
34
35 steady_clock::time_point frame_timer = steady_clock::now();
36 size_t samples_queued = 0;
37
38 double smoothed_ratio = 1.0;
39
40 double sample_rate = static_cast<double>(native_sample_rate);
41};
42
43std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) {
44 // This is a very simple algorithm without any fancy control theory. It works and is stable.
45
46 double ratio = CalculateCurrentRatio();
47 ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue);
48 impl->smoothed_ratio =
49 (1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio;
50 impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio);
51
52 // SoundTouch's tempo definition the inverse of our ratio definition.
53 impl->soundtouch.setTempo(1.0 / impl->smoothed_ratio);
54
55 std::vector<s16> samples = GetSamples();
56 if (samples_in_queue >= DROP_FRAMES_SAMPLE_DELAY) {
57 samples.clear();
58 LOG_TRACE(Audio, "Dropping frames!");
59 }
60 return samples;
61}
62
63TimeStretcher::TimeStretcher() : impl(std::make_unique<Impl>()) {
64 impl->soundtouch.setPitch(1.0);
65 impl->soundtouch.setChannels(2);
66 impl->soundtouch.setSampleRate(native_sample_rate);
67 Reset();
68}
69
70TimeStretcher::~TimeStretcher() {
71 impl->soundtouch.clear();
72}
73
74void TimeStretcher::SetOutputSampleRate(unsigned int sample_rate) {
75 impl->sample_rate = static_cast<double>(sample_rate);
76 impl->soundtouch.setRate(static_cast<double>(native_sample_rate) / impl->sample_rate);
77}
78
79void TimeStretcher::AddSamples(const s16* buffer, size_t num_samples) {
80 impl->soundtouch.putSamples(buffer, static_cast<uint>(num_samples));
81 impl->samples_queued += num_samples;
82}
83
84void TimeStretcher::Flush() {
85 impl->soundtouch.flush();
86}
87
88void TimeStretcher::Reset() {
89 impl->soundtouch.setTempo(1.0);
90 impl->soundtouch.clear();
91 impl->smoothed_ratio = 1.0;
92 impl->frame_timer = steady_clock::now();
93 impl->samples_queued = 0;
94 SetOutputSampleRate(native_sample_rate);
95}
96
97double TimeStretcher::CalculateCurrentRatio() {
98 const steady_clock::time_point now = steady_clock::now();
99 const std::chrono::duration<double> duration = now - impl->frame_timer;
100
101 const double expected_time =
102 static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate);
103 const double actual_time = duration.count();
104
105 double ratio;
106 if (expected_time != 0) {
107 ratio = ClampRatio(actual_time / expected_time);
108 } else {
109 ratio = impl->smoothed_ratio;
110 }
111
112 impl->frame_timer = now;
113 impl->samples_queued = 0;
114
115 return ratio;
116}
117
118double TimeStretcher::CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const {
119 const size_t min_sample_delay = static_cast<size_t>(MIN_DELAY_TIME * impl->sample_rate);
120 const size_t max_sample_delay = static_cast<size_t>(MAX_DELAY_TIME * impl->sample_rate);
121
122 if (sample_delay < min_sample_delay) {
123 // Make the ratio bigger.
124 ratio = ratio > 1.0 ? ratio * ratio : sqrt(ratio);
125 } else if (sample_delay > max_sample_delay) {
126 // Make the ratio smaller.
127 ratio = ratio > 1.0 ? sqrt(ratio) : ratio * ratio;
128 }
129
130 return ClampRatio(ratio);
131}
132
133std::vector<s16> TimeStretcher::GetSamples() {
134 uint available = impl->soundtouch.numSamples();
135
136 std::vector<s16> output(static_cast<size_t>(available) * 2);
137
138 impl->soundtouch.receiveSamples(output.data(), available);
139
140 return output;
141}
142
143} // namespace AudioCore