Skip to content

Commit 27b6ac5

Browse files
authored
More Work on Quads (#726)
- Some initialization in QuadLFO - QuadAD looping works bidirectionally - QuadAD Polyphony correct - QuadAD Proper drawing items for forward/backwards triggers
1 parent 79565e2 commit 27b6ac5

File tree

5 files changed

+254
-43
lines changed

5 files changed

+254
-43
lines changed

src/QuadAD.cpp

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,149 @@ struct QuadADWidget : public widgets::XTModuleWidget
5252
}
5353
};
5454

55+
struct ThreeStateTriggerSwitch : rack::app::Switch, style::StyleParticipant
56+
{
57+
widgets::BufferedDrawFunctionWidget *bdw{nullptr}, *bdwLight{nullptr};
58+
float radius;
59+
60+
ThreeStateTriggerSwitch()
61+
{
62+
box.size = rack::mm2px(rack::Vec(3.5, 3.5));
63+
radius = rack::mm2px(1.1);
64+
bdw = new widgets::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size,
65+
[this](auto v) { this->drawBackground(v); });
66+
bdwLight = new widgets::BufferedDrawFunctionWidgetOnLayer(
67+
rack::Vec(0, 0), box.size, [this](auto v) { this->drawLight(v); });
68+
addChild(bdw);
69+
addChild(bdwLight);
70+
}
71+
72+
bool hovered{false};
73+
void onHover(const HoverEvent &e) override
74+
{
75+
e.consume(this);
76+
rack::app::Switch::onHover(e);
77+
}
78+
void onEnter(const EnterEvent &e) override
79+
{
80+
hovered = true;
81+
bdw->dirty = true;
82+
e.consume(this);
83+
84+
rack::app::Switch::onEnter(e);
85+
}
86+
void onLeave(const LeaveEvent &e) override
87+
{
88+
hovered = false;
89+
bdw->dirty = true;
90+
e.consume(this);
91+
92+
rack::app::Switch::onLeave(e);
93+
}
94+
95+
int val()
96+
{
97+
if (!getParamQuantity())
98+
return 0;
99+
return std::round(getParamQuantity()->getValue());
100+
}
101+
102+
void drawBackground(NVGcontext *vg)
103+
{
104+
auto state = val();
105+
if (state == 0)
106+
{
107+
auto col = style()->getColor(style::XTStyle::POWER_BUTTON_LIGHT_OFF);
108+
if (hovered)
109+
{
110+
col.r *= 1.2;
111+
col.g *= 1.2;
112+
col.b *= 1.2;
113+
}
114+
115+
nvgBeginPath(vg);
116+
nvgStrokeColor(vg, style()->getColor(style::XTStyle::PANEL_RULER));
117+
nvgFillColor(vg, col);
118+
nvgEllipse(vg, box.size.x * 0.5, box.size.y * 0.5, radius, radius);
119+
nvgFill(vg);
120+
nvgStrokeWidth(vg, 0.75);
121+
nvgStroke(vg);
122+
}
123+
else
124+
{
125+
}
126+
}
127+
128+
void drawLight(NVGcontext *vg)
129+
{
130+
131+
const float halo = rack::settings::haloBrightness;
132+
auto state = val();
133+
134+
if (state == 1)
135+
{
136+
auto col = style()->getColor(style::XTStyle::POWER_BUTTON_LIGHT_ON);
137+
138+
nvgBeginPath(vg);
139+
nvgStrokeColor(vg, style()->getColor(style::XTStyle::PANEL_RULER));
140+
nvgFillColor(vg, col);
141+
142+
auto cx = box.size.x * 0.5;
143+
auto cy = box.size.y * 0.5;
144+
145+
auto r = radius * 1.25;
146+
nvgMoveTo(vg, cx - r, cy - r);
147+
nvgLineTo(vg, cx + r, cy);
148+
nvgLineTo(vg, cx - r, cy + r);
149+
150+
nvgFill(vg);
151+
nvgStrokeWidth(vg, 0.75);
152+
nvgStroke(vg);
153+
}
154+
else if (state == -1)
155+
{
156+
auto col = style()->getColor(style::XTStyle::POWER_BUTTON_LIGHT_ON);
157+
158+
nvgBeginPath(vg);
159+
nvgStrokeColor(vg, style()->getColor(style::XTStyle::PANEL_RULER));
160+
nvgFillColor(vg, col);
161+
162+
auto cx = box.size.x * 0.5;
163+
auto cy = box.size.y * 0.5;
164+
165+
auto r = radius * 1.25;
166+
nvgMoveTo(vg, cx + r, cy - r);
167+
nvgLineTo(vg, cx - r, cy);
168+
nvgLineTo(vg, cx + r, cy + r);
169+
170+
nvgFill(vg);
171+
nvgStrokeWidth(vg, 0.75);
172+
nvgStroke(vg);
173+
}
174+
}
175+
176+
void onChange(const ChangeEvent &e) override
177+
{
178+
bdw->dirty = true;
179+
bdwLight->dirty = true;
180+
Widget::onChange(e);
181+
}
182+
183+
float phalo{0.f};
184+
void step() override
185+
{
186+
const float halo = rack::settings::haloBrightness;
187+
if (phalo != halo)
188+
{
189+
phalo = halo;
190+
bdw->dirty = true;
191+
bdwLight->dirty = true;
192+
}
193+
Switch::step();
194+
}
195+
void onStyleChanged() override {}
196+
};
197+
55198
QuadADWidget::QuadADWidget(sst::surgext_rack::quadad::ui::QuadADWidget::M *module)
56199
{
57200
setModule(module);
@@ -105,8 +248,8 @@ QuadADWidget::QuadADWidget(sst::surgext_rack::quadad::ui::QuadADWidget::M *modul
105248
}
106249

107250
auto cp = rack::mm2px(rack::Vec(col + layout::LayoutConstants::columnWidth_MM * 0.5, row1));
108-
auto trigLight = rack::createParamCentered<widgets::ActivateKnobSwitch>(
109-
cp, module, M::LINK_TRIGGER_0 + i);
251+
auto trigLight =
252+
rack::createParamCentered<ThreeStateTriggerSwitch>(cp, module, M::LINK_TRIGGER_0 + i);
110253
addChild(trigLight);
111254

112255
auto lcdw = rack::app::RACK_GRID_WIDTH * 12 - widgets::LCDBackground::posx * 2;

src/QuadAD.h

Lines changed: 95 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ struct QuadAD : modules::XTModule
137137
{
138138
processors[i][p] = std::make_unique<dsp::envelopes::ADAREnvelope>(storage.get());
139139
accumulatedOutputs[i][p] = 0.f;
140+
gated[i][p] = false;
141+
eocFromAway[i][p] = 0;
140142
}
143+
pushEOCOnto[i] = -1;
141144
isTriggerLinked[i] = false;
142145
isEnvLinked[i] = false;
143146
adPoly[i] = 1;
@@ -151,10 +154,10 @@ struct QuadAD : modules::XTModule
151154
configSwitch(A_SHAPE_0 + i, 0, 2, 1, "Attack Curve", {"A <", "A -", "A >"});
152155
configSwitch(D_SHAPE_0 + i, 0, 2, 1, "Decay Curve", {"D <", "D -", "D >"});
153156
configSwitch(ADAR_0 + i, 0, 1, 0, "AD vs AR", {"AD Trig", "AR Gate"});
154-
configSwitch(LINK_TRIGGER_0 + i, 0, 1, 0,
157+
configSwitch(LINK_TRIGGER_0 + i, -1, 1, 0,
155158
"Link " + std::to_string(i + 1) + " EOC to " +
156159
std::to_string((i + 1) % n_ads + 1) + " Attack",
157-
{"Off", "On"});
160+
{"Cycle", "Off", "On"});
158161
configSwitch(LINK_ENV_0 + i, 0, 1, 0,
159162
"Sum " + std::to_string(i + 1) + " ENV to " +
160163
std::to_string((i + 1) % n_ads + 1) + " Output",
@@ -222,12 +225,19 @@ struct QuadAD : modules::XTModule
222225

223226
std::atomic<bool> attackFromZero{false};
224227
int processCount{BLOCK_SIZE};
225-
rack::dsp::SchmittTrigger inputTriggers[n_ads][MAX_POLY];
228+
rack::dsp::SchmittTrigger inputTriggers[n_ads][MAX_POLY], linkTriggers[n_ads][MAX_POLY];
229+
bool gated[n_ads][MAX_POLY];
226230
float accumulatedOutputs[n_ads][MAX_POLY];
227231

228-
bool isTriggerLinked[n_ads], isEnvLinked[n_ads];
232+
bool isEnvLinked[n_ads];
229233
int adPoly[n_ads];
230234

235+
bool isTriggerLinked[n_ads]; // this target is linked from away
236+
int pushEOCOnto[n_ads]; // this target pushes to that source
237+
bool anyLinked{false};
238+
float eocFromAway[n_ads][MAX_POLY];
239+
int lastTriggerLinkParaams[n_ads]{-1, -1, -1, -1};
240+
231241
void process(const typename rack::Module::ProcessArgs &args) override
232242
{
233243
if (processCount == BLOCK_SIZE)
@@ -236,36 +246,82 @@ struct QuadAD : modules::XTModule
236246

237247
for (int i = 0; i < n_ads; ++i)
238248
{
239-
int linkIdx = (i + n_ads - 1) & (n_ads - 1);
240-
isTriggerLinked[i] = params[LINK_TRIGGER_0 + linkIdx].getValue() > 0.5;
241-
isEnvLinked[i] = params[LINK_ENV_0 + linkIdx].getValue() > 0.5;
249+
int envLinkIdx = (i + n_ads - 1) & (n_ads - 1);
250+
isEnvLinked[i] = params[LINK_ENV_0 + envLinkIdx].getValue() > 0.5;
242251
}
243252

244-
nChan = 1;
253+
// Only do this if the triggers have changed
254+
bool recalc{false};
245255
for (int i = 0; i < n_ads; ++i)
246256
{
247-
if (isTriggerLinked[i])
257+
auto tn = (int)std::round(params[LINK_TRIGGER_0 + i].getValue());
258+
if (tn != lastTriggerLinkParaams[i])
259+
recalc = true;
260+
lastTriggerLinkParaams[i] = tn;
261+
}
262+
if (recalc)
263+
{
264+
// burn it down and start again
265+
for (int i = 0; i < n_ads; ++i)
266+
{
267+
pushEOCOnto[i] = -1;
268+
isTriggerLinked[i] = false;
269+
anyLinked = false;
270+
}
271+
for (int i = 0; i < n_ads; ++i)
248272
{
249-
int chl = inputs[TRIGGER_0 + i].getChannels();
250-
int j = (i + n_ads - 1) & (n_ads - 1);
251-
while (j != i)
273+
auto tn = (int)std::round(params[LINK_TRIGGER_0 + i].getValue());
274+
anyLinked = anyLinked || (tn != 0);
275+
if (tn == 1)
252276
{
253-
if (!isTriggerLinked[j])
277+
pushEOCOnto[i] = (i + 1) & (n_ads - 1);
278+
isTriggerLinked[pushEOCOnto[i]] = true;
279+
}
280+
if (tn == -1)
281+
{
282+
// Single cycle for now
283+
int q = (i - 1 + n_ads) & (n_ads - 1);
284+
int loopFrom{i};
285+
while (q != i)
254286
{
255-
chl = std::max(chl, inputs[TRIGGER_0 + j].getChannels());
256-
break;
287+
auto qn = (int)std::round(params[LINK_TRIGGER_0 + q].getValue());
288+
if (qn != 1)
289+
break;
290+
loopFrom = q;
291+
q = (q - 1 + n_ads) & (n_ads - 1);
257292
}
258-
j = (j + n_ads - 1) & (n_ads - 1);
293+
pushEOCOnto[i] = loopFrom;
294+
isTriggerLinked[i] = true;
295+
isTriggerLinked[loopFrom] = true;
259296
}
260-
adPoly[i] = std::max(1, chl);
261297
}
262-
else
298+
}
299+
// TRIGGER POLY TODO
300+
nChan = 1;
301+
for (int i = 0; i < n_ads; ++i)
302+
{
303+
adPoly[i] =
304+
inputs[TRIGGER_0 + i].isConnected() ? inputs[TRIGGER_0 + i].getChannels() : 1;
305+
nChan = std::max(nChan, adPoly[i]);
306+
}
307+
if (anyLinked)
308+
{
309+
for (int i = 0; i < n_ads; ++i)
310+
{
311+
adPoly[i] = nChan;
312+
}
313+
}
314+
315+
memset(eocFromAway, 0, n_ads * MAX_POLY * sizeof(float));
316+
for (int i = 0; i < n_ads; ++i)
317+
{
318+
if (pushEOCOnto[i] >= 0)
263319
{
264-
adPoly[i] = inputs[TRIGGER_0 + i].isConnected()
265-
? inputs[TRIGGER_0 + i].getChannels()
266-
: 1;
320+
for (int c = 0; c < MAX_POLY; ++c)
321+
{
322+
eocFromAway[pushEOCOnto[i]][c] += processors[i][c]->eoc_output;
323+
}
267324
}
268-
nChan = std::max(nChan, adPoly[i]);
269325
}
270326

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

282-
if (inputs[TRIGGER_0 + i].isConnected() || isTriggerLinked[i] || isEnvLinked[i])
337+
if (inputs[TRIGGER_0 + i].isConnected() || isTriggerLinked[i] || isEnvLinked[i] ||
338+
pushEOCOnto[i] >= 0)
283339
{
284340
int ch = adPoly[i];
285341
outputs[OUTPUT_0 + i].setChannels(ch);
@@ -288,21 +344,30 @@ struct QuadAD : modules::XTModule
288344
for (int c = 0; c < ch; ++c)
289345
{
290346
auto iv = inputs[TRIGGER_0 + i].getVoltage(c);
291-
auto lv = processors[linkIdx][c]->eoc_output * linkMul;
347+
auto lv = (isTriggerLinked[i] && (eocFromAway[i][c] > 0)) ? 10.f : 0.f;
292348

293-
if (inputTriggers[i][c].process(iv + lv))
349+
auto gl = std::clamp(iv + lv, 0.f, 10.f);
350+
351+
if (inputTriggers[i][c].process(iv) || linkTriggers[i][c].process(lv))
294352
{
353+
gated[i][c] = true;
295354
processors[i][c]->attackFrom(attackFromZero ? 0 : processors[i][c]->output,
296355
as, params[MODE_0 + i].getValue() < 0.5,
297356
params[ADAR_0 + i].getValue() > 0.5);
298357
}
358+
359+
if (gated[i][c] && gl < 1.f) // that's the default trigger threshold
360+
{
361+
gated[i][c] = false;
362+
}
363+
299364
processors[i][c]->process(modAssist.values[ATTACK_0 + i][c],
300365
modAssist.values[DECAY_0 + i][c], as, ds,
301-
(lv + iv) * 0.1);
366+
gated[i][c]);
302367

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

307372
outputs[OUTPUT_0 + i].setVoltage(ov, c);
308373
accumulatedOutputs[i][c] = ov;

src/QuadLFO.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
*
2121
* Module
2222
* - Clock and tempoSync cases
23+
* - Animation under modulation of animation doesn't seem to happen
2324
* - Square and S&H get sample accurate transitions in block rather than interps
25+
* - Mod Labels and Quantity Recalc on names and so on (basically 100% lintbuddy)
26+
* - Remove alpha label
2427
*/
2528

2629
#ifndef SURGE_XT_RACK_QUADADHPP

0 commit comments

Comments
 (0)