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.

analog_engine.hpp 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Mutable Instruments Streams emulation for VCV Rack
  2. // Copyright (C) 2020 Tyler Coy
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. #pragma once
  17. #include <rack.hpp>
  18. #include "aafilter.hpp"
  19. namespace streams
  20. {
  21. using namespace rack;
  22. class AnalogEngine
  23. {
  24. public:
  25. struct ChannelFrame
  26. {
  27. // Parameters
  28. float level_mod_knob;
  29. float response_knob;
  30. // Inputs
  31. float signal_in;
  32. float level_cv;
  33. float dac_cv;
  34. float pwm_cv;
  35. // Outputs
  36. float signal_out;
  37. float adc_out;
  38. };
  39. struct Frame
  40. {
  41. ChannelFrame ch1;
  42. ChannelFrame ch2;
  43. };
  44. AnalogEngine()
  45. {
  46. Reset();
  47. SetSampleRate(1.f);
  48. }
  49. void Reset(void)
  50. {
  51. v_out_ = 0.f;
  52. }
  53. void SetSampleRate(float sample_rate)
  54. {
  55. sample_time_ = 1.f / sample_rate;
  56. oversampling_ = OversamplingFactor(sample_rate);
  57. float oversampled_rate = sample_rate * oversampling_;
  58. up_filter_[0].Init(sample_rate);
  59. up_filter_[1].Init(sample_rate);
  60. down_filter_.Init(sample_rate);
  61. auto cutoff = float_4(kDACFilterCutoff,
  62. kDACFilterCutoff,
  63. kPWMFilterCutoff,
  64. kPWMFilterCutoff);
  65. rc_lpf_.setCutoffFreq(cutoff / oversampled_rate);
  66. }
  67. void Process(Frame& frame)
  68. {
  69. auto a_inputs = float_4(frame.ch1.signal_in,
  70. frame.ch2.signal_in,
  71. frame.ch1.level_cv,
  72. frame.ch2.level_cv);
  73. auto d_inputs = float_4(frame.ch1.dac_cv,
  74. frame.ch2.dac_cv,
  75. frame.ch1.pwm_cv,
  76. frame.ch2.pwm_cv);
  77. auto level_mod = float_4(frame.ch1.level_mod_knob,
  78. frame.ch2.level_mod_knob, 0.f, 0.f);
  79. auto response = float_4(frame.ch1.response_knob,
  80. frame.ch2.response_knob, 0.f, 0.f);
  81. float_4 output;
  82. float timestep = sample_time_ / oversampling_;
  83. for (int i = 0; i < oversampling_; i++)
  84. {
  85. // Upsample and apply anti-aliasing filters
  86. a_inputs = up_filter_[0].Process(
  87. (i == 0) ? (a_inputs * oversampling_) : 0.f);
  88. d_inputs = up_filter_[1].Process(
  89. (i == 0) ? (d_inputs * oversampling_) : 0.f);
  90. rc_lpf_.process(d_inputs);
  91. d_inputs = rc_lpf_.lowpass();
  92. float_4 signal_in = a_inputs;
  93. float_4 level_cv = _mm_movehl_ps(a_inputs.v, a_inputs.v);
  94. float_4 dac_cv = d_inputs;
  95. auto level = CalculateLevel(dac_cv, level_cv, level_mod, response);
  96. signal_in *= level;
  97. float_4 pwm_cv = _mm_movehl_ps(d_inputs.v, d_inputs.v);
  98. pwm_cv *= kPWMCVInputR / (kPWMCVInputR + kPWMCVOutputR);
  99. auto rad_per_s = -PinVoltageToLevel(pwm_cv) / kFilterCoreRC;
  100. // Solve each VCF cell using the backward Euler method.
  101. float_4 v_in = _mm_movelh_ps(signal_in.v, v_out_.v);
  102. v_out_ = (v_in + v_out_) * simd::exp(rad_per_s * timestep) - v_in;
  103. v_out_ = simd::clamp(v_out_, -kClampVoltage, kClampVoltage);
  104. // Pre-downsample anti-alias filtering
  105. // output will contain the lower two elements from level and the
  106. // upper two elements from v_out_
  107. output =
  108. _mm_shuffle_ps(level.v, v_out_.v, _MM_SHUFFLE(3, 2, 1, 0));
  109. output = down_filter_.Process(output);
  110. }
  111. frame.ch1.signal_out = output[2];
  112. frame.ch2.signal_out = output[3];
  113. // We don't care too much about aliasing in this signal, since it's
  114. // only fed back to the digital section for VU metering.
  115. output = LevelToPinVoltage(output);
  116. frame.ch1.adc_out = output[0];
  117. frame.ch2.adc_out = output[1];
  118. }
  119. protected:
  120. using float_4 = simd::float_4;
  121. float sample_time_;
  122. int oversampling_;
  123. UpsamplingAAFilter<float_4> up_filter_[2];
  124. DownsamplingAAFilter<float_4> down_filter_;
  125. dsp::TRCFilter<float_4> rc_lpf_;
  126. float_4 v_out_;
  127. // Calculate level from the VCA control pin voltage
  128. template <typename T>
  129. T PinVoltageToLevel(T v_control)
  130. {
  131. return simd::pow(10.f, v_control / (kVCAGainConstant * 20.f));
  132. }
  133. // Calculate VCA control pin voltage from level
  134. template <typename T>
  135. T LevelToPinVoltage(T level)
  136. {
  137. T volts = kVCAGainConstant * 20.f * simd::log10(level);
  138. return simd::ifelse(level > 0.f, volts, kClampVoltage);
  139. }
  140. // Calculate level from the CV inputs and pots
  141. template <typename T>
  142. T CalculateLevel(T dac_cv, T level_cv, T level_mod, T response)
  143. {
  144. T power = (kLevelResponseMinR + kLevelResponsePotR) /
  145. (kLevelResponseMinR + (kLevelResponsePotR * response));
  146. T i_level = level_mod * level_cv / kLevelCVInputR;
  147. T i_dac = dac_cv / (kDACCVOutputR + kDACCVInputR);
  148. T i_in = i_level + i_dac + kVCAOffsetI;
  149. T base = -i_in / kLevelRefI;
  150. T level = simd::pow(base, power);
  151. level = simd::ifelse(base > 0.f, level, 0.f);
  152. return simd::fmin(level, kVCAMaxLevel);
  153. }
  154. // The 2164's gain constant is -33mV/dB
  155. static constexpr float kVCAGainConstant = -33e-3f;
  156. // The 2164's maximum gain is +20dB
  157. static constexpr float kVCAMaxLevel = 10.f;
  158. static constexpr float kLevelCVInputR = 100e3f;
  159. static constexpr float kDACCVOutputR = 11e3f;
  160. static constexpr float kDACCVInputR = 14e3f;
  161. static constexpr float kVCAOffsetI = -10.f / 10e6f;
  162. static constexpr float kPWMCVOutputR = 1.2e3f;
  163. static constexpr float kPWMCVInputR = 2.5e3f;
  164. // Level response VCA input current
  165. static constexpr float kLevelRefI = -10.f / 200e3f;
  166. // Level response potentiometer and series resistor
  167. static constexpr float kLevelResponsePotR = 10e3f;
  168. static constexpr float kLevelResponseMinR = 510.f;
  169. // Opamp saturation voltage
  170. static constexpr float kClampVoltage = 10.5f;
  171. // 1N4148 forward voltage
  172. static constexpr float kDiodeVoltage = 745e-3f;
  173. static constexpr float kDACFilterCutoff = 12.7e3f;
  174. static constexpr float kPWMFilterCutoff = 242.f;
  175. static constexpr float kFilterCoreR = 100e3f;
  176. static constexpr float kFilterCoreC = 33e-12f;
  177. static constexpr float kFilterCoreRC = kFilterCoreR * kFilterCoreC;
  178. };
  179. }