Skip to content

Commit adeb07e

Browse files
committed
Move level gain post dry/wet mixer; double click now select either gain
or level as CV destination on legio
1 parent 111bc47 commit adeb07e

7 files changed

Lines changed: 273 additions & 104 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The original VST includes:
1818

1919
PiRAT inherits the circuit emulation from Rodent and adds its own flavor to it:
2020

21-
- stereo signal path;
21+
- stereo signal path (can act like a simple stereo VCA when bypass is enabled);
2222
- dry / wet mixer to cross-fade between clean / overdrive / distortion;
2323
- mixer to blend between silicon and diode clipping in hard clipping (turbo) mode;
2424
- optional noise gate (based on the wonderful [NoiseInvader](https://github.com/ValdemarOrn/NoiseInvaderVST) VST).

docs/pirat_legio.pdf

33 KB
Binary file not shown.

docs/res/pirat_legio.svg

Lines changed: 156 additions & 91 deletions
Loading

docs/static/pirat_legio.jpg

79.9 KB
Loading

src/commons/DoubleClicker.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
#ifndef PIRAT_DOUBLECLICKER_H
3+
#define PIRAT_DOUBLECLICKER_H
4+
5+
#include "Utils.h"
6+
7+
#ifdef __cplusplus
8+
9+
namespace pirat
10+
{
11+
12+
struct DoubleClicker {
13+
14+
DoubleClicker(uint32_t interval = 1000):
15+
first_(0), last_(0), state_(false), interval_(interval) {}
16+
17+
inline void Update(bool state, uint32_t now) {
18+
const bool prev = state_;
19+
// count a click on a rising edge
20+
if (state && !prev) {
21+
// first one is our reference for the interval
22+
if (first_ == 0) {
23+
first_ = now;
24+
} else {
25+
last_ = now;
26+
}
27+
}
28+
// timeout
29+
if (first_ && (now > first_ + interval_)) {
30+
reset();
31+
}
32+
state_ = state;
33+
}
34+
35+
inline bool DoubleClick() {
36+
const bool event = last_ != 0;
37+
// we have detected a double click, reset our state
38+
if (event) {
39+
reset();
40+
}
41+
return event;
42+
}
43+
44+
inline void reset() {
45+
first_ = 0;
46+
last_ = 0;
47+
}
48+
49+
private:
50+
uint32_t first_;
51+
uint32_t last_;
52+
53+
bool state_;
54+
55+
const uint32_t interval_;
56+
};
57+
58+
}
59+
60+
#endif
61+
#endif

src/legio/legio.cpp

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Legio implementation of PiRAT distortion, featuring:
33
*
44
* - stereo signal path;
5-
* - knobs with dedicated CV controls;
5+
* - knobs with dedicated CV controls (either over gain or level);
66
* - hard clip, ruetz and tight mods;
77
* - noise gate (with a bypass and adjustable threshold / release).
88
*
@@ -12,6 +12,7 @@
1212

1313
#include "PiRATDist.h"
1414
#include "NoiseGate.h"
15+
#include "DoubleClicker.h"
1516
#include "GrabValue.h"
1617
#include "Slew.h"
1718

@@ -30,24 +31,34 @@ using namespace pirat;
3031
#define PITCH_CV_0V 0.28571428571428575f
3132

3233

34+
enum PitchCVDest {
35+
GAIN = 0,
36+
LEVEL = 1
37+
};
38+
39+
3340
struct Settings
3441
{
3542
Settings():
3643
pr_gain(0.50f),
3744
pr_level(0.75f),
3845
ng_threshold(0.4f), // -45db
39-
ng_release(0.5f) {} // 100ms
46+
ng_release(0.5f), // 100ms
47+
pitchcvdest(PitchCVDest::GAIN) {}
4048

4149
float pr_gain;
4250
float pr_level;
4351
float ng_threshold;
4452
float ng_release;
4553

54+
enum PitchCVDest pitchcvdest;
55+
4656
bool operator==(const Settings &rhs) {
4757
return Utils::NearlyEqual(pr_gain, rhs.pr_gain)
4858
&& Utils::NearlyEqual(pr_level, rhs.pr_level)
4959
&& Utils::NearlyEqual(ng_threshold, rhs.ng_threshold)
50-
&& Utils::NearlyEqual(ng_release, rhs.ng_release);
60+
&& Utils::NearlyEqual(ng_release, rhs.ng_release)
61+
&& (pitchcvdest == rhs.pitchcvdest);
5162
}
5263
bool operator!=(const Settings &rhs) { return !operator==(rhs); }
5364
};
@@ -71,6 +82,10 @@ NoiseGate ng;
7182
Slew sm_gain;
7283
Slew sm_level;
7384

85+
// pitch CV destination selector
86+
enum PitchCVDest pitchcvdest = PitchCVDest::GAIN;
87+
uint32_t last_dck = 0;
88+
7489
// UX values
7590
float envVal = 0.f;
7691
float satVal = 0.f;
@@ -98,6 +113,8 @@ void AudioCallback(AudioHandle::InputBuffer in,
98113

99114
static uint32_t last_incr = 0;
100115

116+
static DoubleClicker dck;
117+
101118
static GrabValue<float> cv_filter = 0.f;
102119
static GrabValue<float> cv_mix = 0.f;
103120

@@ -112,8 +129,17 @@ void AudioCallback(AudioHandle::InputBuffer in,
112129
return;
113130
}
114131

132+
const uint32_t now = System::GetNow();
133+
115134
const bool shift = hw.encoder.Pressed() && !first;
116135

136+
dck.Update(shift, now);
137+
// double clicking shift switch between Gain and Level CV destinations
138+
if (dck.DoubleClick()) {
139+
pitchcvdest = pitchcvdest == PitchCVDest::GAIN ? PitchCVDest::LEVEL : PitchCVDest::GAIN;
140+
last_dck = now;
141+
}
142+
117143
const float cv0 = hw.GetKnobValue(DaisyLegio::CONTROL_KNOB_TOP);
118144
const float cv1 = hw.GetKnobValue(DaisyLegio::CONTROL_KNOB_BOTTOM);
119145

@@ -124,6 +150,7 @@ void AudioCallback(AudioHandle::InputBuffer in,
124150
pr_level = settings.pr_level;
125151
ng_threshold.Update(settings.ng_threshold);
126152
ng_release.Update(settings.ng_release);
153+
pitchcvdest = settings.pitchcvdest;
127154

128155
} else if (!shift && prevshift) { // save settings when exiting shift mode
129156
Settings &settings = storage.GetSettings();
@@ -132,6 +159,7 @@ void AudioCallback(AudioHandle::InputBuffer in,
132159
settings.pr_level = pr_level;
133160
settings.ng_threshold = ng_threshold.Get();
134161
settings.ng_release = ng_release.Get();
162+
settings.pitchcvdest = pitchcvdest;
135163

136164
// force editing mode to timeout
137165
last_incr = 0;
@@ -144,7 +172,7 @@ void AudioCallback(AudioHandle::InputBuffer in,
144172

145173
// encoder is in editing mode
146174
if (inc != 0) {
147-
last_incr = System::GetNow();
175+
last_incr = now;
148176
}
149177

150178
if (!shift) {
@@ -171,11 +199,20 @@ void AudioCallback(AudioHandle::InputBuffer in,
171199

172200
const float pitch_cv = hw.controls[DaisyLegio::CONTROL_PITCH].Value();
173201
// pitch CV range is -2V -> 5V, we want to remap it in range 0-5V
174-
const float gain_cv = fclamp((pitch_cv - PITCH_CV_0V) / (1.0f - PITCH_CV_0V), 0.f, 1.f);
202+
const float pitch_cv_0v = fclamp((pitch_cv - PITCH_CV_0V) / (1.0f - PITCH_CV_0V), 0.f, 1.f);
203+
204+
float gain_cv = 0.f;
205+
float level_cv = 0.f;
206+
207+
if (pitchcvdest == PitchCVDest::GAIN) {
208+
gain_cv = pitch_cv_0v;
209+
} else {
210+
level_cv = pitch_cv_0v;
211+
}
175212

176213
const float gain = fclamp(pr_gain_sm + gain_cv, 0.f, 1.f);
177214
const float filter = fclamp(cv_filter.Get(), 0.f, 1.f);
178-
const float level = fclamp(pr_level_sm, 0.f, 1.f);
215+
const float level = fclamp(pr_level_sm + level_cv, 0.f, 1.f);
179216
const float mix = fclamp(cv_mix.Get(), 0.f, 1.f);
180217

181218
const int sw_clip = hw.sw[DaisyLegio::SW_LEFT].Read();
@@ -218,7 +255,7 @@ void AudioCallback(AudioHandle::InputBuffer in,
218255
ng.Process(OUT_L, OUT_R, IN_L, OUT_L, OUT_R, size);
219256

220257
// encoder is in editing mode
221-
if (last_incr != 0 && (System::GetNow() - last_incr) < 1000) {
258+
if (last_incr != 0 && (now - last_incr) < 1000) {
222259
if (shift) {
223260
satVal = 0.f;
224261
envVal = pr_level_sm;
@@ -294,8 +331,14 @@ int main(void)
294331
initialized = true;
295332
}
296333
} else {
297-
hw.SetLed(DaisyLegio::LED_LEFT, 0.f, envVal, 0.f);
298-
hw.SetLed(DaisyLegio::LED_RIGHT, satVal, 0.f, 0.f);
334+
// indicate current pitch CV destination
335+
if (System::GetNow() - last_dck < 1000) {
336+
hw.SetLed(DaisyLegio::LED_LEFT, 0.f, 0.f, pitchcvdest == PitchCVDest::LEVEL ? 1.f : 0.f);
337+
hw.SetLed(DaisyLegio::LED_RIGHT, 0.f, 0.f, pitchcvdest == PitchCVDest::GAIN ? 1.f : 0.f);
338+
} else {
339+
hw.SetLed(DaisyLegio::LED_LEFT, 0.f, envVal, 0.f);
340+
hw.SetLed(DaisyLegio::LED_RIGHT, satVal, 0.f, 0.f);
341+
}
299342
hw.UpdateLeds();
300343
// save settings
301344
if (dosave) {

src/pirat/PiRATDist.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ void PiRATDist::Process(
120120
Filter.Process(outputL, outputR, outputL, outputR, len);
121121
Hipass3.Process(outputL, outputR, outputL, outputR, len);
122122

123-
const float gainOut = Utils::ExpResponse(p_level_);
124-
Utils::Gain(outputL, outputR, gainOut, outputL, outputR, len);
125-
126123
#if HAS_FADER_SUPPORT
127124
MixerDryWet.Process(inputL, inputR, outputL, outputR, outputL, outputR, len);
128125
#endif
129126

130127
if (p_bypass_ > 0.5f) {
131128
Utils::Copy(inputL, inputR, outputL, outputR, len);
132129
}
130+
131+
const float gainOut = Utils::ExpResponse(p_level_);
132+
Utils::Gain(outputL, outputR, gainOut, outputL, outputR, len);
133133
}
134134

0 commit comments

Comments
 (0)