// 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];
};
}