time_stretch.cpp 4.3 KB

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