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 *
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+
3340struct 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;
7182Slew sm_gain;
7283Slew sm_level;
7384
85+ // pitch CV destination selector
86+ enum PitchCVDest pitchcvdest = PitchCVDest::GAIN;
87+ uint32_t last_dck = 0 ;
88+
7489// UX values
7590float envVal = 0 .f;
7691float 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) {
0 commit comments