@@ -1,5 +1,15 @@ | |||
# Change Log | |||
## v2.4.0 | |||
* MotionMTR | |||
* Initial release | |||
* Muxlicer | |||
* Fix gate labels | |||
* Improve docs | |||
* PonyVCO | |||
* Fix NaNs when TZFM input is used at very high sample rates | |||
* Fix pitch wobble when disconnecting TZFM voltage source | |||
## v2.3.0 | |||
* PonyVCO | |||
* Initial release | |||
@@ -17,13 +17,15 @@ We have tried to make the VCV implementations as authentic as possible, however | |||
* Chopping Kinky hardward is DC coupled, but we add the option (default disabled) to remove this offset. | |||
* The hardware Muxlicer assigns multiple functions to the "Speed Div/Mult" dial, that cannot be reproduced with a single mouse click. Some of these have been moved to the context menu, specifically: quadratic gates, the "All In" normalled voltage, and the input/output clock division/mult. The "Speed Div/Mult" dial remains only for main clock div/mult. | |||
* See [docs/Muxlicer.md](docs/Muxlicer.md) | |||
* The Noise Plethora filters self-oscillate on the hardware version but not the software version. | |||
* EvenVCO has the option (default true) to remove DC from the pulse waveform output (hardware contains DC for non-50% duty cycles) | |||
* EvenVCO has the option (default true) to remove DC from the pulse waveform output (hardware contains DC for non-50% duty cycles). | |||
* PonyVCO optionally allows the user: | |||
* to filter DC from the TZFM input signal (hardware filters below 15mHz) | |||
* to limit the pulsewidth from 5% to 95% (hardware is full range) | |||
* to remove DC from the pulse waveform output (hardware contains DC for non-50% duty cycles) | |||
* to remove DC from the pulse waveform output (hardware contains DC for non-50% duty cycles) | |||
* MotionMTR optionally doesn't use the 10V normalling on inputs if in audio mode to avoid acidentally adding unwanted DC to audio signals, see context menu. E.g. if you temporarily unpatch an audio source whilst using it it mixer mode, you get 10V DC suddenly and a nasty pop. |
@@ -0,0 +1,23 @@ | |||
# Muxlicer | |||
The VCV port is designed to be as close as possible to the hardware unit, for which the manual can be found here: https://www.befaco.org/muxlicer-2/. This includes patch ideas and detailed notes. | |||
> Muxlicer is a sequential signal processor designed for add a huge range of special functions to your modular setup in the minimum space. It is divided in three main blocks: a Digital Step Controller, a Gate Generator and an Analog Switch (a.k.a. Mux/DeMux). The module is designed with high “function to HP ratio” philosophy , to have maximum flexibility in minimum space. | |||
## Differences from hardware version | |||
The hardware Muxlicer assigns multiple functions to the "Speed Div/Mult" dial, that cannot be reproduced with a single mouse click. Some of these have been moved to the context menu, specifically: quadratic gates, the "All In" normalled voltage, and the input/output clock division/mult. The "Speed Div/Mult" dial remains only for main clock div/mult. | |||
Tap tempo, which would be obtained by clicking the Speed Div/Mult dial in hardware, is now in the context menu. | |||
The hardware uses a bidirectional sequential switch, meaning the IO jacks can be inputs or outputs. This is not possible with VCV, so a menu item has been added to allowing changing between "8 in - 1 out" and "1 in - 8 out" modes, see below. The jacks will change colour to indicate the change of mode. | |||
 | |||
## Patch Ideas | |||
You can chain multiple Muxlicers by linking the End of Cycle (EOC) outputs to the One-Shot/Reset outputs (see below). This works best when both units are externally clocked by the same clock source. | |||
 | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"slug": "Befaco", | |||
"version": "2.3.0", | |||
"version": "2.4.0", | |||
"license": "GPL-3.0-or-later", | |||
"name": "Befaco", | |||
"brand": "Befaco", | |||
@@ -208,7 +208,7 @@ | |||
"slug": "Muxlicer", | |||
"name": "Muxlicer", | |||
"description": "VC adressable sequential switch and sequencer", | |||
"manualUrl": "https://www.befaco.org/muxlicer-2/", | |||
"manualUrl": "https://github.com/VCVRack/Befaco/blob/v2/docs/Muxlicer.md", | |||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-muxlicer", | |||
"tags": [ | |||
"Clock generator", | |||
@@ -272,7 +272,7 @@ | |||
}, | |||
{ | |||
"slug": "MotionMTR", | |||
"name": "MotionMTR", | |||
"name": "Motion MTR", | |||
"description": "CV and audio utility / realtime visualizer", | |||
"manualUrl": "https://www.befaco.org/motion_mtr/", | |||
"modularGridUrl": "https://www.modulargrid.net/e/befaco-motion-mtr", | |||
@@ -0,0 +1,88 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||
<svg | |||
version="1.0" | |||
id="svg258108" | |||
sodipodi:docname="CKSSThree_bg.svg" | |||
x="0px" | |||
y="0px" | |||
width="13.457px" | |||
height="28.34766px" | |||
viewBox="0 0 13.457 28.34766" | |||
enable-background="new 0 0 13.457 28.34766" | |||
xml:space="preserve" | |||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:svg="http://www.w3.org/2000/svg"><defs | |||
id="defs158" /> | |||
<sodipodi:namedview | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
fit-margin-bottom="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-top="0" | |||
id="base" | |||
inkscape:current-layer="svg258108" | |||
inkscape:cx="3.8392857" | |||
inkscape:cy="16.964286" | |||
inkscape:document-units="mm" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:window-height="882" | |||
inkscape:window-maximized="0" | |||
inkscape:window-width="1600" | |||
inkscape:window-x="0" | |||
inkscape:window-y="25" | |||
inkscape:zoom="11.2" | |||
pagecolor="#ffffff" | |||
showgrid="false" | |||
inkscape:showpageshadow="2" | |||
inkscape:pagecheckerboard="0" | |||
inkscape:deskcolor="#d1d1d1"> | |||
</sodipodi:namedview> | |||
<path | |||
id="path243332" | |||
inkscape:connector-curvature="0" | |||
d="M0,1.41795c0-0.78124,0.6406-1.41797,1.41793-1.41797h10.62499 c0.77739,0,1.4141,0.63671,1.4141,1.41797v25.51173c0,0.77735-0.63669,1.41797-1.4141,1.41797H1.41793 C0.6406,28.34765,0,27.70702,0,26.92968V1.41795z" /> | |||
<linearGradient | |||
id="path243334_1_" | |||
gradientUnits="userSpaceOnUse" | |||
x1="6.73046" | |||
y1="1.26953" | |||
x2="6.73046" | |||
y2="27.07812"> | |||
<stop | |||
offset="0" | |||
style="stop-color:#3B3B3B" | |||
id="stop134" /> | |||
<stop | |||
offset="1" | |||
style="stop-color:#242424" | |||
id="stop136" /> | |||
</linearGradient> | |||
<path | |||
id="path243334" | |||
inkscape:connector-curvature="0" | |||
opacity="0.54" | |||
fill="url(#path243334_1_)" | |||
d="M11.3242,1.26953H2.13671 c-0.48326,0-0.87501,0.39176-0.87501,0.87501v24.05857c0,0.48326,0.39176,0.87501,0.87501,0.87501h9.18749 c0.48326,0,0.87501-0.39176,0.87501-0.87501V2.14454C12.19921,1.66128,11.80746,1.26953,11.3242,1.26953z" /> | |||
<rect | |||
x="-19.43386" | |||
y="19.77311" | |||
fill="none" | |||
width="0.6829" | |||
height="0.89523" | |||
id="rect140" /> | |||
<rect | |||
x="0.73033" | |||
y="0.80507" | |||
opacity="0.3" | |||
width="12.00026" | |||
height="1.71664" | |||
id="rect142" /> | |||
</svg> |
@@ -0,0 +1,148 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||
<svg | |||
version="1.0" | |||
id="svg258108" | |||
sodipodi:docname="CKSSThree_fg.svg" | |||
x="0px" | |||
y="0px" | |||
width="10.94143" | |||
height="11.941" | |||
viewBox="0 0 10.94143 11.941" | |||
enable-background="new 0 0 13.457 28.34766" | |||
xml:space="preserve" | |||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:xlink="http://www.w3.org/1999/xlink" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:svg="http://www.w3.org/2000/svg"><defs | |||
id="defs3211"><linearGradient | |||
inkscape:collect="always" | |||
xlink:href="#linearGradient1577" | |||
id="linearGradient1579" | |||
x1="6.4409394" | |||
y1="10.29908" | |||
x2="6.4493213" | |||
y2="13.185839" | |||
gradientUnits="userSpaceOnUse" | |||
gradientTransform="matrix(1.003741,0,0,1,-0.03019806,-1.4171131)" /><linearGradient | |||
inkscape:collect="always" | |||
id="linearGradient1577"><stop | |||
style="stop-color:#000000;stop-opacity:0.38225257;" | |||
offset="0" | |||
id="stop1573" /><stop | |||
style="stop-color:#000000;stop-opacity:0;" | |||
offset="1" | |||
id="stop1575" /></linearGradient></defs> | |||
<sodipodi:namedview | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
fit-margin-bottom="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-top="0" | |||
id="base" | |||
inkscape:current-layer="svg258108" | |||
inkscape:cx="1.0976869" | |||
inkscape:cy="8.2185788" | |||
inkscape:document-units="mm" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:window-height="882" | |||
inkscape:window-maximized="0" | |||
inkscape:window-width="1600" | |||
inkscape:window-x="0" | |||
inkscape:window-y="25" | |||
inkscape:zoom="53.293886" | |||
pagecolor="#ffffff" | |||
showgrid="false" | |||
inkscape:showpageshadow="2" | |||
inkscape:pagecheckerboard="0" | |||
inkscape:deskcolor="#d1d1d1"> | |||
</sodipodi:namedview> | |||
<linearGradient | |||
id="path243334_1_" | |||
gradientUnits="userSpaceOnUse" | |||
x1="6.7304602" | |||
y1="1.2695301" | |||
x2="6.7304602" | |||
y2="27.078119"> | |||
<stop | |||
offset="0" | |||
style="stop-color:#3B3B3B" | |||
id="stop3187" /> | |||
<stop | |||
offset="1" | |||
style="stop-color:#242424" | |||
id="stop3189" /> | |||
</linearGradient> | |||
<rect | |||
x="-19.433861" | |||
y="19.773109" | |||
fill="none" | |||
width="0.68290001" | |||
height="0.89523" | |||
id="rect3193" /> | |||
<g | |||
id="g3208" | |||
transform="translate(-1.25778,-18.23161)"> | |||
<linearGradient | |||
id="path5815_2_" | |||
gradientUnits="userSpaceOnUse" | |||
x1="6.7284999" | |||
y1="18.231609" | |||
x2="6.7284999" | |||
y2="27.078119"> | |||
<stop | |||
offset="0.00559" | |||
style="stop-color:#3D3B3B" | |||
id="stop3197" /> | |||
<stop | |||
offset="1" | |||
style="stop-color:#2D2C2C" | |||
id="stop3199" /> | |||
</linearGradient> | |||
<path | |||
id="path5815_1_" | |||
inkscape:connector-curvature="0" | |||
fill="url(#path5815_2_)" | |||
d="m 1.25778,18.23161 h 10.94143 v 8.84652 H 1.25778 Z" | |||
style="fill:url(#path5815_2_)" /> | |||
<path | |||
id="path5817_1_" | |||
inkscape:connector-curvature="0" | |||
fill="#5e5e5e" | |||
d="m 1.25778,21.77853 h 10.94143 v 0.87738 H 1.25778 Z" /> | |||
<path | |||
id="path5819_1_" | |||
inkscape:connector-curvature="0" | |||
fill="#5e5e5e" | |||
d="m 1.25778,25.32545 h 10.94143 v 0.87738 H 1.25778 Z" /> | |||
<path | |||
id="path5821_1_" | |||
inkscape:connector-curvature="0" | |||
fill="#5e5e5e" | |||
d="m 1.25778,23.55285 h 10.94143 v 0.87738 H 1.25778 Z" /> | |||
<path | |||
id="path5823_1_" | |||
inkscape:connector-curvature="0" | |||
fill="#5e5e5e" | |||
d="m 1.25778,20.00513 h 10.94143 v 0.87738 H 1.25778 Z" /> | |||
<path | |||
id="path5825_1_" | |||
inkscape:connector-curvature="0" | |||
fill="#797979" | |||
d="m 1.25778,18.23161 h 10.94143 v 0.87738 H 1.25778 Z" /> | |||
</g> | |||
<rect | |||
style="fill:url(#linearGradient1579);fill-opacity:1;stroke:none;stroke-width:1.33393;stroke-linejoin:round;stroke-miterlimit:1.5" | |||
id="rect1517" | |||
width="13.160316" | |||
height="2.9625583" | |||
x="0" | |||
y="8.8440666" /></svg> |
@@ -39,48 +39,85 @@ struct MotionMTR : Module { | |||
LIGHTS_LEN | |||
}; | |||
dsp::VuMeter vuBar[3]; | |||
struct Map { | |||
float dbValue; | |||
long ledNumber; | |||
}; | |||
const Map lut[20] = { | |||
{ -30.5, 1}, { -30, 2}, { -30, 3}, { -26, 4}, { -25, 5}, { -24, 6}, { -23, 7 }, { -22, 8 }, { -21, 9}, { -20, 10}, | |||
{ -19, 11}, { -18, 12}, { -16, 13}, { -14, 14}, { -12, 15}, { -10, 16}, { -8, 17}, { -6, 18}, { -4, 19}, { -2, 20} | |||
}; | |||
dsp::VuMeter2 vuBar[3]; | |||
const int updateLEDRate = 16; | |||
dsp::ClockDivider sliderUpdate; | |||
bool startingUp = true; | |||
dsp::Timer startupTimer; | |||
bool break10VNormalForAudioMode = true; | |||
MotionMTR() { | |||
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); | |||
configSwitch(MODE1_PARAM, 0.f, 2.f, 0.f, "Channel 1 mode", modeLabels); | |||
auto mode1 = configSwitch(MODE1_PARAM, 0.f, 2.f, 1.f, "Channel 1 mode", modeLabels); | |||
mode1->snapEnabled = true; | |||
configParam(CTRL_1_PARAM, 0.f, 1.f, 0.f, "Channel 1 gain"); | |||
configSwitch(MODE2_PARAM, 0.f, 2.f, 0.f, "Channel 2 mode", modeLabels); | |||
auto mode2 = configSwitch(MODE2_PARAM, 0.f, 2.f, 1.f, "Channel 2 mode", modeLabels); | |||
mode2->snapEnabled = true; | |||
configParam(CTRL_2_PARAM, 0.f, 1.f, 0.f, "Channel 2 gain"); | |||
configSwitch(MODE3_PARAM, 0.f, 2.f, 0.f, "Channel 3 mode", modeLabels); | |||
auto mode3 = configSwitch(MODE3_PARAM, 0.f, 2.f, 1.f, "Channel 3 mode", modeLabels); | |||
mode3->snapEnabled = true; | |||
configParam(CTRL_3_PARAM, 0.f, 1.f, 0.f, "Channel 3 gain"); | |||
auto in1 = configInput(IN1_INPUT, "Channel 1"); | |||
in1->description = "Normalled to 10V"; | |||
in1->description = "Normalled to 10V (except optionally in audio mode)"; | |||
auto in2 = configInput(IN2_INPUT, "Channel 2"); | |||
in2->description = "Normalled to 10V"; | |||
in2->description = "Normalled to 10V (except optionally in audio mode)"; | |||
auto in3 = configInput(IN3_INPUT, "Channel 3"); | |||
in3->description = "Normalled to 10V"; | |||
in3->description = "Normalled to 10V (except optionally in audio mode)"; | |||
configOutput(OUT1_OUTPUT, "Channel 1"); | |||
configOutput(OUT2_OUTPUT, "Channel 2"); | |||
configOutput(OUT3_OUTPUT, "Channel 3 (Mix)"); | |||
vuBar[0].dBInterval = 3; | |||
vuBar[1].dBInterval = 3; | |||
vuBar[2].dBInterval = 3; | |||
for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) { | |||
configLight(LIGHT_1 + i * 3, string::f("%g to %g dB", lut[i - 1].dbValue, lut[i].dbValue)); | |||
configLight(LIGHT_2 + i * 3, string::f("%g to %g dB", lut[i - 1].dbValue, lut[i].dbValue)); | |||
configLight(LIGHT_3 + i * 3, string::f("%g to %g dB", lut[i - 1].dbValue, lut[i].dbValue)); | |||
} | |||
for (int i = 0; i < 3; ++i) { | |||
vuBar[i].mode = dsp::VuMeter2::PEAK; | |||
vuBar[i].lambda = 10.f * updateLEDRate; | |||
} | |||
// only poll EQ sliders every 16 samples | |||
sliderUpdate.setDivision(updateLEDRate); | |||
// timer for startup animation | |||
startupTimer.reset(); | |||
} | |||
void process(const ProcessArgs& args) override { | |||
void onReset(const ResetEvent& e) override { | |||
startingUp = true; | |||
startupTimer.reset(); | |||
Module::onReset(e); | |||
} | |||
const float in1 = inputs[IN1_INPUT].getNormalVoltage(10.f); | |||
const float in2 = inputs[IN2_INPUT].getNormalVoltage(10.f); | |||
const float in3 = inputs[IN3_INPUT].getNormalVoltage(10.f); | |||
void process(const ProcessArgs& args) override { | |||
const LightDisplayType mode1 = (LightDisplayType) params[MODE1_PARAM].getValue(); | |||
const LightDisplayType mode2 = (LightDisplayType) params[MODE2_PARAM].getValue(); | |||
const LightDisplayType mode3 = (LightDisplayType) params[MODE3_PARAM].getValue(); | |||
const float in1 = inputs[IN1_INPUT].getNormalVoltage(10.f * (mode1 != AUDIO || !break10VNormalForAudioMode)); | |||
const float in2 = inputs[IN2_INPUT].getNormalVoltage(10.f * (mode2 != AUDIO || !break10VNormalForAudioMode)); | |||
const float in3 = inputs[IN3_INPUT].getNormalVoltage(10.f * (mode3 != AUDIO || !break10VNormalForAudioMode)); | |||
const float out1 = in1 * params[CTRL_1_PARAM].getValue() * (mode1 == CV_INV ? -1 : +1); | |||
const float out2 = in2 * params[CTRL_2_PARAM].getValue() * (mode2 == CV_INV ? -1 : +1); | |||
float out3 = in3 * params[CTRL_3_PARAM].getValue() * (mode3 == CV_INV ? -1 : +1); | |||
@@ -92,7 +129,12 @@ struct MotionMTR : Module { | |||
out3 += out2; | |||
} | |||
if (sliderUpdate.process()) { | |||
// special light pattern when starting up :) | |||
if (startingUp) { | |||
processStartup(args); | |||
} | |||
// otherwise (periodically) update LEDS according to value | |||
else if (sliderUpdate.process()) { | |||
lightsForSignal(mode1, LIGHT_1, out1, args, 0); | |||
lightsForSignal(mode2, LIGHT_2, out2, args, 1); | |||
lightsForSignal(mode3, LIGHT_3, out3, args, 2); | |||
@@ -103,57 +145,158 @@ struct MotionMTR : Module { | |||
outputs[OUT3_OUTPUT].setVoltage(out3); | |||
} | |||
void setLightRGB(int lightId, const ProcessArgs& args, float R, float G, float B) { | |||
void processStartup(const ProcessArgs& args) { | |||
float time = startupTimer.process(args.sampleTime); | |||
const float ringTime = 0.4; | |||
if (time < ringTime) { | |||
int light = floor(NUM_LIGHTS_PER_DIAL * time / ringTime); | |||
setLightHSBSmooth(LIGHT_1 + 3 * light, args, 360 * time / ringTime, 1., 1.); | |||
} | |||
else if (time < 2 * ringTime) { | |||
time -= ringTime; | |||
int light = floor(NUM_LIGHTS_PER_DIAL * time / ringTime); | |||
setLightHSBSmooth(LIGHT_2 + 3 * light, args, 360 * time / ringTime, 1., 1.); | |||
} | |||
else if (time < 3 * ringTime) { | |||
time -= 2 * ringTime; | |||
int light = floor(NUM_LIGHTS_PER_DIAL * time / ringTime); | |||
setLightHSBSmooth(LIGHT_3 + 3 * light, args, 360 * time / ringTime, 1., 1.); | |||
} | |||
else { | |||
startingUp = false; | |||
} | |||
} | |||
void setLightRGB(int lightId, float R, float G, float B) { | |||
lights[lightId + 0].setBrightness(R); | |||
lights[lightId + 1].setBrightness(G); | |||
lights[lightId + 2].setBrightness(B); | |||
} | |||
void setLightRGBSmooth(int lightId, const ProcessArgs& args, float R, float G, float B) { | |||
// inverse time constant for LED smoothing | |||
const float lambda = 10.f * updateLEDRate; | |||
lights[lightId + 0].setBrightnessSmooth(R, args.sampleTime, lambda); | |||
lights[lightId + 1].setBrightnessSmooth(G, args.sampleTime, lambda); | |||
lights[lightId + 2].setBrightnessSmooth(B, args.sampleTime, lambda); | |||
} | |||
// hue: 0 - 360 | |||
void setLightHSBSmooth(int lightId, const ProcessArgs& args, float H, float S, float V) { | |||
float C = S * V; | |||
float X = C * (1 - std::abs(std::fmod(H / 60.0, 2) - 1)); | |||
float m = V - C; | |||
float r, g, b; | |||
if (H >= 0 && H < 60) { | |||
r = C, g = X, b = 0; | |||
} | |||
else if (H >= 60 && H < 120) { | |||
r = X, g = C, b = 0; | |||
} | |||
else if (H >= 120 && H < 180) { | |||
r = 0, g = C, b = X; | |||
} | |||
else if (H >= 180 && H < 240) { | |||
r = 0, g = X, b = C; | |||
} | |||
else if (H >= 240 && H < 300) { | |||
r = X, g = 0, b = C; | |||
} | |||
else { | |||
r = C, g = 0, b = X; | |||
} | |||
float R = (r + m); | |||
float G = (g + m); | |||
float B = (b + m); | |||
setLightRGBSmooth(lightId, args, R, G, B); | |||
} | |||
void lightsForSignal(LightDisplayType type, const LightId lightId, float signal, const ProcessArgs& args, const int channel) { | |||
if (type == AUDIO) { | |||
setLightRGB(lightId, args, 0.f, 1.0f, 0.f); | |||
setLightRGB(lightId, 0.f, 1.0f, 0.f); | |||
vuBar[channel].setValue(signal / 10.0f); | |||
vuBar[channel].process(args.sampleTime * updateLEDRate, signal / 10.f); | |||
for (int i = 1; i < NUM_LIGHTS_PER_DIAL; i++) { | |||
const float value = vuBar[channel].getBrightness(NUM_LIGHTS_PER_DIAL - i); | |||
const float value = vuBar[channel].getBrightness(lut[i - 1].dbValue, lut[i].dbValue); | |||
if (i < 15) { | |||
// green | |||
setLightRGB(lightId + 3 * i, args, 0.f, value, 0.f); | |||
setLightRGB(lightId + 3 * i, 0.f, value, 0.f); | |||
} | |||
else if (i < NUM_LIGHTS_PER_DIAL - 1) { | |||
// yellow | |||
setLightRGB(lightId + 3 * i, args, value, 0.65 * value, 0.f); | |||
setLightRGB(lightId + 3 * i, value, 0.65 * value, 0.f); | |||
} | |||
else { | |||
// red | |||
setLightRGB(lightId + 3 * i, args, value, 0.f, 0.f); | |||
setLightRGB(lightId + 3 * i, value, 0.f, 0.f); | |||
} | |||
} | |||
} | |||
else { | |||
setLightRGB(lightId, args, 0.82f, 0.0f, 0.82f); | |||
setLightRGBSmooth(lightId, args, 0.82f, 0.0f, 0.82f); | |||
if (signal >= 0) { | |||
for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) { | |||
float value = 0.5f * (signal > (10 * (i + 1.) / (NUM_LIGHTS_PER_DIAL + 1))); | |||
float value = (signal > (10 * (i + 1.) / (NUM_LIGHTS_PER_DIAL + 1))); | |||
// purple | |||
setLightRGB(lightId + 3 * i, args, 0.82f * value, 0.0f, 0.82f * value); | |||
setLightRGBSmooth(lightId + 3 * i, args, 0.82f * value, 0.0f, 0.82f * value); | |||
} | |||
} | |||
else { | |||
for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) { | |||
float value = (signal < (-10 * (NUM_LIGHTS_PER_DIAL - i + 1.) / (NUM_LIGHTS_PER_DIAL + 1.))); | |||
setLightRGB(lightId + 3 * i, args, value, 0.65f * value, 0.f); | |||
// orange | |||
setLightRGBSmooth(lightId + 3 * i, args, value, 0.4f * value, 0.f); | |||
} | |||
} | |||
} | |||
} | |||
json_t* dataToJson() override { | |||
json_t* rootJ = json_object(); | |||
json_object_set_new(rootJ, "break10VNormalForAudioMode", json_boolean(break10VNormalForAudioMode)); | |||
return rootJ; | |||
} | |||
void dataFromJson(json_t* rootJ) override { | |||
json_t* break10VNormalAudioJ = json_object_get(rootJ, "break10VNormalForAudioMode"); | |||
if (break10VNormalAudioJ) { | |||
break10VNormalForAudioMode = json_boolean_value(break10VNormalAudioJ); | |||
} | |||
} | |||
}; | |||
struct CKSSThreeDragable : app::SvgSlider { | |||
CKSSThreeDragable() { | |||
math::Vec margin = math::Vec(0, 0); | |||
maxHandlePos = math::Vec(1, 1).plus(margin); | |||
minHandlePos = math::Vec(1, 18).plus(margin); | |||
setBackgroundSvg(Svg::load(asset::plugin(pluginInstance, "res/components/CKSSThree_bg.svg"))); | |||
setHandleSvg(Svg::load(asset::plugin(pluginInstance, "res/components/CKSSThree_fg.svg"))); | |||
background->box.pos = margin; | |||
box.size = background->box.size.plus(margin.mult(2)); | |||
} | |||
// disable double click as this messes with click to advance | |||
void onDoubleClick(const event::DoubleClick& e) override { } | |||
// cycle through the values (with reset) on click only (not drag) | |||
void onAction(const ActionEvent& e) override { | |||
ParamQuantity* paramQuantity = getParamQuantity(); | |||
float range = paramQuantity->maxValue - paramQuantity->minValue; | |||
float newValue = paramQuantity->getValue() + 1.f; | |||
if (newValue > paramQuantity->maxValue) { | |||
newValue -= range + 1.f; | |||
} | |||
paramQuantity->setValue(newValue); | |||
} | |||
}; | |||
@@ -165,11 +308,11 @@ struct MotionMTRWidget : ModuleWidget { | |||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | |||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
addParam(createParam<CKSSThree>(mm2px(Vec(1.298, 17.851)), module, MotionMTR::MODE1_PARAM)); | |||
addParam(createParam<CKSSThreeDragable>(mm2px(Vec(1.298, 17.851)), module, MotionMTR::MODE1_PARAM)); | |||
addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(18.217, 22.18)), module, MotionMTR::CTRL_1_PARAM)); | |||
addParam(createParam<CKSSThree>(mm2px(Vec(23.762, 46.679)), module, MotionMTR::MODE2_PARAM)); | |||
addParam(createParam<CKSSThreeDragable>(mm2px(Vec(23.762, 46.679)), module, MotionMTR::MODE2_PARAM)); | |||
addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(11.777, 50.761)), module, MotionMTR::CTRL_2_PARAM)); | |||
addParam(createParam<CKSSThree>(mm2px(Vec(1.34, 74.461)), module, MotionMTR::MODE3_PARAM)); | |||
addParam(createParam<CKSSThreeDragable>(mm2px(Vec(1.34, 74.461)), module, MotionMTR::MODE3_PARAM)); | |||
addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(18.31, 78.89)), module, MotionMTR::CTRL_3_PARAM)); | |||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.008, 100.315)), module, MotionMTR::IN1_INPUT)); | |||
@@ -202,6 +345,21 @@ struct MotionMTRWidget : ModuleWidget { | |||
} | |||
} | |||
} | |||
void appendContextMenu(Menu* menu) override { | |||
MotionMTR* module = dynamic_cast<MotionMTR*>(this->module); | |||
assert(module); | |||
menu->addChild(new MenuSeparator()); | |||
menu->addChild(createSubmenuItem("Hardware compatibility", "", | |||
[ = ](Menu * menu) { | |||
menu->addChild(createBoolPtrMenuItem("Disable 10V normal in audio mode", "", &module->break10VNormalForAudioMode)); | |||
} | |||
)); | |||
} | |||
}; | |||
@@ -322,7 +322,10 @@ struct Muxlicer : Module { | |||
return "No gate"; | |||
} | |||
else if (gate == 0) { | |||
return "1/2 gate"; | |||
return "1 gate (100\% duty)"; | |||
} | |||
else if (gate == 1) { | |||
return "1 gate (50\% duty)"; | |||
} | |||
else { | |||
return string::f("%d gate(s)", gate); | |||
@@ -49,6 +49,11 @@ public: | |||
return x * x / 2.f; | |||
} | |||
} | |||
void reset() { | |||
xPrev = 0.f; | |||
} | |||
private: | |||
float xPrev = 0.f; | |||
}; | |||
@@ -101,6 +106,10 @@ public: | |||
} | |||
} | |||
void reset() { | |||
xPrev = 0.f; | |||
} | |||
private: | |||
float xPrev = 0.f; | |||
static constexpr float c = 0.1; | |||
@@ -142,7 +151,7 @@ struct PonyVCO : Module { | |||
chowdsp::VariableOversampling<6> oversampler; // uses a 2*6=12th order Butterworth filter | |||
int oversamplingIndex = 1; // default is 2^oversamplingIndex == x2 oversampling | |||
DCBlocker blockTZFMDCFilter; | |||
dsp::RCFilter blockTZFMDCFilter; | |||
bool blockTZFMDC = true; | |||
// hardware doesn't limit PW but some user might want to (to 5%->95%) | |||
@@ -182,9 +191,12 @@ struct PonyVCO : Module { | |||
void onSampleRateChange() override { | |||
float sampleRate = APP->engine->getSampleRate(); | |||
blockTZFMDCFilter.setFrequency(5. / sampleRate); | |||
blockTZFMDCFilter.setCutoffFreq(5.0 / sampleRate); | |||
oversampler.setOversamplingIndex(oversamplingIndex); | |||
oversampler.reset(sampleRate); | |||
stage1.reset(); | |||
stage2.reset(); | |||
} | |||
// implementation taken from "Alias-Suppressed Oscillators Based on Differentiated Polynomial Waveforms", | |||
@@ -209,7 +221,8 @@ struct PonyVCO : Module { | |||
float tzfmVoltage = inputs[TZFM_INPUT].getVoltage(); | |||
if (blockTZFMDC) { | |||
tzfmVoltage = blockTZFMDCFilter.process(tzfmVoltage); | |||
blockTZFMDCFilter.process(tzfmVoltage); | |||
tzfmVoltage = blockTZFMDCFilter.highpass(); | |||
} | |||
const double pitch = inputs[VOCT_INPUT].getVoltage() + params[FREQ_PARAM].getValue() * range[rangeIndex]; | |||