Skip to content

Commit

Permalink
More Work on Quads (#726)
Browse files Browse the repository at this point in the history
- Some initialization in QuadLFO
- QuadAD looping works bidirectionally
- QuadAD Polyphony correct
- QuadAD Proper drawing items for forward/backwards triggers
  • Loading branch information
baconpaul committed Dec 11, 2022
1 parent 79565e2 commit 27b6ac5
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 43 deletions.
147 changes: 145 additions & 2 deletions src/QuadAD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,149 @@ struct QuadADWidget : public widgets::XTModuleWidget
}
};

struct ThreeStateTriggerSwitch : rack::app::Switch, style::StyleParticipant
{
widgets::BufferedDrawFunctionWidget *bdw{nullptr}, *bdwLight{nullptr};
float radius;

ThreeStateTriggerSwitch()
{
box.size = rack::mm2px(rack::Vec(3.5, 3.5));
radius = rack::mm2px(1.1);
bdw = new widgets::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size,
[this](auto v) { this->drawBackground(v); });
bdwLight = new widgets::BufferedDrawFunctionWidgetOnLayer(
rack::Vec(0, 0), box.size, [this](auto v) { this->drawLight(v); });
addChild(bdw);
addChild(bdwLight);
}

bool hovered{false};
void onHover(const HoverEvent &e) override
{
e.consume(this);
rack::app::Switch::onHover(e);
}
void onEnter(const EnterEvent &e) override
{
hovered = true;
bdw->dirty = true;
e.consume(this);

rack::app::Switch::onEnter(e);
}
void onLeave(const LeaveEvent &e) override
{
hovered = false;
bdw->dirty = true;
e.consume(this);

rack::app::Switch::onLeave(e);
}

int val()
{
if (!getParamQuantity())
return 0;
return std::round(getParamQuantity()->getValue());
}

void drawBackground(NVGcontext *vg)
{
auto state = val();
if (state == 0)
{
auto col = style()->getColor(style::XTStyle::POWER_BUTTON_LIGHT_OFF);
if (hovered)
{
col.r *= 1.2;
col.g *= 1.2;
col.b *= 1.2;
}

nvgBeginPath(vg);
nvgStrokeColor(vg, style()->getColor(style::XTStyle::PANEL_RULER));
nvgFillColor(vg, col);
nvgEllipse(vg, box.size.x * 0.5, box.size.y * 0.5, radius, radius);
nvgFill(vg);
nvgStrokeWidth(vg, 0.75);
nvgStroke(vg);
}
else
{
}
}

void drawLight(NVGcontext *vg)
{

const float halo = rack::settings::haloBrightness;
auto state = val();

if (state == 1)
{
auto col = style()->getColor(style::XTStyle::POWER_BUTTON_LIGHT_ON);

nvgBeginPath(vg);
nvgStrokeColor(vg, style()->getColor(style::XTStyle::PANEL_RULER));
nvgFillColor(vg, col);

auto cx = box.size.x * 0.5;
auto cy = box.size.y * 0.5;

auto r = radius * 1.25;
nvgMoveTo(vg, cx - r, cy - r);
nvgLineTo(vg, cx + r, cy);
nvgLineTo(vg, cx - r, cy + r);

nvgFill(vg);
nvgStrokeWidth(vg, 0.75);
nvgStroke(vg);
}
else if (state == -1)
{
auto col = style()->getColor(style::XTStyle::POWER_BUTTON_LIGHT_ON);

nvgBeginPath(vg);
nvgStrokeColor(vg, style()->getColor(style::XTStyle::PANEL_RULER));
nvgFillColor(vg, col);

auto cx = box.size.x * 0.5;
auto cy = box.size.y * 0.5;

auto r = radius * 1.25;
nvgMoveTo(vg, cx + r, cy - r);
nvgLineTo(vg, cx - r, cy);
nvgLineTo(vg, cx + r, cy + r);

nvgFill(vg);
nvgStrokeWidth(vg, 0.75);
nvgStroke(vg);
}
}

void onChange(const ChangeEvent &e) override
{
bdw->dirty = true;
bdwLight->dirty = true;
Widget::onChange(e);
}

float phalo{0.f};
void step() override
{
const float halo = rack::settings::haloBrightness;
if (phalo != halo)
{
phalo = halo;
bdw->dirty = true;
bdwLight->dirty = true;
}
Switch::step();
}
void onStyleChanged() override {}
};

QuadADWidget::QuadADWidget(sst::surgext_rack::quadad::ui::QuadADWidget::M *module)
{
setModule(module);
Expand Down Expand Up @@ -105,8 +248,8 @@ QuadADWidget::QuadADWidget(sst::surgext_rack::quadad::ui::QuadADWidget::M *modul
}

auto cp = rack::mm2px(rack::Vec(col + layout::LayoutConstants::columnWidth_MM * 0.5, row1));
auto trigLight = rack::createParamCentered<widgets::ActivateKnobSwitch>(
cp, module, M::LINK_TRIGGER_0 + i);
auto trigLight =
rack::createParamCentered<ThreeStateTriggerSwitch>(cp, module, M::LINK_TRIGGER_0 + i);
addChild(trigLight);

auto lcdw = rack::app::RACK_GRID_WIDTH * 12 - widgets::LCDBackground::posx * 2;
Expand Down
125 changes: 95 additions & 30 deletions src/QuadAD.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ struct QuadAD : modules::XTModule
{
processors[i][p] = std::make_unique<dsp::envelopes::ADAREnvelope>(storage.get());
accumulatedOutputs[i][p] = 0.f;
gated[i][p] = false;
eocFromAway[i][p] = 0;
}
pushEOCOnto[i] = -1;
isTriggerLinked[i] = false;
isEnvLinked[i] = false;
adPoly[i] = 1;
Expand All @@ -151,10 +154,10 @@ struct QuadAD : modules::XTModule
configSwitch(A_SHAPE_0 + i, 0, 2, 1, "Attack Curve", {"A <", "A -", "A >"});
configSwitch(D_SHAPE_0 + i, 0, 2, 1, "Decay Curve", {"D <", "D -", "D >"});
configSwitch(ADAR_0 + i, 0, 1, 0, "AD vs AR", {"AD Trig", "AR Gate"});
configSwitch(LINK_TRIGGER_0 + i, 0, 1, 0,
configSwitch(LINK_TRIGGER_0 + i, -1, 1, 0,
"Link " + std::to_string(i + 1) + " EOC to " +
std::to_string((i + 1) % n_ads + 1) + " Attack",
{"Off", "On"});
{"Cycle", "Off", "On"});
configSwitch(LINK_ENV_0 + i, 0, 1, 0,
"Sum " + std::to_string(i + 1) + " ENV to " +
std::to_string((i + 1) % n_ads + 1) + " Output",
Expand Down Expand Up @@ -222,12 +225,19 @@ struct QuadAD : modules::XTModule

std::atomic<bool> attackFromZero{false};
int processCount{BLOCK_SIZE};
rack::dsp::SchmittTrigger inputTriggers[n_ads][MAX_POLY];
rack::dsp::SchmittTrigger inputTriggers[n_ads][MAX_POLY], linkTriggers[n_ads][MAX_POLY];
bool gated[n_ads][MAX_POLY];
float accumulatedOutputs[n_ads][MAX_POLY];

bool isTriggerLinked[n_ads], isEnvLinked[n_ads];
bool isEnvLinked[n_ads];
int adPoly[n_ads];

bool isTriggerLinked[n_ads]; // this target is linked from away
int pushEOCOnto[n_ads]; // this target pushes to that source
bool anyLinked{false};
float eocFromAway[n_ads][MAX_POLY];
int lastTriggerLinkParaams[n_ads]{-1, -1, -1, -1};

void process(const typename rack::Module::ProcessArgs &args) override
{
if (processCount == BLOCK_SIZE)
Expand All @@ -236,36 +246,82 @@ struct QuadAD : modules::XTModule

for (int i = 0; i < n_ads; ++i)
{
int linkIdx = (i + n_ads - 1) & (n_ads - 1);
isTriggerLinked[i] = params[LINK_TRIGGER_0 + linkIdx].getValue() > 0.5;
isEnvLinked[i] = params[LINK_ENV_0 + linkIdx].getValue() > 0.5;
int envLinkIdx = (i + n_ads - 1) & (n_ads - 1);
isEnvLinked[i] = params[LINK_ENV_0 + envLinkIdx].getValue() > 0.5;
}

nChan = 1;
// Only do this if the triggers have changed
bool recalc{false};
for (int i = 0; i < n_ads; ++i)
{
if (isTriggerLinked[i])
auto tn = (int)std::round(params[LINK_TRIGGER_0 + i].getValue());
if (tn != lastTriggerLinkParaams[i])
recalc = true;
lastTriggerLinkParaams[i] = tn;
}
if (recalc)
{
// burn it down and start again
for (int i = 0; i < n_ads; ++i)
{
pushEOCOnto[i] = -1;
isTriggerLinked[i] = false;
anyLinked = false;
}
for (int i = 0; i < n_ads; ++i)
{
int chl = inputs[TRIGGER_0 + i].getChannels();
int j = (i + n_ads - 1) & (n_ads - 1);
while (j != i)
auto tn = (int)std::round(params[LINK_TRIGGER_0 + i].getValue());
anyLinked = anyLinked || (tn != 0);
if (tn == 1)
{
if (!isTriggerLinked[j])
pushEOCOnto[i] = (i + 1) & (n_ads - 1);
isTriggerLinked[pushEOCOnto[i]] = true;
}
if (tn == -1)
{
// Single cycle for now
int q = (i - 1 + n_ads) & (n_ads - 1);
int loopFrom{i};
while (q != i)
{
chl = std::max(chl, inputs[TRIGGER_0 + j].getChannels());
break;
auto qn = (int)std::round(params[LINK_TRIGGER_0 + q].getValue());
if (qn != 1)
break;
loopFrom = q;
q = (q - 1 + n_ads) & (n_ads - 1);
}
j = (j + n_ads - 1) & (n_ads - 1);
pushEOCOnto[i] = loopFrom;
isTriggerLinked[i] = true;
isTriggerLinked[loopFrom] = true;
}
adPoly[i] = std::max(1, chl);
}
else
}
// TRIGGER POLY TODO
nChan = 1;
for (int i = 0; i < n_ads; ++i)
{
adPoly[i] =
inputs[TRIGGER_0 + i].isConnected() ? inputs[TRIGGER_0 + i].getChannels() : 1;
nChan = std::max(nChan, adPoly[i]);
}
if (anyLinked)
{
for (int i = 0; i < n_ads; ++i)
{
adPoly[i] = nChan;
}
}

memset(eocFromAway, 0, n_ads * MAX_POLY * sizeof(float));
for (int i = 0; i < n_ads; ++i)
{
if (pushEOCOnto[i] >= 0)
{
adPoly[i] = inputs[TRIGGER_0 + i].isConnected()
? inputs[TRIGGER_0 + i].getChannels()
: 1;
for (int c = 0; c < MAX_POLY; ++c)
{
eocFromAway[pushEOCOnto[i]][c] += processors[i][c]->eoc_output;
}
}
nChan = std::max(nChan, adPoly[i]);
}

modAssist.setupMatrix(this);
Expand All @@ -275,11 +331,11 @@ struct QuadAD : modules::XTModule
int tnc = 1;
for (int i = 0; i < n_ads; ++i)
{
// who is my trigger neighbor?
int linkIdx = (i + n_ads - 1) & (n_ads - 1);
float linkMul = isTriggerLinked[i] * 10.f;
// who is my env neighbor?
int envLinkIdx = (i + n_ads - 1) & (n_ads - 1);

if (inputs[TRIGGER_0 + i].isConnected() || isTriggerLinked[i] || isEnvLinked[i])
if (inputs[TRIGGER_0 + i].isConnected() || isTriggerLinked[i] || isEnvLinked[i] ||
pushEOCOnto[i] >= 0)
{
int ch = adPoly[i];
outputs[OUTPUT_0 + i].setChannels(ch);
Expand All @@ -288,21 +344,30 @@ struct QuadAD : modules::XTModule
for (int c = 0; c < ch; ++c)
{
auto iv = inputs[TRIGGER_0 + i].getVoltage(c);
auto lv = processors[linkIdx][c]->eoc_output * linkMul;
auto lv = (isTriggerLinked[i] && (eocFromAway[i][c] > 0)) ? 10.f : 0.f;

if (inputTriggers[i][c].process(iv + lv))
auto gl = std::clamp(iv + lv, 0.f, 10.f);

if (inputTriggers[i][c].process(iv) || linkTriggers[i][c].process(lv))
{
gated[i][c] = true;
processors[i][c]->attackFrom(attackFromZero ? 0 : processors[i][c]->output,
as, params[MODE_0 + i].getValue() < 0.5,
params[ADAR_0 + i].getValue() > 0.5);
}

if (gated[i][c] && gl < 1.f) // that's the default trigger threshold
{
gated[i][c] = false;
}

processors[i][c]->process(modAssist.values[ATTACK_0 + i][c],
modAssist.values[DECAY_0 + i][c], as, ds,
(lv + iv) * 0.1);
gated[i][c]);

auto ov = processors[i][c]->output * 10;
if (isEnvLinked[i])
ov += accumulatedOutputs[linkIdx][c];
ov += accumulatedOutputs[envLinkIdx][c];

outputs[OUTPUT_0 + i].setVoltage(ov, c);
accumulatedOutputs[i][c] = ov;
Expand Down
3 changes: 3 additions & 0 deletions src/QuadLFO.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
*
* Module
* - Clock and tempoSync cases
* - Animation under modulation of animation doesn't seem to happen
* - Square and S&H get sample accurate transitions in block rather than interps
* - Mod Labels and Quantity Recalc on names and so on (basically 100% lintbuddy)
* - Remove alpha label
*/

#ifndef SURGE_XT_RACK_QUADADHPP
Expand Down

0 comments on commit 27b6ac5

Please sign in to comment.