// Mutable Instruments Streams emulation for VCV Rack // Copyright (C) 2020 Tyler Coy // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #pragma once #include #include #include "resampler.hpp" #include "analog_engine.hpp" #include "digital_engine.hpp" namespace streams { using namespace rack; class StreamsEngine { public: struct ChannelFrame { // Parameters float shape_knob; float mod_knob; float level_mod_knob; float response_knob; bool function_button; // Inputs float excite_in; float signal_in; float level_cv; bool signal_in_connected; bool level_cv_connected; // Outputs float signal_out; // Lights float led_green[4]; float led_red[4]; }; struct Frame { ChannelFrame ch1; ChannelFrame ch2; bool metering_button; bool lights_updated; }; StreamsEngine() { Reset(); SetSampleRate(1.f); } void Reset(void) { adc_lpf_.reset(); resampler_.Reset(); analog_engine_.Reset(); digital_engine_.Reset(); adc_feedback_[0] = 0.f; adc_feedback_[1] = 0.f; } void SetSampleRate(float sample_rate) { adc_lpf_.setCutoffFreq(kAdcFilterCutoff / sample_rate); resampler_.Init(sample_rate, DigitalEngine::kSampleRate, 0); analog_engine_.SetSampleRate(sample_rate); } void Randomize(void) { digital_engine_.Randomize(); } void ApplySettings(const UiSettings& settings) { digital_engine_.ApplySettings(settings); } const UiSettings& ui_settings(void) { return digital_engine_.ui_settings(); } void SyncUI(const StreamsEngine& other) { digital_engine_.SyncUI(other.digital_engine_); } void Process(Frame& frame) { frame.lights_updated = false; float ch1_signal_in = frame.ch1.signal_in_connected ? frame.ch1.signal_in : kSignalInNormalV; float ch2_signal_in = frame.ch2.signal_in_connected ? frame.ch2.signal_in : kSignalInNormalV; float ch1_level_cv = frame.ch1.level_cv_connected ? frame.ch1.level_cv : kLevelNormalV; float ch2_level_cv = frame.ch2.level_cv_connected ? frame.ch2.level_cv : kLevelNormalV; Rsmp::InputFrame d_input; d_input.samples[0] = ch1_signal_in; d_input.samples[1] = ch2_signal_in; d_input.samples[2] = frame.ch1.excite_in; d_input.samples[3] = frame.ch2.excite_in; d_input.samples[4] = adc_feedback_[0]; d_input.samples[5] = adc_feedback_[1]; auto adc_input = simd::float_4::load(d_input.samples); adc_lpf_.process(adc_input); adc_input = adc_lpf_.lowpass(); adc_input = kAdcFilterOffset + adc_input * kAdcFilterGain; adc_input.store(d_input.samples); DigitalEngine::Frame d_frame; Rsmp::OutputFrame d_output = resampler_.Process(d_input, [&](Rsmp::OutputFrame* output, const Rsmp::InputFrame* input) { d_frame.ch1.shape_knob = frame.ch1.shape_knob; d_frame.ch1.mod_knob = frame.ch1.mod_knob; d_frame.ch1.level_mod_knob = frame.ch1.level_mod_knob; d_frame.ch1.response_knob = frame.ch1.response_knob; d_frame.ch2.shape_knob = frame.ch2.shape_knob; d_frame.ch2.mod_knob = frame.ch2.mod_knob; d_frame.ch2.level_mod_knob = frame.ch2.level_mod_knob; d_frame.ch2.response_knob = frame.ch2.response_knob; d_frame.ch1.function_button = frame.ch1.function_button; d_frame.ch2.function_button = frame.ch2.function_button; d_frame.metering_button = frame.metering_button; for (int i = 0; i < kBlockSize; i++) { d_frame.ch1.signal_in[i] = input[i].samples[0]; d_frame.ch2.signal_in[i] = input[i].samples[1]; d_frame.ch1.excite_in[i] = input[i].samples[2]; d_frame.ch2.excite_in[i] = input[i].samples[3]; d_frame.ch1.level_adc_in[i] = input[i].samples[4]; d_frame.ch2.level_adc_in[i] = input[i].samples[5]; } digital_engine_.Process(d_frame); for (int i = 0; i < kBlockSize; i++) { output[i].samples[0] = d_frame.ch1.dac_out[i]; output[i].samples[1] = d_frame.ch1.pwm_out[i]; output[i].samples[2] = d_frame.ch2.dac_out[i]; output[i].samples[3] = d_frame.ch2.pwm_out[i]; } frame.ch1.led_green[0] = d_frame.ch1.led_green[0]; frame.ch1.led_green[1] = d_frame.ch1.led_green[1]; frame.ch1.led_green[2] = d_frame.ch1.led_green[2]; frame.ch1.led_green[3] = d_frame.ch1.led_green[3]; frame.ch1.led_red[0] = d_frame.ch1.led_red[0]; frame.ch1.led_red[1] = d_frame.ch1.led_red[1]; frame.ch1.led_red[2] = d_frame.ch1.led_red[2]; frame.ch1.led_red[3] = d_frame.ch1.led_red[3]; frame.ch2.led_green[0] = d_frame.ch2.led_green[0]; frame.ch2.led_green[1] = d_frame.ch2.led_green[1]; frame.ch2.led_green[2] = d_frame.ch2.led_green[2]; frame.ch2.led_green[3] = d_frame.ch2.led_green[3]; frame.ch2.led_red[0] = d_frame.ch2.led_red[0]; frame.ch2.led_red[1] = d_frame.ch2.led_red[1]; frame.ch2.led_red[2] = d_frame.ch2.led_red[2]; frame.ch2.led_red[3] = d_frame.ch2.led_red[3]; frame.lights_updated = true; }); AnalogEngine::Frame a_frame; a_frame.ch1.level_mod_knob = frame.ch1.level_mod_knob; a_frame.ch1.response_knob = frame.ch1.response_knob; a_frame.ch2.level_mod_knob = frame.ch2.level_mod_knob; a_frame.ch2.response_knob = frame.ch2.response_knob; a_frame.ch1.signal_in = ch1_signal_in; a_frame.ch1.level_cv = ch1_level_cv; a_frame.ch2.signal_in = ch2_signal_in; a_frame.ch2.level_cv = ch2_level_cv; a_frame.ch1.dac_cv = d_output.samples[0]; a_frame.ch1.pwm_cv = d_output.samples[1]; a_frame.ch2.dac_cv = d_output.samples[2]; a_frame.ch2.pwm_cv = d_output.samples[3]; analog_engine_.Process(a_frame); frame.ch1.signal_out = a_frame.ch1.signal_out; frame.ch2.signal_out = a_frame.ch2.signal_out; adc_feedback_[0] = a_frame.ch1.adc_out; adc_feedback_[1] = a_frame.ch2.adc_out; } protected: static constexpr int kBlockSize = 16; static constexpr float kAdcFilterCutoff = 1.f / (2 * M_PI * 20e3f * 1e-9f); static constexpr float kAdcFilterGain = -20e3f / 100e3f; static constexpr float kAdcFilterOffset = -10.f * -20e3f / 120e3f; static constexpr float kSignalInNormalV = 5.f; static constexpr float kLevelNormalV = 8.f; dsp::TRCFilter adc_lpf_; using Rsmp = InterpolatingResampler<6, 4, kBlockSize, 256>; Rsmp resampler_; AnalogEngine analog_engine_; DigitalEngine digital_engine_; float adc_feedback_[2]; }; }