diff --git a/AS.JPG b/AS.JPG
index ab0b042..c00cb76 100644
Binary files a/AS.JPG and b/AS.JPG differ
diff --git a/Makefile b/Makefile
index 79429d0..0492670 100755
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
RACK_DIR ?= ../..
SLUG = AS
-VERSION = 0.6.6
+VERSION = 0.6.7
FLAGS +=
SOURCES += $(wildcard src/*.cpp freeverb/*.cpp)
diff --git a/README.md b/README.md
index d7b7580..6b17386 100755
--- a/README.md
+++ b/README.md
@@ -48,6 +48,8 @@ V 0.6.5: Added a Regular/Extended switch to change the clock from 40-250 to 30-3
V 0.6.6: Added a CV input for the "RUN" switch.
+V 0.6.7: Added a CV output for the "RUN" switch, sends a trigger signal on each press of the switch.
+
### BPM to delay/hz calculator
A BPM to delay/hz calculator to setup easier those nice delay effects.
@@ -163,6 +165,11 @@ Delay the incomming CV signal by set milliseconds, with signal thru and delayed
V 0.5.5: First relase of this module.
+### CV 2 T
+CV to Trigger module. Feed a midi signal to the CV inputs and it will output one trigger signal when the incoming signal rises above 0v, and another trigger signal when the incoming signal returns to 0v. Useful to use your external hardware controller/keyboard as a trigger.
+
+V 0.6.7: First relase of this module.
+
### Delay Plus
Fundamental Delay module. Mods: graphics, digital display to show delay time in MS , wet signal send & return, bypass switch.
@@ -175,9 +182,9 @@ V 0.6.3: bypass CV input added.
V 0.6.5: Now it features soft bypass to avoid switching noise.
### Delay Plus Stereo
-Same as Delay Plus, but now in Stereo.
+Stereo version of the Delay Plus module, with link switches for Feedback and Color parameters. If the respective switch is active, the left knob controls the changes for both left and right channels.
-V 0.6.6: Stereo version of the Delay Plus module, with link switches for Feedback and Color parameters. If the respective switch is active, the left knob controls the changes for both left and right channels.
+V 0.6.7: First relase of this module.
### Phaser
Autodafe's Phaser Fx module. Mods: graphics, bypass switch.
@@ -206,6 +213,11 @@ V 0.6.3: bypass CV input added.
V 0.6.5: Now it features soft bypass to avoid switching noise.
+### Reverb Stereo
+Stereo version of the Reverb module, BLEND is replaced with a DRY /WET knob, to work better when used with a mixer send/return ports.
+
+V 0.6.7: First relase of this module.
+
### Tremolo
Tremolo Fx module with SHAPE, SPEED and BLEND parameters, and a phase switch (set your effect, duplicate the module and invert the phase for stereo tremolo setup)your Tremolo to go!.
@@ -215,6 +227,11 @@ V 0.6.3: bypass CV input added.
V 0.6.5: Now it features soft bypass to avoid switching noise.
+### Tremolo Stereo
+Stereo version of the Tremolo module, use the phase switch to change from synced L and R channels to inverted phase, to get stereo panning effect.
+
+V 0.6.7: First relase of this module.
+
### WaveShaper
HetrickCV Wave Shaper module. Mods: graphics, bypass switch.
diff --git a/res/BPMClock.svg b/res/BPMClock.svg
index a80454b..c568309 100644
--- a/res/BPMClock.svg
+++ b/res/BPMClock.svg
@@ -25,17 +25,17 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="8.7988481"
- inkscape:cx="59.598319"
- inkscape:cy="152.76784"
+ inkscape:zoom="8.7383513"
+ inkscape:cx="38.749176"
+ inkscape:cy="153.37982"
inkscape:document-units="mm"
inkscape:current-layer="layer3"
showgrid="true"
units="px"
inkscape:window-width="1318"
inkscape:window-height="1351"
- inkscape:window-x="406"
- inkscape:window-y="1"
+ inkscape:window-x="158"
+ inkscape:window-y="0"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true">
@@ -89,6 +89,125 @@
id="polygon164"
transform="matrix(1,0,0,1.8212547,0,-22.815527)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
diff --git a/res/CV2T.svg b/res/CV2T.svg
new file mode 100644
index 0000000..ee6ca5a
--- /dev/null
+++ b/res/CV2T.svg
@@ -0,0 +1,544 @@
+
+
+
+
diff --git a/res/DelayPlusStereo.svg b/res/DelayPlusStereo.svg
index b93a5bf..58fed1d 100644
--- a/res/DelayPlusStereo.svg
+++ b/res/DelayPlusStereo.svg
@@ -66,9 +66,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="2.6374547"
- inkscape:cx="131.10938"
- inkscape:cy="188.44338"
+ inkscape:zoom="1.5280492"
+ inkscape:cx="-44.059706"
+ inkscape:cy="337.44655"
inkscape:document-units="px"
inkscape:current-layer="layer4"
showgrid="false"
@@ -78,8 +78,8 @@
fit-margin-bottom="0"
inkscape:window-width="1716"
inkscape:window-height="1300"
- inkscape:window-x="514"
- inkscape:window-y="76"
+ inkscape:window-x="37"
+ inkscape:window-y="94"
inkscape:window-maximized="0"
units="px"
showguides="true"
@@ -575,122 +575,6 @@
inkscape:connector-curvature="0" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/Tremolo.svg b/res/Tremolo.svg
index 96830d2..29a8ea6 100644
--- a/res/Tremolo.svg
+++ b/res/Tremolo.svg
@@ -39,6 +39,26 @@
id="stop887-5"
style="stop-color:#000000;stop-opacity:0.15686275" />
+
+
+
+
+ style="fill:#7a141b;fill-opacity:1">
+
+ style="fill:#66141b;fill-opacity:1" />
-
+ style="opacity:1;vector-effect:none;fill:#66141b;fill-opacity:1;stroke:none;stroke-width:0.22310618;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ style="opacity:1;vector-effect:none;fill:#66141b;fill-opacity:1;stroke:none;stroke-width:0.19682446;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+
+
+
diff --git a/src/AS.cpp b/src/AS.cpp
index d32aedf..934373b 100755
--- a/src/AS.cpp
+++ b/src/AS.cpp
@@ -32,14 +32,23 @@ void init(rack::Plugin *p) {
p->addModel(modelTriggersMKI);
p->addModel(modelTriggersMKII);
p->addModel(modelBPMCalc);
+ p->addModel(modelCv2T);
+
//EFFECTS
p->addModel(modelDelayPlusFx);
p->addModel(modelDelayPlusStereoFx);
+
p->addModel(modelPhaserFx);
+
p->addModel(modelReverbFx);
+ p->addModel(modelReverbStereoFx);
+
p->addModel(modelSuperDriveFx);
+
p->addModel(modelTremoloFx);
+ p->addModel(modelTremoloStereoFx);
+
p->addModel(modelWaveShaper);
//BLANK PANELS
p->addModel(modelBlankPanel4);
diff --git a/src/AS.hpp b/src/AS.hpp
index dc3bbb3..8323d16 100755
--- a/src/AS.hpp
+++ b/src/AS.hpp
@@ -34,14 +34,20 @@ extern Model *modelKillGate;
extern Model *modelFlow;
extern Model *modelSignalDelay;
extern Model *modelBPMCalc;
+extern Model *modelCv2T;
extern Model *modelDelayPlusFx;
extern Model *modelDelayPlusStereoFx;
extern Model *modelPhaserFx;
extern Model *modelReverbFx;
+extern Model *modelReverbStereoFx;
+
extern Model *modelSuperDriveFx;
+
extern Model *modelTremoloFx;
+extern Model *modelTremoloStereoFx;
+
extern Model *modelWaveShaper;
extern Model *modelBlankPanel4;
diff --git a/src/BPMClock.cpp b/src/BPMClock.cpp
index 8b1c857..e41c7c7 100755
--- a/src/BPMClock.cpp
+++ b/src/BPMClock.cpp
@@ -14,7 +14,8 @@
struct LFOGenerator {
float phase = 0.0f;
float pw = 0.5f;
- float freq = 1.0f;
+ float freq = 1.0f;
+ SchmittTrigger resetTrigger;
void setFreq(float freq_to_set)
{
freq = freq_to_set;
@@ -24,7 +25,12 @@ struct LFOGenerator {
phase += deltaPhase;
if (phase >= 1.0f)
phase -= 1.0f;
- }
+ }
+ void setReset(float reset) {
+ if (resetTrigger.process(reset)) {
+ phase = 0.0f;
+ }
+ }
float sqr() {
float sqr = phase < pw ? 1.0f : -1.0f;
return sqr;
@@ -51,7 +57,8 @@ struct BPMClock : Module {
EIGHTHS_OUT,
SIXTEENTHS_OUT,
BAR_OUT,
- RESET_OUTPUT,
+ RESET_OUTPUT,
+ RUN_OUTPUT,
NUM_OUTPUTS
};
enum LightIds {
@@ -74,6 +81,9 @@ struct BPMClock : Module {
PulseGenerator resetPulse;
bool reset_pulse = false;
+ PulseGenerator runPulse;
+ bool run_pulse = false;
+
// PULSES FOR TRIGGER OUTPUTS INSTEAD OF GATES
PulseGenerator clockPulse8s;
bool pulse8s = false;
@@ -130,10 +140,14 @@ void BPMClock::step() {
if (run_button_trig.process(params[RUN_SWITCH].value) || ext_run_trig.process(inputs[RUN_CV].value)){
running = !running;
+ runPulse.trigger(0.01f);
}
lights[RUN_LED].value = running ? 1.0f : 0.0f;
+ run_pulse = runPulse.process(1.0 / engineGetSampleRate());
+ outputs[RUN_OUTPUT].value = (run_pulse ? 10.0f : 0.0f);
+
if (params[MODE_PARAM].value){
//regular 40 to 250 bpm mode
tempo = std::round(params[TEMPO_PARAM].value);
@@ -151,6 +165,7 @@ void BPMClock::step() {
//RESET TRIGGER
if(reset_ext_trig.process(inputs[RESET_INPUT].value) || reset_btn_trig.process(params[RESET_SWITCH].value)) {
+ clock.setReset(1.0f);
eighths_count = 0;
quarters_count = 0;
bars_count = 0;
@@ -396,12 +411,13 @@ BPMClockWidget::BPMClockWidget(BPMClock *module) : ModuleWidget(module) {
//SIG BOTTOM KNOB
addParam(ParamWidget::create(Vec(8, 150), module, BPMClock::TIMESIGBOTTOM_PARAM,0.0f, 3.0f, 1.0f));
//RESET & RUN LEDS
+ /*
addParam(ParamWidget::create(Vec(60.5, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
addChild(ModuleLightWidget::create>(Vec(62.7, 204.3), module, BPMClock::RUN_LED));
- /*
- addParam(ParamWidget::create(Vec(10.5, 202), module, BPMClock::RESET_SWITCH , 0.0f, 1.0f, 0.0f));
- addChild(ModuleLightWidget::create>(Vec(12.7, 204.3), module, BPMClock::RESET_LED));
- */
+ */
+ addParam(ParamWidget::create(Vec(33.5, 202), module, BPMClock::RUN_SWITCH , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(35.7, 204.3), module, BPMClock::RUN_LED));
+
addParam(ParamWidget::create(Vec(33.5, 241), module, BPMClock::RESET_SWITCH , 0.0f, 1.0f, 0.0f));
addChild(ModuleLightWidget::create>(Vec(35.7, 243.2), module, BPMClock::RESET_LED));
//RESET INPUT
@@ -416,6 +432,8 @@ BPMClockWidget::BPMClockWidget(BPMClock *module) : ModuleWidget(module) {
//RUN CV
addInput(Port::create(Vec(6, 200), Port::INPUT, module, BPMClock::RUN_CV));
+ //RUN TRIGGER OUTPUT
+ addOutput(Port::create(Vec(59, 200), Port::OUTPUT, module, BPMClock::RUN_OUTPUT));
}
diff --git a/src/CV2T.cpp b/src/CV2T.cpp
new file mode 100755
index 0000000..45f3cea
--- /dev/null
+++ b/src/CV2T.cpp
@@ -0,0 +1,238 @@
+//**************************************************************************************
+//CV to Trigger convenrter module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
+//
+//
+//**************************************************************************************
+#include "AS.hpp"
+#include "dsp/digital.hpp"
+
+struct Cv2T : Module {
+ enum ParamIds {
+ TRIG_SWITCH_1,
+ TRIG_SWITCH_2,
+ TRIG_SWITCH_3,
+ TRIG_SWITCH_4,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ CV_IN_1,
+ CV_IN_2,
+ CV_IN_3,
+ CV_IN_4,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ TRIG_OUT_1,
+ TRIG_OUT_2,
+ TRIG_OUT_3,
+ TRIG_OUT_4,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ TRIG_LED_1,
+ TRIG_LED_2,
+ TRIG_LED_3,
+ TRIG_LED_4,
+ NUM_LIGHTS
+ };
+
+ SchmittTrigger trig_1, trig_2, trig_3, trig_4;
+
+ PulseGenerator trigPulse1, trigPulse2, trigPulse3, trigPulse4;
+ bool trig_pulse_1 = false;
+ bool trig_pulse_2 = false;
+ bool trig_pulse_3 = false;
+ bool trig_pulse_4 = false;
+
+ float trigger_length = 0.0001f;
+
+ const float lightLambda = 0.075f;
+ float trigLight1 = 0.0f;
+ float trigLight2 = 0.0f;
+ float trigLight3 = 0.0f;
+ float trigLight4 = 0.0f;
+
+ bool cv_1_engaged = false;
+ bool cv_2_engaged = false;
+ bool cv_3_engaged = false;
+ bool cv_4_engaged = false;
+
+ float current_cv_1_volts = 0.0f;
+ float current_cv_2_volts = 0.0f;
+ float current_cv_3_volts = 0.0f;
+ float current_cv_4_volts = 0.0f;
+
+ Cv2T() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
+
+ void step() override;
+};
+
+
+void Cv2T::step() {
+ //CV 2 TRIG 1
+ if ( trig_1.process( params[TRIG_SWITCH_1].value ) ) {
+ trigLight1 = 1.0;
+ trigPulse1.trigger( trigger_length );
+ }
+ current_cv_1_volts = inputs[CV_IN_1].value;
+
+ if ( !cv_1_engaged ) {
+ if ( current_cv_1_volts > 0.0f ) {
+ cv_1_engaged = true;
+ trigLight1 = 1.0;
+ trigPulse1.trigger( trigger_length );
+ // send start trigger
+ }
+ } else {
+ if ( (int)current_cv_1_volts <= 0 ) {
+ // send stop trigger
+ trigLight1 = 1.0;
+ trigPulse1.trigger( trigger_length );
+ cv_1_engaged = false;
+ }
+ }
+
+ trigLight1 -= trigLight1 / lightLambda / engineGetSampleRate();
+ lights[TRIG_LED_1].value = trigLight1;
+ trig_pulse_1 = trigPulse1.process( 1.0 / engineGetSampleRate() );
+ outputs[TRIG_OUT_1].value = ( trig_pulse_1 ? 10.0f : 0.0f );
+
+ //CV 2 TRIG 2
+ if ( trig_2.process( params[TRIG_SWITCH_2].value ) ) {
+ trigLight2 = 1.0;
+ trigPulse2.trigger( trigger_length );
+ }
+ current_cv_2_volts = inputs[CV_IN_2].value;
+
+ if ( !cv_2_engaged ) {
+ if ( current_cv_2_volts > 0.0f ) {
+ cv_2_engaged = true;
+ trigLight2 = 1.0;
+ trigPulse2.trigger( trigger_length );
+ // send start trigger
+ }
+ } else {
+ if ( (int)current_cv_2_volts <= 0 ) {
+ // send stop trigger
+ trigLight2 = 1.0;
+ trigPulse2.trigger( trigger_length );
+ cv_2_engaged = false;
+ }
+ }
+
+ trigLight2 -= trigLight2 / lightLambda / engineGetSampleRate();
+ lights[TRIG_LED_2].value = trigLight2;
+ trig_pulse_2 = trigPulse2.process( 1.0 / engineGetSampleRate() );
+ outputs[TRIG_OUT_2].value = ( trig_pulse_2 ? 10.0f : 0.0f );
+
+ //CV 2 TRIG 3
+ if ( trig_3.process( params[TRIG_SWITCH_3].value ) ) {
+ trigLight3 = 1.0;
+ trigPulse3.trigger( trigger_length );
+ }
+ current_cv_3_volts = inputs[CV_IN_3].value;
+
+ if ( !cv_3_engaged ) {
+ if ( current_cv_3_volts > 0.0f ) {
+ cv_3_engaged = true;
+ trigLight3 = 1.0;
+ trigPulse3.trigger( trigger_length );
+ // send start trigger
+ }
+ } else {
+ if ( (int)current_cv_3_volts <= 0 ) {
+ // send stop trigger
+ trigLight3 = 1.0;
+ trigPulse3.trigger( trigger_length );
+ cv_3_engaged = false;
+ }
+ }
+
+ trigLight3 -= trigLight3 / lightLambda / engineGetSampleRate();
+ lights[TRIG_LED_3].value = trigLight3;
+ trig_pulse_3 = trigPulse3.process( 1.0 / engineGetSampleRate() );
+ outputs[TRIG_OUT_3].value = ( trig_pulse_3 ? 10.0f : 0.0f );
+
+ //CV 2 TRIG 4
+ if ( trig_4.process( params[TRIG_SWITCH_4].value ) ) {
+ trigLight4 = 1.0;
+ trigPulse4.trigger( trigger_length );
+ }
+ current_cv_4_volts = inputs[CV_IN_4].value;
+
+ if ( !cv_4_engaged ) {
+ if ( current_cv_4_volts > 0.0f ) {
+ cv_4_engaged = true;
+ trigLight4 = 1.0;
+ trigPulse4.trigger( trigger_length );
+ // send start trigger
+ }
+ } else {
+ if ( (int)current_cv_4_volts <= 0 ) {
+ // send stop trigger
+ trigLight4 = 1.0;
+ trigPulse4.trigger( trigger_length );
+ cv_4_engaged = false;
+ }
+ }
+
+ trigLight4 -= trigLight4 / lightLambda / engineGetSampleRate();
+ lights[TRIG_LED_4].value = trigLight4;
+ trig_pulse_4 = trigPulse4.process( 1.0 / engineGetSampleRate() );
+ outputs[TRIG_OUT_4].value = ( trig_pulse_4 ? 10.0f : 0.0f );
+
+}
+
+struct Cv2TWidget : ModuleWidget
+{
+ Cv2TWidget(Cv2T *module);
+};
+
+
+Cv2TWidget::Cv2TWidget(Cv2T *module) : ModuleWidget(module) {
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/CV2T.svg")));
+
+ //SCREWS - SPECIAL SPACING FOR RACK WIDTH*4
+ addChild(Widget::create(Vec(0, 0)));
+ addChild(Widget::create(Vec(box.size.x - RACK_GRID_WIDTH, 0)));
+ addChild(Widget::create(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(Widget::create(Vec(box.size.x - RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+const int gp_offset = 75;
+ //CV 2 TRIG 1
+ //SWITCH & LED
+ addParam(ParamWidget::create(Vec(6, 101), module, Cv2T::TRIG_SWITCH_1 , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(6+2.2, 103.2), module, Cv2T::TRIG_LED_1));
+ //INPUTS
+ addInput(Port::create(Vec(18,60), Port::INPUT, module, Cv2T::CV_IN_1));
+ //OUTPUTS
+ addOutput(Port::create(Vec(32, 100), Port::OUTPUT, module, Cv2T::TRIG_OUT_1));
+ //CV 2 TRIG 2
+ //SWITCH & LED
+ addParam(ParamWidget::create(Vec(6, 101+gp_offset*1), module, Cv2T::TRIG_SWITCH_2 , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(6+2.2, 103.2+gp_offset*1), module, Cv2T::TRIG_LED_2));
+ //INPUTS
+ addInput(Port::create(Vec(18,60+gp_offset*1), Port::INPUT, module, Cv2T::CV_IN_2));
+ //OUTPUTS
+ addOutput(Port::create(Vec(32, 100+gp_offset*1), Port::OUTPUT, module, Cv2T::TRIG_OUT_2));
+ //CV 2 TRIG 3
+ //SWITCH & LED
+ addParam(ParamWidget::create(Vec(6, 101+gp_offset*2), module, Cv2T::TRIG_SWITCH_3 , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(6+2.2, 103.2+gp_offset*2), module, Cv2T::TRIG_LED_3));
+ //INPUTS
+ addInput(Port::create(Vec(18,60+gp_offset*2), Port::INPUT, module, Cv2T::CV_IN_3));
+ //OUTPUTS
+ addOutput(Port::create(Vec(32, 100+gp_offset*2), Port::OUTPUT, module, Cv2T::TRIG_OUT_3));
+ //CV 2 TRIG 4
+ //SWITCH & LED
+ addParam(ParamWidget::create(Vec(6, 101+gp_offset*3), module, Cv2T::TRIG_SWITCH_4 , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(6+2.2, 103.2+gp_offset*3), module, Cv2T::TRIG_LED_4));
+ //INPUTS
+ addInput(Port::create(Vec(18,60+gp_offset*3), Port::INPUT, module, Cv2T::CV_IN_4));
+ //OUTPUTS
+ addOutput(Port::create(Vec(32, 100+gp_offset*3), Port::OUTPUT, module, Cv2T::TRIG_OUT_4));
+
+}
+
+Model *modelCv2T = Model::create("AS", "Cv2T", "CV to Trigger Switch", SWITCH_TAG);
\ No newline at end of file
diff --git a/src/Reverb.cpp b/src/Reverb.cpp
index a024eab..54413d4 100755
--- a/src/Reverb.cpp
+++ b/src/Reverb.cpp
@@ -130,7 +130,7 @@ void ReverbFx::step() {
if( old_damp != damp ) reverb.setdamp(damp);
if( old_roomsize != roomsize) reverb.setroomsize(roomsize);
- reverb.process(input_signal, out1, out2);
+ reverb.process(input_signal+ input_signal, out1, out2);
float out = input_signal + out1 * clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
diff --git a/src/ReverbStereo.cpp b/src/ReverbStereo.cpp
new file mode 100755
index 0000000..f784b76
--- /dev/null
+++ b/src/ReverbStereo.cpp
@@ -0,0 +1,239 @@
+//***********************************************************************************************
+//
+//Reverb Stereo module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
+//
+//Based on code from ML_Modules by martin-lueders https://github.com/martin-lueders/ML_modules
+//And code from Freeverb by Jezar at Dreampoint - http://www.dreampoint.co.uk
+//
+//***********************************************************************************************
+
+#include "AS.hpp"
+#include "dsp/digital.hpp"
+
+#include "../freeverb/revmodel.hpp"
+
+struct ReverbStereoFx : Module{
+ enum ParamIds {
+ DECAY_PARAM,
+ DAMP_PARAM,
+ BLEND_PARAM,
+ BYPASS_SWITCH,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ SIGNAL_INPUT_L,
+ SIGNAL_INPUT_R,
+ DECAY_CV_INPUT,
+ DAMP_CV_INPUT,
+ BLEND_CV_INPUT,
+ BYPASS_CV_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ SIGNAL_OUTPUT_L,
+ SIGNAL_OUTPUT_R,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ DECAY_LIGHT,
+ DAMP_LIGHT,
+ BLEND_LIGHT,
+ BYPASS_LED,
+ NUM_LIGHTS
+ };
+
+ revmodel reverb;
+ float roomsize, damp;
+
+ SchmittTrigger bypass_button_trig;
+ SchmittTrigger bypass_cv_trig;
+
+ bool fx_bypass = false;
+
+ float input_signal_L = 0.0f;
+ float input_signal_R = 0.0f;
+ float mix_value = 0.0f;
+ float outL = 0.0f;
+ float outR = 0.0f;
+
+ float fade_in_fx = 0.0f;
+ float fade_in_dry = 0.0f;
+ float fade_out_fx = 1.0f;
+ float fade_out_dry = 1.0f;
+ const float fade_speed = 0.001f;
+
+ ReverbStereoFx() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
+
+ float gSampleRate = engineGetSampleRate();
+ reverb.init(gSampleRate);
+ }
+
+ void step() override;
+
+ void onSampleRateChange() override;
+
+ json_t *toJson()override {
+ json_t *rootJm = json_object();
+
+ json_t *statesJ = json_array();
+
+ json_t *bypassJ = json_boolean(fx_bypass);
+ json_array_append_new(statesJ, bypassJ);
+
+ json_object_set_new(rootJm, "as_FxBypass", statesJ);
+
+ return rootJm;
+ }
+
+ void fromJson(json_t *rootJm)override {
+ json_t *statesJ = json_object_get(rootJm, "as_FxBypass");
+
+ json_t *bypassJ = json_array_get(statesJ, 0);
+
+ fx_bypass = !!json_boolean_value(bypassJ);
+
+ }
+
+ void resetFades(){
+ fade_in_fx = 0.0f;
+ fade_in_dry = 0.0f;
+ fade_out_fx = 1.0f;
+ fade_out_dry = 1.0f;
+ }
+
+};
+
+void ReverbStereoFx::onSampleRateChange() {
+
+ float gSampleRate = engineGetSampleRate();
+
+ reverb.init(gSampleRate);
+
+ reverb.setdamp(damp);
+ reverb.setroomsize(roomsize);
+ reverb.setwidth(1.0f);
+
+};
+
+
+void ReverbStereoFx::step() {
+
+ if (bypass_button_trig.process(params[BYPASS_SWITCH].value) || bypass_cv_trig.process(inputs[BYPASS_CV_INPUT].value) ){
+ fx_bypass = !fx_bypass;
+ resetFades();
+ }
+ lights[BYPASS_LED].value = fx_bypass ? 1.0f : 0.0f;
+
+ float wetL, wetR;
+
+ wetL = wetR = 0.0f;
+
+ float old_roomsize = roomsize;
+ float old_damp = damp;
+
+ input_signal_L = clamp(inputs[SIGNAL_INPUT_L].value,-10.0f,10.0f);
+
+ if(!inputs[SIGNAL_INPUT_R].active){
+ input_signal_R = input_signal_L;
+ }else{
+ input_signal_R = clamp(inputs[SIGNAL_INPUT_R].value,-10.0f,10.0f);
+ }
+
+ roomsize = clamp(params[DECAY_PARAM].value + inputs[DECAY_CV_INPUT].value / 10.0f, 0.0f, 0.88f);
+ damp = clamp(params[DAMP_PARAM].value + inputs[DAMP_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+
+ if( old_damp != damp ) reverb.setdamp(damp);
+ if( old_roomsize != roomsize) reverb.setroomsize(roomsize);
+
+
+ reverb.process(input_signal_L + input_signal_R, wetL, wetR);
+
+ /*
+ //original mix method, changed to work better when used with a mixer FX loop
+ float outL = input_signal_L + wetL * clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+ float outR = input_signal_R + wetR * clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+ */
+
+ mix_value = clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+
+ outL = crossfade(input_signal_L, wetL, mix_value);
+ outR = crossfade(input_signal_R, wetR, mix_value);
+
+ //check bypass switch status
+ if (fx_bypass){
+ fade_in_dry += fade_speed;
+ if ( fade_in_dry > 1.0f ) {
+ fade_in_dry = 1.0f;
+ }
+ fade_out_fx -= fade_speed;
+ if ( fade_out_fx < 0.0f ) {
+ fade_out_fx = 0.0f;
+ }
+ outputs[SIGNAL_OUTPUT_L].value = ( input_signal_L * fade_in_dry ) + ( outL * fade_out_fx );
+ outputs[SIGNAL_OUTPUT_R].value = ( input_signal_R * fade_in_dry ) + ( outR * fade_out_fx );
+ }else{
+ fade_in_fx += fade_speed;
+ if ( fade_in_fx > 1.0f ) {
+ fade_in_fx = 1.0f;
+ }
+ fade_out_dry -= fade_speed;
+ if ( fade_out_dry < 0.0f ) {
+ fade_out_dry = 0.0f;
+ }
+ outputs[SIGNAL_OUTPUT_L].value = ( input_signal_L * fade_out_dry ) + ( outL * fade_in_fx );
+ outputs[SIGNAL_OUTPUT_R].value = ( input_signal_R * fade_out_dry ) + ( outR * fade_in_fx );
+ }
+
+ lights[DECAY_LIGHT].value = clamp(params[DECAY_PARAM].value + inputs[DECAY_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+ lights[DAMP_LIGHT].value = clamp(params[DAMP_PARAM].value + inputs[DAMP_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+ lights[BLEND_LIGHT].value = clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+
+}
+
+struct ReverbStereoFxWidget : ModuleWidget
+{
+ ReverbStereoFxWidget(ReverbStereoFx *module);
+};
+
+
+ReverbStereoFxWidget::ReverbStereoFxWidget(ReverbStereoFx *module) : ModuleWidget(module) {
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/ReverbStereo.svg")));
+ //SCREWS
+ addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ //KNOBS
+ addParam(ParamWidget::create(Vec(43, 60), module, ReverbStereoFx::DECAY_PARAM, 0.0f, 0.95f, 0.475f));
+ addParam(ParamWidget::create(Vec(43, 125), module, ReverbStereoFx::DAMP_PARAM, 0.0f, 1.0f, 0.5f));
+ addParam(ParamWidget::create(Vec(43, 190), module, ReverbStereoFx::BLEND_PARAM, 0.0f, 1.0f, 0.5f));
+ //LIGHTS
+ addChild(ModuleLightWidget::create>(Vec(39, 57), module, ReverbStereoFx::DECAY_LIGHT));
+ addChild(ModuleLightWidget::create>(Vec(39, 122), module, ReverbStereoFx::DAMP_LIGHT));
+ addChild(ModuleLightWidget::create>(Vec(39, 187), module, ReverbStereoFx::BLEND_LIGHT));
+ //BYPASS SWITCH
+ addParam(ParamWidget::create(Vec(55, 260), module, ReverbStereoFx::BYPASS_SWITCH , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(57.2, 262), module, ReverbStereoFx::BYPASS_LED));
+ /*
+ //INS/OUTS
+ addInput(Port::create(Vec(10, 310), Port::INPUT, module, ReverbStereoFx::SIGNAL_INPUT_L));
+ addOutput(Port::create(Vec(55, 310), Port::OUTPUT, module, ReverbStereoFx::SIGNAL_OUTPUT_L));
+ */
+ //INPUTS
+ addInput(Port::create(Vec(15, 300), Port::INPUT, module, ReverbStereoFx::SIGNAL_INPUT_L));
+ addInput(Port::create(Vec(15, 330), Port::INPUT, module, ReverbStereoFx::SIGNAL_INPUT_R));
+ //OUTPUTS
+ addOutput(Port::create(Vec(50, 300), Port::OUTPUT, module, ReverbStereoFx::SIGNAL_OUTPUT_L));
+ addOutput(Port::create(Vec(50, 330), Port::OUTPUT, module, ReverbStereoFx::SIGNAL_OUTPUT_R));
+ //CV INPUTS
+ addInput(Port::create(Vec(10, 67), Port::INPUT, module, ReverbStereoFx::DECAY_CV_INPUT));
+ addInput(Port::create(Vec(10, 132), Port::INPUT, module, ReverbStereoFx::DAMP_CV_INPUT));
+ addInput(Port::create(Vec(10, 197), Port::INPUT, module, ReverbStereoFx::BLEND_CV_INPUT));
+
+ //BYPASS CV INPUT
+ addInput(Port::create(Vec(10, 259), Port::INPUT, module, ReverbStereoFx::BYPASS_CV_INPUT));
+
+}
+
+Model *modelReverbStereoFx = Model::create("AS", "ReverbStereoFx", "Reverb Stereo FX", REVERB_TAG, EFFECT_TAG);
\ No newline at end of file
diff --git a/src/TremoloStereo.cpp b/src/TremoloStereo.cpp
new file mode 100644
index 0000000..5cd3715
--- /dev/null
+++ b/src/TremoloStereo.cpp
@@ -0,0 +1,279 @@
+//***********************************************************************************************
+//
+//TremoloStereoFx module for VCV Rack by Alfredo Santamaria - AS - https://github.com/AScustomWorks/AS
+//
+//LFO code adapted from the Fundamentals plugins by Andrew Belt http://www.vcvrack.com
+//***********************************************************************************************
+
+#include "AS.hpp"
+#include "dsp/digital.hpp"
+
+//LFO CODE *****************************
+struct LowFrequencyoscillator {
+ float phase = 0.0f;
+ float pw = 0.5f;
+ float freq = 1.0f;
+ bool offset = false;
+ bool invert = false;
+ SchmittTrigger resetTrigger;
+
+ LowFrequencyoscillator() {
+
+ }
+ void setPitch(float pitch) {
+ pitch = fminf(pitch, 8.0f);
+ freq = powf(2.0f, pitch);
+ }
+ void setPulseWidth(float pw_) {
+ const float pwMin = 0.01f;
+ pw = clamp(pw_, pwMin, 1.0f - pwMin);
+ }
+ void setReset(float reset) {
+ if (resetTrigger.process(reset)) {
+ phase = 0.0f;
+ }
+ }
+ void step(float dt) {
+ float deltaPhase = fminf(freq * dt, 0.5f);
+ phase += deltaPhase;
+ if (phase >= 1.0f)
+ phase -= 1.0f;
+ }
+ float sin() {
+ if (offset)
+ return 1.0f - cosf(2*M_PI * phase) * (invert ? -1.0f : 1.0f);
+ else
+ return sinf(2.0f*M_PI * phase) * (invert ? -1.0f : 1.0f);
+ }
+ float tri(float x) {
+ return 4.0f * fabsf(x - roundf(x));
+ }
+ float tri() {
+ if (offset)
+ return tri(invert ? phase - 0.5 : phase);
+ else
+ return -1.0f + tri(invert ? phase - 0.25f : phase - 0.75f);
+ }
+ float light() {
+ return sinf(2.0f*M_PI * phase);
+ }
+};
+//LFO CODE *****************************
+
+struct TremoloStereoFx : Module{
+ enum ParamIds {
+ WAVE_PARAM,
+ FREQ_PARAM,
+ BLEND_PARAM,
+ INVERT_PARAM,
+ BYPASS_SWITCH,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ SIGNAL_INPUT_L,
+ SIGNAL_INPUT_R,
+ WAVE_CV_INPUT,
+ FREQ_CV_INPUT,
+ BLEND_CV_INPUT,
+ BYPASS_CV_INPUT,
+ RESET_CV_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ SIGNAL_OUTPUT_L,
+ SIGNAL_OUTPUT_R,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ WAVE_LIGHT,
+ PHASE_POS_LIGHT,
+ PHASE_NEG_LIGHT,
+ BLEND_LIGHT,
+ BYPASS_LED,
+ NUM_LIGHTS
+ };
+
+ LowFrequencyoscillator oscillatorL, oscillatorR;
+
+ SchmittTrigger bypass_button_trig;
+ SchmittTrigger bypass_cv_trig;
+
+ bool fx_bypass = false;
+
+ float fade_in_fx = 0.0f;
+ float fade_in_dry = 0.0f;
+ float fade_out_fx = 1.0f;
+ float fade_out_dry = 1.0f;
+ const float fade_speed = 0.001f;
+
+ TremoloStereoFx() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
+
+ void step() override;
+
+ json_t *toJson()override {
+ json_t *rootJm = json_object();
+
+ json_t *statesJ = json_array();
+
+ json_t *bypassJ = json_boolean(fx_bypass);
+ json_array_append_new(statesJ, bypassJ);
+
+ json_object_set_new(rootJm, "as_FxBypass", statesJ);
+
+ return rootJm;
+ }
+
+ void fromJson(json_t *rootJm)override {
+ json_t *statesJ = json_object_get(rootJm, "as_FxBypass");
+
+ json_t *bypassJ = json_array_get(statesJ, 0);
+
+ fx_bypass = !!json_boolean_value(bypassJ);
+
+ }
+
+ void resetFades(){
+ fade_in_fx = 0.0f;
+ fade_in_dry = 0.0f;
+ fade_out_fx = 1.0f;
+ fade_out_dry = 1.0f;
+ }
+
+ float input_signal_L = 0.0f;
+ float output_signal_L = 0.0f;
+ float input_signal_R = 0.0f;
+ float output_signal_R = 0.0f;
+ float tremolo_signal_L = 0.0f;
+ float tremolo_signal_R = 0.0f;
+ float blend_control = 0.0f;
+ float lfo_modulation_L = 0.0f;
+ float lfo_modulation_R = 0.0f;
+
+};
+
+void TremoloStereoFx::step() {
+
+ if (bypass_button_trig.process(params[BYPASS_SWITCH].value) || bypass_cv_trig.process(inputs[BYPASS_CV_INPUT].value) ){
+ fx_bypass = !fx_bypass;
+ resetFades();
+ }
+ lights[BYPASS_LED].value = fx_bypass ? 1.0f : 0.0f;
+
+ input_signal_L = clamp(inputs[SIGNAL_INPUT_L].value,-10.0f,10.0f);
+
+ if(!inputs[SIGNAL_INPUT_R].active){
+ input_signal_R = input_signal_L;
+ }else{
+ input_signal_R = clamp(inputs[SIGNAL_INPUT_R].value,-10.0f,10.0f);
+ }
+
+ float lfo_pitch = clamp(params[FREQ_PARAM].value + inputs[FREQ_CV_INPUT].value, 0.0f, 3.5f);
+ //LFO L
+ oscillatorL.setPitch( lfo_pitch );
+ oscillatorL.offset = (0.0f);
+ oscillatorL.invert = (params[INVERT_PARAM].value <= 0.0f);
+ oscillatorL.setPulseWidth(0.5f);
+ oscillatorL.step(1.0f / engineGetSampleRate());
+ oscillatorL.setReset(inputs[RESET_CV_INPUT].value);
+ //LFO R
+ oscillatorR.setPitch( lfo_pitch );
+ oscillatorR.offset = (0.0f);
+ oscillatorR.invert = false;
+ oscillatorR.setPulseWidth(0.5f);
+ oscillatorR.step(1.0f / engineGetSampleRate());
+ oscillatorR.setReset(inputs[RESET_CV_INPUT].value);
+
+ float wave = clamp( params[WAVE_PARAM].value + inputs[WAVE_CV_INPUT].value, 0.0f, 1.0f );
+
+ float interp_L = crossfade(oscillatorL.sin(), oscillatorL.tri(), wave);
+ float interp_R = crossfade(oscillatorR.sin(), oscillatorR.tri(), wave);
+
+ lfo_modulation_L = 5.0f * interp_L;
+ lfo_modulation_R = 5.0f * interp_R;
+
+ tremolo_signal_L = input_signal_L * clamp(lfo_modulation_L/10.0f, 0.0f, 1.0f);
+ tremolo_signal_R = input_signal_R * clamp(lfo_modulation_R/10.0f, 0.0f, 1.0f);
+
+ blend_control = clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+
+ output_signal_L = crossfade(input_signal_L,tremolo_signal_L,blend_control);
+ output_signal_R = crossfade(input_signal_R,tremolo_signal_R,blend_control);
+ //check bypass switch status
+ if (fx_bypass){
+ fade_in_dry += fade_speed;
+ if ( fade_in_dry > 1.0f ) {
+ fade_in_dry = 1.0f;
+ }
+ fade_out_fx -= fade_speed;
+ if ( fade_out_fx < 0.0f ) {
+ fade_out_fx = 0.0f;
+ }
+ outputs[SIGNAL_OUTPUT_L].value = ( input_signal_L * fade_in_dry ) + ( output_signal_L * fade_out_fx );
+ outputs[SIGNAL_OUTPUT_R].value = ( input_signal_R * fade_in_dry ) + ( output_signal_R * fade_out_fx );
+ }else{
+ fade_in_fx += fade_speed;
+ if ( fade_in_fx > 1.0f ) {
+ fade_in_fx = 1.0f;
+ }
+ fade_out_dry -= fade_speed;
+ if ( fade_out_dry < 0.0f ) {
+ fade_out_dry = 0.0f;
+ }
+ outputs[SIGNAL_OUTPUT_L].value = ( input_signal_L * fade_out_dry ) + ( output_signal_L * fade_in_fx );
+ outputs[SIGNAL_OUTPUT_R].value = ( input_signal_R * fade_out_dry ) + ( output_signal_R * fade_in_fx );
+ }
+
+ lights[PHASE_POS_LIGHT].setBrightnessSmooth(fmaxf(0.0f, oscillatorL.light()));
+ lights[PHASE_NEG_LIGHT].setBrightnessSmooth(fmaxf(0.0f, -oscillatorL.light()));
+ lights[BLEND_LIGHT].value = clamp(params[BLEND_PARAM].value + inputs[BLEND_CV_INPUT].value / 10.0f, 0.0f, 1.0f);
+
+}
+
+
+
+struct TremoloStereoFxWidget : ModuleWidget
+{
+ TremoloStereoFxWidget(TremoloStereoFx *module);
+};
+
+
+TremoloStereoFxWidget::TremoloStereoFxWidget(TremoloStereoFx *module) : ModuleWidget(module) {
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/TremoloStereo.svg")));
+
+ //SCREWS
+ addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ //phase switch
+ addParam(ParamWidget::create(Vec(13, 100), module, TremoloStereoFx::INVERT_PARAM, 0.0f, 1.0f, 1.0f));
+ //KNOBS
+ addParam(ParamWidget::create(Vec(43, 60), module, TremoloStereoFx::WAVE_PARAM, 0.0f, 1.0f, 0.5f));
+ addParam(ParamWidget::create(Vec(43, 125), module, TremoloStereoFx::FREQ_PARAM, 0.0f, 3.5f, 1.75f));
+ addParam(ParamWidget::create(Vec(43, 190), module, TremoloStereoFx::BLEND_PARAM, 0.0f, 1.0f, 0.5f));
+ //LIGHTS
+ addChild(ModuleLightWidget::create>(Vec(39, 122), module, TremoloStereoFx::PHASE_POS_LIGHT));
+ addChild(ModuleLightWidget::create>(Vec(39, 187), module, TremoloStereoFx::BLEND_LIGHT));
+ //CV INPUTS
+ addInput(Port::create(Vec(10, 67), Port::INPUT, module, TremoloStereoFx::WAVE_CV_INPUT));
+ addInput(Port::create(Vec(10, 132), Port::INPUT, module, TremoloStereoFx::FREQ_CV_INPUT));
+ addInput(Port::create(Vec(10, 197), Port::INPUT, module, TremoloStereoFx::BLEND_CV_INPUT));
+ //INPUTS
+ addInput(Port::create(Vec(15, 300), Port::INPUT, module, TremoloStereoFx::SIGNAL_INPUT_L));
+ addInput(Port::create(Vec(15, 330), Port::INPUT, module, TremoloStereoFx::SIGNAL_INPUT_R));
+ //OUTPUTS
+ addOutput(Port::create(Vec(50, 300), Port::OUTPUT, module, TremoloStereoFx::SIGNAL_OUTPUT_L));
+ addOutput(Port::create(Vec(50, 330), Port::OUTPUT, module, TremoloStereoFx::SIGNAL_OUTPUT_R));
+ //RESET CV
+ addInput(Port::create(Vec(6, 259), Port::INPUT, module, TremoloStereoFx::RESET_CV_INPUT));
+
+ //BYPASS CV INPUT
+ addInput(Port::create(Vec(33.5, 259), Port::INPUT, module, TremoloStereoFx::BYPASS_CV_INPUT));
+ //BYPASS SWITCH
+ addParam(ParamWidget::create(Vec(61, 260), module, TremoloStereoFx::BYPASS_SWITCH , 0.0f, 1.0f, 0.0f));
+ addChild(ModuleLightWidget::create>(Vec(63.2, 262.2), module, TremoloStereoFx::BYPASS_LED));
+
+}
+
+Model *modelTremoloStereoFx = Model::create("AS", "TremoloStereoFx", "Tremolo Stereo FX", EFFECT_TAG);
\ No newline at end of file