You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
6.1KB

  1. // Copyright 2015 Olivier Gillet.
  2. //
  3. // Author: Olivier Gillet (ol.gillet@gmail.com)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. // See http://creativecommons.org/licenses/MIT/ for more information.
  24. //
  25. // -----------------------------------------------------------------------------
  26. //
  27. // Onset detector.
  28. #ifndef RINGS_DSP_ONSET_DETECTOR_H_
  29. #define RINGS_DSP_ONSET_DETECTOR_H_
  30. #include "stmlib/stmlib.h"
  31. #include <algorithm>
  32. #include "stmlib/dsp/dsp.h"
  33. #include "stmlib/dsp/filter.h"
  34. namespace rings {
  35. using namespace std;
  36. using namespace stmlib;
  37. class ZScorer {
  38. public:
  39. ZScorer() { }
  40. ~ZScorer() { }
  41. void Init(float cutoff) {
  42. coefficient_ = cutoff;
  43. mean_ = 0.0f;
  44. variance_ = 0.00f;
  45. }
  46. inline float Normalize(float sample) {
  47. return Update(sample) / Sqrt(variance_);
  48. }
  49. inline bool Test(float sample, float threshold) {
  50. float value = Update(sample);
  51. return value > Sqrt(variance_) * threshold;
  52. }
  53. inline bool Test(float sample, float threshold, float absolute_threshold) {
  54. float value = Update(sample);
  55. return value > Sqrt(variance_) * threshold && value > absolute_threshold;
  56. }
  57. private:
  58. inline float Update(float sample) {
  59. float centered = sample - mean_;
  60. mean_ += coefficient_ * centered;
  61. variance_ += coefficient_ * (centered * centered - variance_);
  62. return centered;
  63. }
  64. float coefficient_;
  65. float mean_;
  66. float variance_;
  67. DISALLOW_COPY_AND_ASSIGN(ZScorer);
  68. };
  69. class Compressor {
  70. public:
  71. Compressor() { }
  72. ~Compressor() { }
  73. void Init(float attack, float decay, float max_gain) {
  74. attack_ = attack;
  75. decay_ = decay;
  76. level_ = 0.0f;
  77. skew_ = 1.0f / max_gain;
  78. }
  79. void Process(const float* in, float* out, size_t size) {
  80. float level = level_;
  81. while (size--) {
  82. SLOPE(level, fabs(*in), attack_, decay_);
  83. *out++ = *in++ / (skew_ + level);
  84. }
  85. level_ = level;
  86. }
  87. private:
  88. float attack_;
  89. float decay_;
  90. float level_;
  91. float skew_;
  92. DISALLOW_COPY_AND_ASSIGN(Compressor);
  93. };
  94. class OnsetDetector {
  95. public:
  96. OnsetDetector() { }
  97. ~OnsetDetector() { }
  98. void Init(
  99. float low,
  100. float low_mid,
  101. float mid_high,
  102. float decimated_sr,
  103. float ioi_time) {
  104. float ioi_f = 1.0f / (ioi_time * decimated_sr);
  105. compressor_.Init(ioi_f * 10.0f, ioi_f * 0.05f, 40.0f);
  106. low_mid_filter_.Init();
  107. mid_high_filter_.Init();
  108. low_mid_filter_.set_f_q<FREQUENCY_DIRTY>(low_mid, 0.5f);
  109. mid_high_filter_.set_f_q<FREQUENCY_DIRTY>(mid_high, 0.5f);
  110. attack_[0] = low_mid;
  111. decay_[0] = low * 0.25f;
  112. attack_[1] = low_mid;
  113. decay_[1] = low * 0.25f;
  114. attack_[2] = low_mid;
  115. decay_[2] = low * 0.25f;
  116. fill(&envelope_[0], &envelope_[3], 0.0f);
  117. fill(&energy_[0], &energy_[3], 0.0f);
  118. z_df_.Init(ioi_f * 0.05f);
  119. inhibit_time_ = static_cast<int32_t>(ioi_time * decimated_sr);
  120. inhibit_decay_ = 1.0f / (ioi_time * decimated_sr);
  121. inhibit_threshold_ = 0.0f;
  122. inhibit_counter_ = 0;
  123. onset_df_ = 0.0f;
  124. }
  125. bool Process(const float* samples, size_t size) {
  126. // Automatic gain control.
  127. compressor_.Process(samples, bands_[0], size);
  128. // Quick and dirty filter bank - split the signal in three bands.
  129. mid_high_filter_.Split(bands_[0], bands_[1], bands_[2], size);
  130. low_mid_filter_.Split(bands_[1], bands_[0], bands_[1], size);
  131. // Compute low-pass energy and onset detection function
  132. // (derivative of energy) in each band.
  133. float onset_df = 0.0f;
  134. float total_energy = 0.0f;
  135. for (int32_t i = 0; i < 3; ++i) {
  136. float* s = bands_[i];
  137. float energy = 0.0f;
  138. float envelope = envelope_[i];
  139. size_t increment = 4 >> i;
  140. for (size_t j = 0; j < size; j += increment) {
  141. SLOPE(envelope, s[j] * s[j], attack_[i], decay_[i]);
  142. energy += envelope;
  143. }
  144. energy = Sqrt(energy) * float(increment);
  145. envelope_[i] = envelope;
  146. float derivative = energy - energy_[i];
  147. onset_df += derivative + fabs(derivative);
  148. energy_[i] = energy;
  149. total_energy += energy;
  150. }
  151. onset_df_ += 0.05f * (onset_df - onset_df_);
  152. bool outlier_in_df = z_df_.Test(onset_df_, 1.0f, 0.01f);
  153. bool exceeds_energy_threshold = total_energy >= inhibit_threshold_;
  154. bool not_inhibited = !inhibit_counter_;
  155. bool has_onset = outlier_in_df && exceeds_energy_threshold && not_inhibited;
  156. if (has_onset) {
  157. inhibit_threshold_ = total_energy * 1.5f;
  158. inhibit_counter_ = inhibit_time_;
  159. } else {
  160. inhibit_threshold_ -= inhibit_decay_ * inhibit_threshold_;
  161. if (inhibit_counter_) {
  162. --inhibit_counter_;
  163. }
  164. }
  165. return has_onset;
  166. }
  167. private:
  168. Compressor compressor_;
  169. NaiveSvf low_mid_filter_;
  170. NaiveSvf mid_high_filter_;
  171. float attack_[3];
  172. float decay_[3];
  173. float energy_[3];
  174. float envelope_[3];
  175. float onset_df_;
  176. float bands_[3][32];
  177. ZScorer z_df_;
  178. float inhibit_threshold_;
  179. float inhibit_decay_;
  180. int32_t inhibit_time_;
  181. int32_t inhibit_counter_;
  182. DISALLOW_COPY_AND_ASSIGN(OnsetDetector);
  183. };
  184. } // namespace rings
  185. #endif // RINGS_DSP_ONSET_DETECTOR_H_