Browse Source

Addressing review comments

tags/v2.1.0
hemmer 3 years ago
parent
commit
9f6ce79c2a
4 changed files with 84 additions and 133 deletions
  1. +5
    -23
      src/ADSR.cpp
  2. +39
    -56
      src/Mex.cpp
  3. +19
    -47
      src/Morphader.cpp
  4. +21
    -7
      src/Muxlicer.hpp

+ 5
- 23
src/ADSR.cpp View File

@@ -221,28 +221,10 @@ struct ADSR : Module {
static constexpr float maxStageTime = 10.f; // in seconds

// given a value from the slider and/or cv (rescaled to range 0 to 1), transform into the appropriate time in seconds
static float convertCVToTimeInSeconds(float cv) {
float cv2 = cv * cv;
// according to hardware, slider appears to respond roughly as a quartic
return minStageTime + (maxStageTime - minStageTime) * cv2 * cv2;
static float convertCVToTimeInSeconds(float cv) {
return minStageTime * std::pow(maxStageTime / minStageTime, cv);
}

// given a time in seconds, transform into the appropriate CV/slider value (in range 0, 1)
static float convertTimeInSecondsToCV(float timeInSecs) {
// according to hardware, slider appears to respond roughly as a quartic
return std::pow((timeInSecs - minStageTime) / (maxStageTime - minStageTime), 0.25f);
}

struct StageTimeParam : ParamQuantity {
std::string getDisplayValueString() override {
return string::f("%.3f", convertCVToTimeInSeconds(getValue()));
}

void setDisplayValue(float v) override {
ParamQuantity::setDisplayValue(convertTimeInSecondsToCV(v));
}
};

struct TriggerGateParamQuantity : ParamQuantity {
std::string getDisplayValueString() override {
switch ((EnvelopeMode) getValue()) {
@@ -259,10 +241,10 @@ struct ADSR : Module {
configParam(MANUAL_TRIGGER_PARAM, 0.f, 1.f, 0.f, "Trigger envelope");
configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Envelope shape");

configParam<StageTimeParam>(ATTACK_PARAM, 0.f, 1.f, 0.f, "Attack time", "s");
configParam<StageTimeParam>(DECAY_PARAM, 0.f, 1.f, 0.f, "Decay time", "s");
configParam(ATTACK_PARAM, 0.f, 1.f, 0.f, "Attack time", "s", maxStageTime / minStageTime, minStageTime);
configParam(DECAY_PARAM, 0.f, 1.f, 0.f, "Decay time", "s", maxStageTime / minStageTime, minStageTime);
configParam(SUSTAIN_PARAM, 0.f, 1.f, 0.f, "Sustain level", "%", 0.f, 100.f);
configParam<StageTimeParam>(RELEASE_PARAM, 0.f, 1.f, 0.f, "Release time", "s");
configParam(RELEASE_PARAM, 0.f, 1.f, 0.f, "Release time", "s", maxStageTime / minStageTime, minStageTime);

cvDivider.setDivision(16);
}


+ 39
- 56
src/Mex.cpp View File

@@ -28,10 +28,6 @@ struct Mex : Module {

dsp::SchmittTrigger gateInTrigger;

// this expander communicates with the mother module (Muxlicer) purely
// through this pointer (it cannot modify Muxlicer, read-only)
Muxlicer const* mother = nullptr;

struct GateSwitchParamQuantity : ParamQuantity {
std::string getDisplayValueString() override {

@@ -52,72 +48,59 @@ struct Mex : Module {
}
}

Muxlicer* findHostModulePtr(Module* module) {
if (module) {
if (module->leftExpander.module) {
// if it's Muxlicer, we're done
if (module->leftExpander.module->model == modelMuxlicer) {
return reinterpret_cast<Muxlicer*>(module->leftExpander.module);
}
// if it's Mex, keep recursing
else if (module->leftExpander.module->model == modelMex) {
return findHostModulePtr(module->leftExpander.module);
}
}
}

return nullptr;
}

void process(const ProcessArgs& args) override {

for (int i = 0; i < 8; i++) {
lights[i].setBrightness(0.f);
}

if (leftExpander.module) {
// this expander is active if:
// * muxlicer is to the left or
if (leftExpander.module->model == modelMuxlicer) {
mother = reinterpret_cast<Muxlicer*>(leftExpander.module);
}
// * an active Mex is to the left
else if (leftExpander.module->model == modelMex) {
Mex* moduleMex = reinterpret_cast<Mex*>(leftExpander.module);
if (moduleMex) {
mother = moduleMex->mother;
}
}
else {
mother = nullptr;
}
Muxlicer const* mother = findHostModulePtr(this);

if (mother) {
if (mother) {

float gate = 0.f;
float gate = 0.f;

if (mother->playState != Muxlicer::STATE_STOPPED) {
const int currentStep = clamp(mother->addressIndex, 0, 7);
StepState state = (StepState) params[STEP_PARAM + currentStep].getValue();
if (state == MUXLICER_MODE) {
gate = mother->isAllGatesOutHigh;
if (mother->playState != Muxlicer::STATE_STOPPED) {
const int currentStep = clamp(mother->addressIndex, 0, 7);
StepState state = (StepState) params[STEP_PARAM + currentStep].getValue();
if (state == MUXLICER_MODE) {
gate = mother->isAllGatesOutHigh;
}
else if (state == GATE_IN_MODE) {
// gate in will convert non-gate signals to gates (via schmitt trigger)
// if input is present
if (inputs[GATE_IN_INPUT].isConnected()) {
gateInTrigger.process(inputs[GATE_IN_INPUT].getVoltage());
gate = gateInTrigger.isHigh();
}
else if (state == GATE_IN_MODE) {
// gate in will convert non-gate signals to gates (via schmitt trigger)
// if input is present
if (inputs[GATE_IN_INPUT].isConnected()) {
gateInTrigger.process(inputs[GATE_IN_INPUT].getVoltage());
gate = gateInTrigger.isHigh();
}
// otherwise the main Muxlicer output clock (including divisions/multiplications)
// is normalled in
else {
gate = mother->isOutputClockHigh;
}
// otherwise the main Muxlicer output clock (including divisions/multiplications)
// is normalled in
else {
gate = mother->isOutputClockHigh;
}

lights[currentStep].setBrightness(gate);
}

outputs[OUT_OUTPUT].setVoltage(gate * 10.f);

// if there's another Mex to the right, update it to also point at the message we just received,
// i.e. just forward on the message
if (rightExpander.module && rightExpander.module->model == modelMex) {
Mex* moduleMexRight = reinterpret_cast<Mex*>(rightExpander.module);

// assign current message pointer to the right expander
moduleMexRight->mother = mother;
}
lights[currentStep].setBrightness(gate);
}
}
// if we've become disconnected, i.e. no module to the left, then break the connection
// which will propagate to all expanders to the right
else {
mother = nullptr;

outputs[OUT_OUTPUT].setVoltage(gate * 10.f);
}
}
};


+ 19
- 47
src/Morphader.cpp View File

@@ -1,5 +1,6 @@
#include "plugin.hpp"

using simd::float_4;

// equal sum crossfade, -1 <= p <= 1
template <typename T>
@@ -16,43 +17,8 @@ inline T equalPowerCrossfade(T a, T b, const float p) {

// TExponentialSlewLimiter doesn't appear to work as is required for this application.
// I think it is due to the absence of the logic that stops the output rising / falling too quickly,
// i.e. faster than the original signal? I think the following modification would yield the
// expected behaviour (see 2 lines in process).
/*
template <typename T = float>
struct TExponentialSlewLimiter {
T out = 0.f;
T riseLambda = 0.f;
T fallLambda = 0.f;

void reset() {
out = 0.f;
}

void setRiseFall(T riseLambda, T fallLambda) {
this->riseLambda = riseLambda;
this->fallLambda = fallLambda;
}
T process(T deltaTime, T in) {
// MODIFICATION:
T rising = in > out;
T lambda = simd::ifelse(rising, riseLambda, fallLambda);
T y = out + (in - out) * lambda * deltaTime;
// If the change from the old out to the new out is too small for floats, set `in` directly.
out = simd::ifelse(out == y, in, y);

// MODIFICATION:
out = simd::ifelse(rising, simd::ifelse(out > in, in, y), simd::ifelse(out < in, in, y));
return out;
}
DEPRECATED T process(T in) {
return process(1.f, in);
}
};
*/

// For now, I provide this implementation (essentialy the same as SlewLimiter.cpp), but ideally I
// would replace with updated library function
// i.e. faster than the original signal? For now, we use this implementation (essentialy the same as
// SlewLimiter.cpp)
struct ExpLogSlewLimiter {

float out = 0.f;
@@ -114,7 +80,7 @@ struct Morphader : Module {
};

static const int NUM_MIXER_CHANNELS = 4;
const simd::float_4 normal10VSimd = {10.f};
const float_4 normal10VSimd = {10.f};
ExpLogSlewLimiter slewLimiter;

// minimum and maximum slopes in volts per second, they specify the time to get
@@ -152,9 +118,9 @@ struct Morphader : Module {
}

// determine the cross-fade between -1 (A) and +1 (B) for each of the 4 channels
simd::float_4 determineChannelCrossfades(const float deltaTime) {
float_4 determineChannelCrossfades(const float deltaTime) {

simd::float_4 channelCrossfades = {};
float_4 channelCrossfades = {};
const float slewLambda = 2.0f / params[FADER_LAG_PARAM].getValue();
slewLimiter.setSlew(slewLambda);
const float masterCrossfadeValue = slewLimiter.process(deltaTime, params[FADER_PARAM].getValue());
@@ -192,8 +158,8 @@ struct Morphader : Module {
void process(const ProcessArgs& args) override {

int maxChannels = 1;
simd::float_4 mix[4] = {0.f};
const simd::float_4 channelCrossfades = determineChannelCrossfades(args.sampleTime);
float_4 mix[4] = {};
const float_4 channelCrossfades = determineChannelCrossfades(args.sampleTime);

for (int i = 0; i < NUM_MIXER_CHANNELS; i++) {

@@ -204,10 +170,10 @@ struct Morphader : Module {
maxChannels = std::max(maxChannels, channels);
}

simd::float_4 out[4] = {0.f};
float_4 out[4] = {};
for (int c = 0; c < channels; c += 4) {
simd::float_4 inA = inputs[A_INPUT + i].getNormalVoltageSimd(normal10VSimd, c) * params[A_LEVEL + i].getValue();
simd::float_4 inB = inputs[B_INPUT + i].getNormalVoltageSimd(normal10VSimd, c) * params[B_LEVEL + i].getValue();
float_4 inA = inputs[A_INPUT + i].getNormalVoltageSimd(normal10VSimd, c) * params[A_LEVEL + i].getValue();
float_4 inB = inputs[B_INPUT + i].getNormalVoltageSimd(normal10VSimd, c) * params[B_LEVEL + i].getValue();

switch (static_cast<CrossfadeMode>(params[MODE + i].getValue())) {
case CV_MODE: {
@@ -221,7 +187,9 @@ struct Morphader : Module {
out[c / 4] = equalPowerCrossfade(inA, inB, channelCrossfades[i]);
break;
}
default: assert(false);
default: {
out[c / 4] = 0.f;
}
}
}

@@ -257,7 +225,11 @@ struct Morphader : Module {
lights[B_LED + i].setBrightness(equalSumCrossfade(0.f, 1.f, channelCrossfades[i]));
break;
}
default: assert(false);
default: {
lights[A_LED + i].setBrightness(0.f);
lights[B_LED + i].setBrightness(0.f);
break;
}
}
} // end loop over mixer channels
}


+ 21
- 7
src/Muxlicer.hpp View File

@@ -641,25 +641,39 @@ struct Muxlicer : Module {

void dataFromJson(json_t* rootJ) override {
json_t* modeJ = json_object_get(rootJ, "modeCOMIO");
modeCOMIO = (Muxlicer::ModeCOMIO) json_integer_value(modeJ);
if (modeJ) {
modeCOMIO = (Muxlicer::ModeCOMIO) json_integer_value(modeJ);
}

json_t* quadraticJ = json_object_get(rootJ, "quadraticGatesOnly");
quadraticGatesOnly = json_boolean_value(quadraticJ);
if (quadraticJ) {
quadraticGatesOnly = json_boolean_value(quadraticJ);
}

json_t* allInNormalVoltageJ = json_object_get(rootJ, "allInNormalVoltage");
allInNormalVoltage = json_integer_value(allInNormalVoltageJ);
if (allInNormalVoltageJ) {
allInNormalVoltage = json_integer_value(allInNormalVoltageJ);
}

json_t* mainClockMultDivJ = json_object_get(rootJ, "mainClockMultDiv");
mainClockMultDiv.multDiv = json_integer_value(mainClockMultDivJ);
if (mainClockMultDivJ) {
mainClockMultDiv.multDiv = json_integer_value(mainClockMultDivJ);
}

json_t* outputClockMultDivJ = json_object_get(rootJ, "outputClockMultDiv");
outputClockMultDiv.multDiv = json_integer_value(outputClockMultDivJ);
if (outputClockMultDivJ) {
outputClockMultDiv.multDiv = json_integer_value(outputClockMultDivJ);
}

json_t* playStateJ = json_object_get(rootJ, "playState");
playState = (PlayState) json_integer_value(playStateJ);
if (playStateJ) {
playState = (PlayState) json_integer_value(playStateJ);
}

json_t* outputClockFollowsPlayModeJ = json_object_get(rootJ, "outputClockFollowsPlayMode");
outputClockFollowsPlayMode = json_boolean_value(outputClockFollowsPlayModeJ);
if (outputClockFollowsPlayModeJ) {
outputClockFollowsPlayMode = json_boolean_value(outputClockFollowsPlayModeJ);
}

updateParamFromMainClockMultDiv();
}


Loading…
Cancel
Save