diff --git a/.gitignore b/.gitignore
index 3f3c92f..13a8de1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,11 @@
-backup
+/backup
+/dist
+/build
+/mschack
plugin.dll
+plugin.so
+plugin.dylib
+WaveShaper1.svg
+WaveShaper.cpp
*.bat
-Waveshaper1.svg
\ No newline at end of file
+*.scc
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..f901edc
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,7 @@
+Copyright (c) 2017 Mark Schack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..672af45
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+
+SOURCES = $(wildcard src/*.cpp)
+
+# Add files to the ZIP package when running `make dist`
+# The compiled plugin is automatically added.
+DISTRIBUTABLES += $(wildcard LICENSE*) res
+
+# If RACK_DIR is not defined when calling the Makefile, default to two levels above
+RACK_DIR ?= ../..
+
+# Include the VCV Rack plugin Makefile framework
+include $(RACK_DIR)/plugin.mk
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..37c065d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+# mscHack Plugins for VCV Rack 0.5.0
+
+(Note the release folder is in the mscHack.zip Windows Only at the moment)
+
+
+
+# Updates
+- 14 Dec 17
+ - NEW ARP 700 - 7 note arpeggiator with different pattern modes
+ - externally clocked
+ - 16 programs
+ - glide and legato
+- 7 Dec 17:
+ - Remove annoying click from 3xOSC
+ - Add Hold CV option to 6x32 SEQ
+- 6 Dec 17:
+ - Added side chain input to compressor
+ - Fixes to compressor.
+- 26 Nov 17:
+ - Added Freq CV to Synth Drums.
+ - Added Notch filters to 3xOSC and Ping Pong Delay.
+ - 6x32 sequencer and triad2 now can have different max steps per saved pattern.
+ - Triad Sequencer now has 8 pattern saves.
+- 25 Nov 17:
+ - improved buttons on multiple modules.
+ - fixed LED Meters randomly not working.
+- 24 Nov 17:
+ - (NEW) 4 Channel Master Clock (BPM, mult and div, chainable, humanize setting, sync buttons)
+ - fix 2x4 mixer solo/mute
+ - fix triad sequencer keyboard bug
+ - fix OSC 3 crash when VOCT offset push note into to high a range, and fix osc tuning.
+
+# Includes
+- 4 Channel Master Clock
+- 3 Channel 16 Step Programmable Sequencer.
+- 6 Channel 32 Step Sequencer
+- Mixer 4x4 (old) (GOING OBSOLETE!!!)
+- Mixer 1x4, 2x4 and 4x4 (with EQ, 4xAUX buses, and 2 x amplification )
+- Triad Sequencer (GOING OBSOLETE!!!)
+- Triad Sequencer (Type 2), with Independantly clocked channels
+- Synth Drums
+- 3 Channel Oscillator with Filter and Amp Envelope (latest has multi oscillator capability)
+- Ping Pong Delay
+- Compressor (a work in progress...)
+- X Fade, 3 stereo channel cross fader
+- ARP 700, 7 note arpeggiator.
+
+# Demo Vids
+- 4 Channel Master Clock https://www.youtube.com/watch?v=hiQciS8ch5U
+- Sequencer: https://www.youtube.com/watch?v=2PN0-UZhocA
+- Mixer: https://www.youtube.com/watch?v=QOjSEM3mPqs
+- Triad Sequencer https://www.youtube.com/watch?v=InOgQA91cs8
+- Triad Sequencer (Type 2) https://www.youtube.com/watch?v=NAza3lUqkkQ
+- Synth Drums https://www.youtube.com/watch?v=zYrtJ2XsbTw
+- Pong https://www.youtube.com/watch?v=hTsco8omRT0
+- 3 Channel OSC https://www.youtube.com/watch?v=BV9nWX9Izq0
+- Compressor https://www.youtube.com/watch?v=0F9KCk0IgFU
+- X Fade https://www.youtube.com/watch?v=1NN4ly77nXo
+- 6 x 32 trigger sequencer https://www.youtube.com/watch?v=S1PB-WaqXt4
+- ARP 700 quicky demo https://www.youtube.com/watch?v=ht9EwXmkDJI
diff --git a/modules.PNG b/modules.PNG
new file mode 100644
index 0000000..63b92e2
Binary files /dev/null and b/modules.PNG differ
diff --git a/mscHack.zip b/mscHack.zip
new file mode 100644
index 0000000..e2dc556
Binary files /dev/null and b/mscHack.zip differ
diff --git a/res/ARP700.svg b/res/ARP700.svg
new file mode 100644
index 0000000..472b758
--- /dev/null
+++ b/res/ARP700.svg
@@ -0,0 +1,1032 @@
+
+
+
+
diff --git a/res/Compressor.svg b/res/Compressor.svg
new file mode 100644
index 0000000..5517407
--- /dev/null
+++ b/res/Compressor.svg
@@ -0,0 +1,611 @@
+
+
+
+
diff --git a/res/MasterClockx4.svg b/res/MasterClockx4.svg
new file mode 100644
index 0000000..5144062
--- /dev/null
+++ b/res/MasterClockx4.svg
@@ -0,0 +1,1332 @@
+
+
+
+
diff --git a/res/Mix_1x4_Stereo.svg b/res/Mix_1x4_Stereo.svg
new file mode 100644
index 0000000..09619d6
--- /dev/null
+++ b/res/Mix_1x4_Stereo.svg
@@ -0,0 +1,947 @@
+
+
+
+
diff --git a/res/Mix_2x4_Stereo.svg b/res/Mix_2x4_Stereo.svg
new file mode 100644
index 0000000..596f774
--- /dev/null
+++ b/res/Mix_2x4_Stereo.svg
@@ -0,0 +1,1645 @@
+
+
+
+
diff --git a/res/Mix_4x4_Stereo.svg b/res/Mix_4x4_Stereo.svg
new file mode 100644
index 0000000..37b9bd5
--- /dev/null
+++ b/res/Mix_4x4_Stereo.svg
@@ -0,0 +1,2356 @@
+
+
+
+
diff --git a/res/Mix_4x4_Stereo_old.svg b/res/Mix_4x4_Stereo_old.svg
new file mode 100644
index 0000000..cb31940
--- /dev/null
+++ b/res/Mix_4x4_Stereo_old.svg
@@ -0,0 +1,2207 @@
+
+
+
+
diff --git a/res/OSC3Channel.svg b/res/OSC3Channel.svg
new file mode 100644
index 0000000..7e26d9d
--- /dev/null
+++ b/res/OSC3Channel.svg
@@ -0,0 +1,1434 @@
+
+
+
+
diff --git a/res/PingPong.svg b/res/PingPong.svg
new file mode 100644
index 0000000..62950fb
--- /dev/null
+++ b/res/PingPong.svg
@@ -0,0 +1,629 @@
+
+
+
+
diff --git a/res/SEQ_6x32x16.svg b/res/SEQ_6x32x16.svg
new file mode 100644
index 0000000..4a7b6b4
--- /dev/null
+++ b/res/SEQ_6x32x16.svg
@@ -0,0 +1,2267 @@
+
+
+
+
diff --git a/res/SynthDrums.svg b/res/SynthDrums.svg
new file mode 100644
index 0000000..fed183a
--- /dev/null
+++ b/res/SynthDrums.svg
@@ -0,0 +1,1538 @@
+
+
+
+
diff --git a/res/TriadSequencer.svg b/res/TriadSequencer.svg
new file mode 100644
index 0000000..723d8fb
--- /dev/null
+++ b/res/TriadSequencer.svg
@@ -0,0 +1,1145 @@
+
+
+
+
diff --git a/res/TriadSequencer2.svg b/res/TriadSequencer2.svg
new file mode 100644
index 0000000..e039c49
--- /dev/null
+++ b/res/TriadSequencer2.svg
@@ -0,0 +1,1819 @@
+
+
+
+
diff --git a/res/XFade.svg b/res/XFade.svg
new file mode 100644
index 0000000..2a107a5
--- /dev/null
+++ b/res/XFade.svg
@@ -0,0 +1,548 @@
+
+
+
+
diff --git a/res/mschack_5p_filtersel_01.svg b/res/mschack_5p_filtersel_01.svg
new file mode 100644
index 0000000..5b987dc
--- /dev/null
+++ b/res/mschack_5p_filtersel_01.svg
@@ -0,0 +1,118 @@
+
+
+
+
diff --git a/res/mschack_5p_filtersel_02.svg b/res/mschack_5p_filtersel_02.svg
new file mode 100644
index 0000000..13638be
--- /dev/null
+++ b/res/mschack_5p_filtersel_02.svg
@@ -0,0 +1,114 @@
+
+
+
+
diff --git a/res/mschack_5p_filtersel_03.svg b/res/mschack_5p_filtersel_03.svg
new file mode 100644
index 0000000..4bb37ae
--- /dev/null
+++ b/res/mschack_5p_filtersel_03.svg
@@ -0,0 +1,114 @@
+
+
+
+
diff --git a/res/mschack_5p_filtersel_04.svg b/res/mschack_5p_filtersel_04.svg
new file mode 100644
index 0000000..be1fb24
--- /dev/null
+++ b/res/mschack_5p_filtersel_04.svg
@@ -0,0 +1,114 @@
+
+
+
+
diff --git a/res/mschack_5p_filtersel_05.svg b/res/mschack_5p_filtersel_05.svg
new file mode 100644
index 0000000..578ddd3
--- /dev/null
+++ b/res/mschack_5p_filtersel_05.svg
@@ -0,0 +1,126 @@
+
+
+
+
diff --git a/res/mschack_BlackKeyOff.svg b/res/mschack_BlackKeyOff.svg
new file mode 100644
index 0000000..f23110f
--- /dev/null
+++ b/res/mschack_BlackKeyOff.svg
@@ -0,0 +1,72 @@
+
+
+
+
diff --git a/res/mschack_BlackKeyOn.svg b/res/mschack_BlackKeyOn.svg
new file mode 100644
index 0000000..e70a3ba
--- /dev/null
+++ b/res/mschack_BlackKeyOn.svg
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/res/mschack_Blue1_small.svg b/res/mschack_Blue1_small.svg
new file mode 100644
index 0000000..c2cadf9
--- /dev/null
+++ b/res/mschack_Blue1_small.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_Blue2_big.svg b/res/mschack_Blue2_big.svg
new file mode 100644
index 0000000..5781e23
--- /dev/null
+++ b/res/mschack_Blue2_big.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_Blue2_small.svg b/res/mschack_Blue2_small.svg
new file mode 100644
index 0000000..5abc33e
--- /dev/null
+++ b/res/mschack_Blue2_small.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_Green1_small.svg b/res/mschack_Green1_small.svg
new file mode 100644
index 0000000..dd00554
--- /dev/null
+++ b/res/mschack_Green1_small.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_KnobBlue3.svg b/res/mschack_KnobBlue3.svg
new file mode 100644
index 0000000..773ea68
--- /dev/null
+++ b/res/mschack_KnobBlue3.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_KnobPurp1.svg b/res/mschack_KnobPurp1.svg
new file mode 100644
index 0000000..3e591ac
--- /dev/null
+++ b/res/mschack_KnobPurp1.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_KnobRed1.svg b/res/mschack_KnobRed1.svg
new file mode 100644
index 0000000..807a5fa
--- /dev/null
+++ b/res/mschack_KnobRed1.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_KnobYellow3.svg b/res/mschack_KnobYellow3.svg
new file mode 100644
index 0000000..dc43f2a
--- /dev/null
+++ b/res/mschack_KnobYellow3.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_WhiteKeyOff.svg b/res/mschack_WhiteKeyOff.svg
new file mode 100644
index 0000000..83a0ac3
--- /dev/null
+++ b/res/mschack_WhiteKeyOff.svg
@@ -0,0 +1,72 @@
+
+
+
+
diff --git a/res/mschack_WhiteKeyOn.svg b/res/mschack_WhiteKeyOn.svg
new file mode 100644
index 0000000..aaa3339
--- /dev/null
+++ b/res/mschack_WhiteKeyOn.svg
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/res/mschack_square_button2.svg b/res/mschack_square_button2.svg
new file mode 100644
index 0000000..1e7a4a4
--- /dev/null
+++ b/res/mschack_square_button2.svg
@@ -0,0 +1,70 @@
+
+
+
+
diff --git a/res/mschack_yellow1_small.svg b/res/mschack_yellow1_small.svg
new file mode 100644
index 0000000..ce6ed08
--- /dev/null
+++ b/res/mschack_yellow1_small.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/res/mschack_yellow2_small.svg b/res/mschack_yellow2_small.svg
new file mode 100644
index 0000000..223740d
--- /dev/null
+++ b/res/mschack_yellow2_small.svg
@@ -0,0 +1,73 @@
+
+
+
+
diff --git a/src/3Ch_Osc.cpp b/src/3Ch_Osc.cpp
new file mode 100644
index 0000000..6e6685b
--- /dev/null
+++ b/src/3Ch_Osc.cpp
@@ -0,0 +1,828 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define nCHANNELS 3
+#define CHANNEL_H 90
+#define CHANNEL_Y 65
+#define CHANNEL_X 10
+
+#define MAX_nWAVES 7
+#define MAX_DETUNE 20 //Hz
+
+#define freqMAX 300.0f
+#define ADS_MAX_TIME_SECONDS 0.5f
+
+#define WAVE_BUFFER_LEN ( 192000 / 20 ) // (9600) based on quality for 20Hz at max sample rate 192000
+
+typedef struct
+{
+ int state;
+ int a, d, r;
+ int acount, dcount, rcount, fadecount;
+ float fainc, frinc, fadeinc;
+ float out;
+ bool bTrig;
+}ADR_STRUCT;
+
+typedef struct
+{
+ int wavetype;
+ int filtertype;
+
+ // wave
+ float phase[ MAX_nWAVES ];
+ float freq[ MAX_nWAVES ];
+
+ //filter
+ float q, f;
+
+ float lp1[ 2 ] = {}, bp1[ 2 ] = {};
+
+ // ads
+ ADR_STRUCT adr_wave;
+
+}OSC_PARAM_STRUCT;
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct Osc_3Ch : Module
+{
+ enum WaveTypes
+ {
+ WAVE_SIN,
+ WAVE_TRI,
+ WAVE_SQR,
+ WAVE_SAW,
+ WAVE_NOISE,
+ nWAVEFORMS
+ };
+
+ enum ParamIds
+ {
+ PARAM_DELAY,
+ PARAM_ATT = PARAM_DELAY + nCHANNELS,
+ PARAM_REL = PARAM_ATT + nCHANNELS,
+ PARAM_REZ = PARAM_REL + nCHANNELS,
+ PARAM_WAVES = PARAM_REZ + nCHANNELS,
+ PARAM_CUTOFF = PARAM_WAVES + (nWAVEFORMS * nCHANNELS),
+ PARAM_RES = PARAM_CUTOFF + nCHANNELS,
+ PARAM_OUTLVL = PARAM_RES + nCHANNELS,
+ PARAM_FILTER_MODE = PARAM_OUTLVL + nCHANNELS,
+ PARAM_nWAVES = PARAM_FILTER_MODE + nCHANNELS,
+ PARAM_SPREAD = PARAM_nWAVES + nCHANNELS,
+ PARAM_DETUNE = PARAM_SPREAD + nCHANNELS,
+ nPARAMS = PARAM_DETUNE + nCHANNELS
+ };
+
+ enum InputIds
+ {
+ IN_VOCT,
+ IN_TRIG = IN_VOCT + nCHANNELS,
+ IN_FILTER = IN_TRIG + nCHANNELS,
+ IN_REZ = IN_FILTER + nCHANNELS,
+ IN_LEVEL = IN_REZ + nCHANNELS,
+ nINPUTS = IN_LEVEL + nCHANNELS,
+ };
+
+ enum OutputIds
+ {
+ OUTPUT_AUDIO,
+ nOUTPUTS = OUTPUT_AUDIO + (nCHANNELS * 2)
+ };
+
+ enum ADRSTATES
+ {
+ ADR_OFF,
+ ADR_FADE,
+ ADR_WAIT_PHASE,
+ ADR_FADE_OUT,
+ ADR_ATTACK,
+ ADR_DELAY,
+ ADR_RELEASE
+ };
+
+ enum FILTER_TYPES
+ {
+ FILTER_OFF,
+ FILTER_LP,
+ FILTER_HP,
+ FILTER_BP,
+ FILTER_NT
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ SchmittTrigger m_SchTrig[ nCHANNELS ];
+
+ OSC_PARAM_STRUCT m_Wave[ nCHANNELS ] = {};
+
+ // waveforms
+ float m_BufferWave[ nWAVEFORMS ][ WAVE_BUFFER_LEN ] = {};
+
+ float m_DetuneIn[ nCHANNELS ] = {};
+ float m_Detune[ nCHANNELS ][ MAX_nWAVES ][ MAX_nWAVES ];
+
+ float m_SpreadIn[ nCHANNELS ] = {};
+ float m_Pan[ nCHANNELS ][ MAX_nWAVES ][ MAX_nWAVES ][ 2 ];
+
+ int m_nWaves[ nCHANNELS ] = {};
+
+ MyLEDButtonStrip *m_pButtonWaveSelect[ nCHANNELS ] = {};
+
+ // Contructor
+ Osc_3Ch() : Module( nPARAMS, nINPUTS, nOUTPUTS, 0 ){}
+
+ //-----------------------------------------------------
+ // MynWaves_Knob
+ //-----------------------------------------------------
+ struct MynWaves_Knob : Yellow3_Med_Snap
+ {
+ Osc_3Ch *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Osc_3Ch*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Osc_3Ch::PARAM_nWAVES;
+ mymodule->m_nWaves[ param ] = (int)( value );
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyDetune_Knob : Yellow3_Med
+ {
+ Osc_3Ch *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Osc_3Ch*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Osc_3Ch::PARAM_DETUNE;
+
+ mymodule->m_DetuneIn[ param ] = value;
+ mymodule->CalcDetune( param );
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MySpread_Knob : Yellow3_Med
+ {
+ Osc_3Ch *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Osc_3Ch*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Osc_3Ch::PARAM_SPREAD;
+
+ mymodule->m_SpreadIn[ param ] = value;
+ mymodule->CalcSpread( param );
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override;
+ void reset() override;
+
+ void CalcSpread( int ch );
+ void CalcDetune( int ch );
+ void SetWaveLights( void );
+ void BuildWaves( void );
+ void ChangeFilterCutoff( int ch, float cutfreq );
+ void Filter( int ch, float *InL, float *InR );
+ float GetWave( int type, float phase );
+ float ProcessADR( int ch );
+ void GetAudio( int ch, float *pOutL, float *pOutR );
+};
+
+//-----------------------------------------------------
+// Osc_3Ch_WaveSelect
+//-----------------------------------------------------
+void Osc_3Ch_WaveSelect( void *pClass, int id, int nbutton, bool bOn )
+{
+ Osc_3Ch *mymodule;
+ mymodule = (Osc_3Ch*)pClass;
+ mymodule->m_Wave[ id ].wavetype = nbutton;
+}
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+struct Osc_3Ch_Widget : ModuleWidget {
+ Osc_3Ch_Widget(Osc_3Ch *module) : ModuleWidget(module)
+{
+ int ch, x, y, x2, y2;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/OSC3Channel.svg")));
+
+ //module->lg.Open("OSC3Channel.txt");
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ y = CHANNEL_Y;
+
+ for( ch = 0; ch < nCHANNELS; ch++ )
+ {
+ x = CHANNEL_X;
+
+ // inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Osc_3Ch::IN_VOCT + ch ) );
+ addChild(Port::create( Vec( x, y + 43 ), Port::INPUT, module, Osc_3Ch::IN_TRIG + ch ) );
+
+ x2 = x + 32;
+ y2 = y + 52;
+
+ module->m_pButtonWaveSelect[ ch ] = new MyLEDButtonStrip( x2, y2, 11, 11, 5, 8.0, 5, false, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, ch, module, Osc_3Ch_WaveSelect );
+ addChild( module->m_pButtonWaveSelect[ ch ] );
+
+ x2 = x + 24;
+ y2 = y + 18;
+
+ // params
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Osc_3Ch::PARAM_ATT + ch, 0.0, 1.0, 0.0 ) );
+
+ x2 += 31;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Osc_3Ch::PARAM_DELAY + ch, 0.0, 1.0, 0.0 ) );
+
+ x2 += 31;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Osc_3Ch::PARAM_REL + ch, 0.0, 1.0, 0.0 ) );
+
+ // waves/detune/spread
+ addParam(ParamWidget::create( Vec( x + 129, y + 11 ), module, Osc_3Ch::PARAM_nWAVES + ch, 0.0, 6.0, 0.0 ) );
+ addParam(ParamWidget::create( Vec( x + 116, y + 48 ), module, Osc_3Ch::PARAM_DETUNE + ch, 0.0, 0.05, 0.0 ) );
+ addParam(ParamWidget::create( Vec( x + 116 + 28, y + 48 ), module, Osc_3Ch::PARAM_SPREAD + ch, 0.0, 1.0, 0.0 ) );
+
+ // inputs
+ x2 = x + 178;
+ y2 = y + 51;
+
+ addChild(Port::create( Vec( x2, y2 ), Port::INPUT, module, Osc_3Ch::IN_FILTER + ch ) );
+ x2 += 36;
+ addChild(Port::create( Vec( x2, y2 ), Port::INPUT, module, Osc_3Ch::IN_REZ + ch ) );
+ x2 += 40;
+ addChild(Port::create( Vec( x2, y2 ), Port::INPUT, module, Osc_3Ch::IN_LEVEL + ch ) );
+
+ // filter
+ y2 = y + 6;
+ x2 = x + 167;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Osc_3Ch::PARAM_CUTOFF + ch, 0.0, 0.1, 0.0 ) );
+ addParam(ParamWidget::create( Vec( x2 + 43, y2 + 2 ), module, Osc_3Ch::PARAM_FILTER_MODE + ch, 0.0, 4.0, 0.0 ) );
+ addParam(ParamWidget::create( Vec( x2 + 46, y2 + 20 ), module, Osc_3Ch::PARAM_RES + ch, 0.0, 1.0, 0.0 ) );
+
+ // main level
+ addParam(ParamWidget::create( Vec( x2 + 76, y2 ), module, Osc_3Ch::PARAM_OUTLVL + ch, 0.0, 1.0, 0.0 ) );
+
+ // outputs
+ addChild(Port::create( Vec( x + 283, y + 4 ), Port::OUTPUT, module, Osc_3Ch::OUTPUT_AUDIO + (ch * 2) ) );
+ addChild(Port::create( Vec( x + 283, y + 53 ), Port::OUTPUT, module, Osc_3Ch::OUTPUT_AUDIO + (ch * 2) + 1 ) );
+
+ y += CHANNEL_H;
+ module->m_nWaves[ ch ] = 0;
+ }
+
+ module->m_bInitialized = true;
+
+ module->BuildWaves();
+ module->SetWaveLights();
+}
+};
+
+Model *modelOsc_3Ch_Widget = Model::create( "mscHack", "Osc_3Ch_Widget", "OSC 3 Channel", SYNTH_VOICE_TAG, OSCILLATOR_TAG, MULTIPLE_TAG );
+
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *Osc_3Ch::toJson()
+{
+ json_t *gatesJ;
+ json_t *rootJ = json_object();
+
+ // wavetypes
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( m_Wave[ i ].wavetype );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "wavetypes", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void Osc_3Ch::fromJson(json_t *rootJ)
+{
+ int i;
+ json_t *StepsJ;
+
+ // wave select
+ StepsJ = json_object_get( rootJ, "wavetypes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ m_Wave[ i ].wavetype = json_integer_value( gateJ );
+ }
+ }
+
+ // set up parameters
+ for ( i = 0; i < nCHANNELS; i++)
+ {
+ m_nWaves[ i ] = (int)( params[ PARAM_nWAVES + i ].value );
+
+ m_SpreadIn[ i ] = params[ PARAM_SPREAD + i ].value;
+ CalcSpread( i );
+ m_DetuneIn[ i ] = params[ PARAM_DETUNE + i ].value;
+ CalcDetune( i );
+ }
+
+ SetWaveLights();
+}
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void Osc_3Ch::reset()
+{
+}
+
+//-----------------------------------------------------
+// Procedure: randomize
+//
+//-----------------------------------------------------
+void Osc_3Ch::randomize()
+{
+ int ch;
+
+ for( ch = 0; ch < nCHANNELS; ch++ )
+ {
+ m_Wave[ ch ].wavetype = (int)( randomUniform() * (nWAVEFORMS-1) );
+ }
+
+ SetWaveLights();
+}
+
+//-----------------------------------------------------
+// Procedure: SetWaveLights
+//
+//-----------------------------------------------------
+void Osc_3Ch::SetWaveLights( void )
+{
+ int ch;
+
+ for( ch = 0; ch < nCHANNELS; ch++ )
+ m_pButtonWaveSelect[ ch ]->Set( m_Wave[ ch ].wavetype, true );
+}
+
+//-----------------------------------------------------
+// Procedure: initialize
+//
+//-----------------------------------------------------
+#define DEG2RAD( x ) ( ( x ) * ( 3.14159f / 180.0f ) )
+void Osc_3Ch::BuildWaves( void )
+{
+ int i;
+ float finc, pos, val;
+
+ finc = 360.0 / WAVE_BUFFER_LEN;
+ pos = 0;
+
+ // create sin wave
+ for( i = 0; i < WAVE_BUFFER_LEN; i++ )
+ {
+ m_BufferWave[ WAVE_SIN ][ i ] = sin( DEG2RAD( pos ) );
+ pos += finc;
+ }
+
+ // create sqr wave
+ for( i = 0; i < WAVE_BUFFER_LEN; i++ )
+ {
+ if( i < WAVE_BUFFER_LEN / 2 )
+ m_BufferWave[ WAVE_SQR ][ i ] = 1.0;
+ else
+ m_BufferWave[ WAVE_SQR ][ i ] = -1.0;
+ }
+
+ finc = 2.0 / (float)WAVE_BUFFER_LEN;
+ val = 1.0;
+
+ // create saw wave
+ for( i = 0; i < WAVE_BUFFER_LEN; i++ )
+ {
+ m_BufferWave[ WAVE_SAW ][ i ] = val;
+
+ val -= finc;
+ }
+
+ finc = 4 / (float)WAVE_BUFFER_LEN;
+ val = 0;
+
+ // create tri wave
+ for( i = 0; i < WAVE_BUFFER_LEN; i++ )
+ {
+ m_BufferWave[ WAVE_TRI ][ i ] = val;
+
+ if( i < WAVE_BUFFER_LEN / 4 )
+ val += finc;
+ else if( i < (WAVE_BUFFER_LEN / 4) * 3 )
+ val -= finc;
+ else if( i < WAVE_BUFFER_LEN )
+ val += finc;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: GetAudio
+//
+//-----------------------------------------------------
+float Osc_3Ch::GetWave( int type, float phase )
+{
+ float fval = 0.0;
+ float ratio = (float)(WAVE_BUFFER_LEN-1) / engineGetSampleRate();
+
+ switch( type )
+ {
+ case WAVE_SIN:
+ case WAVE_TRI:
+ case WAVE_SQR:
+ case WAVE_SAW:
+ fval = m_BufferWave[ type ][ int( ( phase * ratio ) + 0.5 ) ];
+ break;
+
+ case WAVE_NOISE:
+ fval = ( randomUniform() > 0.5 ) ? (randomUniform() * -1.0) : randomUniform();
+ break;
+
+ default:
+ break;
+ }
+
+ return fval;
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessADS
+//
+//-----------------------------------------------------
+float Osc_3Ch::ProcessADR( int ch )
+{
+ ADR_STRUCT *padr;
+
+ padr = &m_Wave[ ch ].adr_wave;
+
+ // retrig the adsr
+ if( padr->bTrig )
+ {
+ padr->state = ADR_FADE;
+
+ padr->fadecount = 900;
+ padr->fadeinc = padr->out / (float)padr->fadecount;
+
+ padr->acount = 40 + (int)( params[ PARAM_ATT + ch ].value * 2.0f * engineGetSampleRate() );
+ padr->fainc = 1.0f / (float)padr->acount;
+
+ padr->dcount = (int)( params[ PARAM_DELAY + ch ].value * 4.0f * engineGetSampleRate() );
+
+ padr->rcount = 20 + (int)( params[ PARAM_REL + ch ].value * 10.0f * engineGetSampleRate() );
+ padr->frinc = 1.0f / (float)padr->rcount;
+
+ padr->bTrig = false;
+ }
+
+ // process
+ switch( padr->state )
+ {
+ case ADR_FADE:
+ if( --padr->fadecount <= 0 )
+ {
+ padr->state = ADR_ATTACK;
+ padr->out = 0.0f;
+ m_Wave[ ch ].phase[ 0 ] = 0.0f;
+ m_Wave[ ch ].phase[ 1 ] = 0.0f;
+ m_Wave[ ch ].phase[ 2 ] = 0.0f;
+ m_Wave[ ch ].phase[ 3 ] = 0.0f;
+ m_Wave[ ch ].phase[ 4 ] = 0.0f;
+ m_Wave[ ch ].phase[ 5 ] = 0.0f;
+ m_Wave[ ch ].phase[ 6 ] = 0.0f;
+ }
+ else
+ {
+ padr->out -= padr->fadeinc;
+ }
+
+ break;
+
+ case ADR_OFF:
+ padr->out = 0.0f;
+ break;
+
+ case ADR_ATTACK:
+
+ if( --padr->acount <= 0 )
+ {
+ padr->state = ADR_DELAY;
+ }
+ else
+ {
+ padr->out += padr->fainc;
+ }
+
+ break;
+
+ case ADR_DELAY:
+ padr->out = 1.0f;
+ if( --padr->dcount <= 0 )
+ {
+ padr->state = ADR_RELEASE;
+ }
+ break;
+
+ case ADR_RELEASE:
+
+ if( --padr->rcount <= 0 )
+ {
+ padr->state = ADR_OFF;
+ padr->out = 0.0f;
+ }
+ else
+ {
+ padr->out -= padr->frinc;
+ }
+
+ break;
+ }
+
+ return clamp( padr->out, 0.0f, 1.0f );
+}
+
+//-----------------------------------------------------
+// Procedure: ChangeFilterCutoff
+//
+//-----------------------------------------------------
+void Osc_3Ch::ChangeFilterCutoff( int ch, float cutfreq )
+{
+ float fx, fx2, fx3, fx5, fx7;
+
+ // clamp at 1.0 and 20/samplerate
+ cutfreq = fmax(cutfreq, 20 / engineGetSampleRate());
+ cutfreq = fmin(cutfreq, 1.0);
+
+ // calculate eq rez freq
+ fx = 3.141592 * (cutfreq * 0.026315789473684210526315789473684) * 2 * 3.141592;
+ fx2 = fx*fx;
+ fx3 = fx2*fx;
+ fx5 = fx3*fx2;
+ fx7 = fx5*fx2;
+
+ m_Wave[ ch ].f = 2.0 * (fx
+ - (fx3 * 0.16666666666666666666666666666667)
+ + (fx5 * 0.0083333333333333333333333333333333)
+ - (fx7 * 0.0001984126984126984126984126984127));
+}
+
+//-----------------------------------------------------
+// Procedure: Filter
+//
+//-----------------------------------------------------
+#define MULTI (0.33333333333333333333333333333333f)
+void Osc_3Ch::Filter( int ch, float *InL, float *InR )
+{
+ OSC_PARAM_STRUCT *p;
+ float rez, hp1;
+ float input[ 2 ], out[ 2 ], lowpass, bandpass, highpass;
+
+ if( (int)params[ PARAM_FILTER_MODE + ch ].value == 0 )
+ return;
+
+ p = &m_Wave[ ch ];
+
+ rez = 1.0 - params[ PARAM_RES + ch ].value;
+
+ input[ 0 ] = *InL;
+ input[ 1 ] = *InR;
+
+ // do left and right channels
+ for( int i = 0; i < 2; i++ )
+ {
+ input[ i ] = input[ i ] + 0.000000001;
+
+ p->lp1[ i ] = p->lp1[ i ] + p->f * p->bp1[ i ];
+ hp1 = input[ i ] - p->lp1[ i ] - rez * p->bp1[ i ];
+ p->bp1[ i ] = p->f * hp1 + p->bp1[ i ];
+ lowpass = p->lp1[ i ];
+ highpass = hp1;
+ bandpass = p->bp1[ i ];
+
+ p->lp1[ i ] = p->lp1[ i ] + p->f * p->bp1[ i ];
+ hp1 = input[ i ] - p->lp1[ i ] - rez * p->bp1[ i ];
+ p->bp1[ i ] = p->f * hp1 + p->bp1[ i ];
+ lowpass = lowpass + p->lp1[ i ];
+ highpass = highpass + hp1;
+ bandpass = bandpass + p->bp1[ i ];
+
+ input[ i ] = input[ i ] - 0.000000001;
+
+ p->lp1[ i ] = p->lp1[ i ] + p->f * p->bp1[ i ];
+ hp1 = input[ i ] - p->lp1[ i ] - rez * p->bp1[ i ];
+ p->bp1[ i ] = p->f * hp1 + p->bp1[ i ];
+
+ lowpass = (lowpass + p->lp1[ i ]) * MULTI;
+ highpass = (highpass + hp1) * MULTI;
+ bandpass = (bandpass + p->bp1[ i ]) * MULTI;
+
+ switch( (int)params[ PARAM_FILTER_MODE + ch ].value )
+ {
+ case FILTER_LP:
+ out[ i ] = lowpass;
+ break;
+ case FILTER_HP:
+ out[ i ] = highpass;
+ break;
+ case FILTER_BP:
+ out[ i ] = bandpass;
+ break;
+ case FILTER_NT:
+ out[ i ] = lowpass + highpass;
+ break;
+ default:
+ out[ i ] = 0;
+ break;
+ }
+ }
+
+ *InL = out[ 0 ];
+ *InR = out[ 1 ];
+}
+
+//-----------------------------------------------------
+// Procedure: CalcSpread
+//
+//-----------------------------------------------------
+typedef struct
+{
+ float pan[ 2 ];
+ float maxdetune;
+}PAN_DETUNE;
+
+PAN_DETUNE pandet[ 7 ][ 7 ] =
+{
+ { { {1.0, 1.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 } },
+ { { {1.0, 0.5}, 0.1 }, { {0.5, 1.0}, 0.2 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 } },
+ { { {1.0, 0.5}, 0.3 }, { {1.0, 1.0}, 0.0 }, { {0.5, 1.0}, 0.2 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 } },
+ { { {1.0, 0.3}, 0.4 }, { {1.0, 0.5}, 0.2 }, { {0.5, 1.0}, 0.2 }, { {0.3, 1.0}, 0.3 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 } },
+ { { {1.0, 0.3}, 0.5 }, { {1.0, 0.5}, 0.4 }, { {1.0, 1.0}, 0.0 }, { {0.5, 1.0}, 0.3 }, { {0.3, 1.0}, 0.1 }, { {0.0, 0.0}, 0.0 }, { {0.0, 0.0}, 0.0 } },
+ { { {1.0, 0.2}, 0.6 }, { {1.0, 0.3}, 0.4 }, { {1.0, 0.5}, 0.2 }, { {0.5, 1.0}, 0.3 }, { {0.3, 1.0}, 0.5 }, { {0.2, 1.0}, 0.8 }, { {0.0, 0.0}, 0.0 } },
+ { { {1.0, 0.0}, 0.9 }, { {1.0, 0.2}, 0.7 }, { {1.0, 0.3}, 0.5 }, { {1.0, 1.0}, 0.0 }, { {0.3, 1.0}, 0.4 }, { {0.2, 1.0}, 0.8 }, { {0.0, 1.0}, 1.0 } },
+};
+
+void Osc_3Ch::CalcSpread( int ch )
+{
+ int used; // number of waves being used by channel
+ int wave; // values for each individual wave
+
+ // calculate pans for each possible number of waves being used
+ for( used = 0; used < MAX_nWAVES; used++ )
+ {
+ for( wave = 0; wave <= used; wave++ )
+ {
+ m_Pan[ ch ][ used ][ wave ][ 0 ] = ( 1.0 - m_SpreadIn[ ch ] ) + ( pandet[ used ][ wave ].pan[ 0 ] * m_SpreadIn[ ch ] );
+ m_Pan[ ch ][ used ][ wave ][ 1 ] = ( 1.0 - m_SpreadIn[ ch ] ) + ( pandet[ used ][ wave ].pan[ 1 ] * m_SpreadIn[ ch ] );
+ }
+ }
+}
+
+void Osc_3Ch::CalcDetune( int ch )
+{
+ int used; // number of waves being used by channel
+ int wave; // values for each individual wave
+
+ // calculate detunes for each possible number of waves being used
+ for( used = 0; used < MAX_nWAVES; used++ )
+ {
+ for( wave = 0; wave <= used; wave++ )
+ m_Detune[ ch ][ used ][ wave ] = pandet[ used ][ wave ].maxdetune * MAX_DETUNE * m_DetuneIn[ ch ];
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: GetAudio
+//
+//-----------------------------------------------------
+void Osc_3Ch::GetAudio( int ch, float *pOutL, float *pOutR )
+{
+ float foutL = 0, foutR = 0, cutoff, adr;
+ int i;
+
+ for( i = 0; i <= m_nWaves[ ch ]; i++ )
+ {
+ foutL = GetWave( m_Wave[ ch ].wavetype, m_Wave[ ch ].phase[ i ] );
+ foutR = foutL;
+
+ foutL *= m_Pan[ ch ][ m_nWaves[ ch ] ][ i ][ 0 ];
+ foutR *= m_Pan[ ch ][ m_nWaves[ ch ] ][ i ][ 1 ];
+
+ // ( 32.7032 is C1 ) ( 4186.01 is C8)
+ m_Wave[ ch ].phase[ i ] += 32.7032f * clamp( powf( 2.0f, clamp( inputs[ IN_VOCT + ch ].value, 0.0f, VOCT_MAX ) ) + m_Detune[ ch ][ m_nWaves[ ch ] ][ i ], 0.0, 4186.01f );
+
+ if( m_Wave[ ch ].phase[ i ] >= engineGetSampleRate() )
+ m_Wave[ ch ].phase[ i ] = m_Wave[ ch ].phase[ i ] - engineGetSampleRate();
+
+ *pOutL += foutL;
+ *pOutR += foutR;
+ }
+
+ adr = ProcessADR( ch );
+
+ *pOutL = *pOutL * adr;
+ *pOutR = *pOutR * adr;
+
+ cutoff = clamp( params[ PARAM_CUTOFF + ch ].value * ( inputs[ IN_FILTER + ch ].normalize( CV_MAX ) / CV_MAX ), 0.0, 1.0 );
+
+ ChangeFilterCutoff( ch, cutoff );
+
+ Filter( ch, pOutL, pOutR );
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void Osc_3Ch::step()
+{
+ int ch;
+ float outL, outR;
+
+ if( !m_bInitialized )
+ return;
+
+ // check for triggers
+ for( ch = 0; ch < nCHANNELS; ch++ )
+ {
+ outL = 0.0;
+ outR = 0.0;
+
+ if( inputs[ IN_TRIG + ch ].active )
+ {
+ if( m_SchTrig[ ch ].process( inputs[ IN_TRIG + ch ].value ) )
+ {
+ m_Wave[ ch ].adr_wave.bTrig = true;
+ }
+ }
+
+ GetAudio( ch, &outL, &outR );
+
+ outL = clamp( ( outL * AUDIO_MAX ) * params[ PARAM_OUTLVL + ch ].value * ( inputs[ IN_LEVEL + ch ].normalize( CV_MAX ) / CV_MAX ), -AUDIO_MAX, AUDIO_MAX );
+ outR = clamp( ( outR * AUDIO_MAX ) * params[ PARAM_OUTLVL + ch ].value * ( inputs[ IN_LEVEL + ch ].normalize( CV_MAX ) / CV_MAX ), -AUDIO_MAX, AUDIO_MAX );
+
+ outputs[ OUTPUT_AUDIO + (ch * 2 ) ].value = outL;
+ outputs[ OUTPUT_AUDIO + (ch * 2 ) + 1 ].value = outR;
+ }
+}
diff --git a/src/ARP700.cpp b/src/ARP700.cpp
new file mode 100644
index 0000000..87af41c
--- /dev/null
+++ b/src/ARP700.cpp
@@ -0,0 +1,865 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define MAX_ARP_PATTERNS 16
+#define MAX_ARP_NOTES 7
+#define SUBSTEP_PER_NOTE 3
+
+#define SEMI ( 1.0f / 12.0f )
+#define TRIG_OFF_TICKS 10
+
+#define ARP_OFF 0
+#define ARP_ON 1
+#define ARP_REST 2
+
+typedef struct
+{
+ int notesused;
+ int notes [ MAX_ARP_NOTES ];
+ int onoffsel[ MAX_ARP_NOTES ][ SUBSTEP_PER_NOTE ];
+ int lensel [ MAX_ARP_NOTES ][ SUBSTEP_PER_NOTE ];
+ int lenmod [ MAX_ARP_NOTES ][ SUBSTEP_PER_NOTE ];
+ int legato [ MAX_ARP_NOTES ];
+ int glide [ MAX_ARP_NOTES ];
+ int mode;
+ int oct;
+
+}ARP_PATTERN_STRUCT;
+
+typedef struct
+{
+ bool bPending;
+ int pat;
+}ARP_PHRASE_CHANGE_STRUCT;
+
+//------------------------------------------------------
+// sliding window average
+#define AVG_ARRAY_LEN 4
+#define AVG_AND (AVG_ARRAY_LEN - 1)
+
+typedef struct
+{
+ int count;
+ int avg[ AVG_ARRAY_LEN ];
+ int tot;
+}SLIDING_AVG_STRUCT;
+
+typedef struct
+{
+ // track in clk bpm
+ SLIDING_AVG_STRUCT Avg;
+ int tickcount;
+ float ftickspersec;
+ float fbpm;
+
+ // track sync tick
+ float fsynclen;
+ float fsynccount;
+
+ bool bClockReset;
+
+}MAIN_SYNC_CLOCK;
+
+typedef struct
+{
+ // timing
+ bool bTrig;
+ int pat;
+ int used;
+
+ int step;
+ int virtstep;
+
+ ARP_PHRASE_CHANGE_STRUCT pending;
+
+ // track current arp trig
+ int nextcount;
+ bool bNextTrig;
+
+ // glide
+ float fglideInc;
+ int glideCount;
+ float fglide;
+ float fLastNotePlayed;
+ bool bWasLastNotePlayed;
+
+ // voct out
+ float fCvStartOut = 0;
+ float fCvEndOut = 0;
+
+}PAT_STEP_STRUCT;
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct ARP700 : Module
+{
+ enum ParamIds
+ {
+ nPARAMS
+ };
+
+ enum InputIds
+ {
+ IN_CLOCK_TRIG,
+ IN_VOCT_OFF,
+ IN_PROG_CHANGE,
+ IN_CLOCK_RESET,
+ nINPUTS
+ };
+
+ enum OutputIds
+ {
+ OUT_TRIG,
+ OUT_VOCTS,
+ nOUTPUTS
+ };
+
+ enum NoteStates
+ {
+ STATE_NOTE_OFF,
+ STATE_NOTE_ON,
+ STATE_NOTE_REST,
+ STATE_TRIG_OFF
+ };
+
+ CLog lg;
+ bool m_bInitialized = false;
+
+ // Contructor
+ ARP700() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0){}
+
+ // pattern
+ ARP_PATTERN_STRUCT m_PatternSave[ MAX_ARP_PATTERNS ] = {};
+ PAT_STEP_STRUCT m_PatCtrl = {};
+
+ SchmittTrigger m_SchTrigPatternChange;
+ PatternSelectStrip *m_pPatternSelect = NULL;
+
+ // pattern buttons
+ MyLEDButtonStrip *m_pButtonOnOff [ MAX_ARP_NOTES ][ SUBSTEP_PER_NOTE ] = {};
+ MyLEDButtonStrip *m_pButtonLen [ MAX_ARP_NOTES ][ SUBSTEP_PER_NOTE ] = {};
+ MyLEDButtonStrip *m_pButtonLenMod[ MAX_ARP_NOTES ][ SUBSTEP_PER_NOTE ] = {};
+ MyLEDButton *m_pButtonGlide [ MAX_ARP_NOTES ] = {};
+ MyLEDButton *m_pButtonTrig [ MAX_ARP_NOTES ] = {};
+ MyLEDButtonStrip *m_plastbut = NULL;
+
+ // clock
+ SchmittTrigger m_SchTrigClk;
+ MAIN_SYNC_CLOCK m_Clock;
+
+ // global triggers
+ SchmittTrigger m_SchTrigGlobalClkReset;
+ bool m_GlobalClkResetPending = false;
+
+ // keyboard
+ Keyboard_3Oct_Widget *pKeyboardWidget = NULL;
+ float m_fKeyNotes[ 37 ];
+ float m_VoctOffsetIn = 0;
+
+ // octave
+ MyLEDButtonStrip *m_pButtonOctaveSelect = NULL;
+
+ // pause
+ bool m_bPauseState = false;
+ MyLEDButton *m_pButtonPause;
+
+ // mode
+ MyLEDButtonStrip *m_pButtonMode = 0;
+
+ // Overrides
+ void step() override;
+ void JsonParams( bool bTo, json_t *root);
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override;
+ void reset() override;
+
+ void SetPatternSteps( int nSteps );
+ void SetOut( void );
+ void ChangePattern( int index, bool bForce );
+ void SetPendingPattern( int phrase );
+ void ArpStep( bool bReset );
+};
+
+//-----------------------------------------------------
+// Procedure: ARP700_ModeSelect
+//-----------------------------------------------------
+void ARP700_ModeSelect( void *pClass, int id, int nbutton, bool bOn )
+{
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].mode = nbutton;
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_NoteOnOff
+//-----------------------------------------------------
+void ARP700_NoteOnOff( void *pClass, int id, int nbutton, bool bOn )
+{
+ int note, param;
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+
+ note = id / SUBSTEP_PER_NOTE;
+ param = id - ( SUBSTEP_PER_NOTE * note );
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].onoffsel[ note ][ param ] = nbutton;
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_NoteLenSelect
+//-----------------------------------------------------
+void ARP700_NoteLenSelect( void *pClass, int id, int nbutton, bool bOn )
+{
+ int note, param;
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+
+ note = id / SUBSTEP_PER_NOTE;
+ param = id - ( SUBSTEP_PER_NOTE * note );
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].lensel[ note ][ param ] = nbutton;
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_OctSelect
+//-----------------------------------------------------
+void ARP700_OctSelect( void *pClass, int id, int nbutton, bool bOn )
+{
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].oct = nbutton;
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_mod
+//-----------------------------------------------------
+void ARP700_mod( void *pClass, int id, int nbutton, bool bOn )
+{
+ int note, param;
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+
+ note = id / SUBSTEP_PER_NOTE;
+ param = id - ( SUBSTEP_PER_NOTE * note );
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].lenmod[ note ][ param ] = nbutton;
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_Pause
+//-----------------------------------------------------
+void ARP700_Pause( void *pClass, int id, bool bOn )
+{
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+ mymodule->m_bPauseState = bOn;
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_Glide
+//-----------------------------------------------------
+void ARP700_Glide( void *pClass, int id, bool bOn )
+{
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].glide[ id ] = bOn;
+ //mymodule->lg.f("Glide[ %d ] = %d\n", id, bOn );
+}
+
+//-----------------------------------------------------
+// Procedure: ARP700_Trig
+//-----------------------------------------------------
+void ARP700_Trig( void *pClass, int id, bool bOn )
+{
+ ARP700 *mymodule;
+ mymodule = (ARP700*)pClass;
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].legato[ id ] = bOn;
+ //mymodule->lg.f("Legato[ %d ] = %d\n", id, bOn );
+}
+
+//-----------------------------------------------------
+// Procedure: NoteChangeCallback
+//
+//-----------------------------------------------------
+void ARP700_Widget_NoteChangeCallback ( void *pClass, int kb, int notepressed, int *pnotes, bool bOn )
+{
+ ARP700 *mymodule = (ARP700 *)pClass;
+
+ if( !pClass )
+ return;
+
+ memcpy( mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].notes, pnotes, sizeof( int ) * MAX_ARP_NOTES );
+ mymodule->m_PatternSave[ mymodule->m_PatCtrl.pat ].notesused = mymodule->pKeyboardWidget->m_nKeysOn;
+}
+
+//-----------------------------------------------------
+// Procedure: PatternChangeCallback
+//
+//-----------------------------------------------------
+void ARP700_Widget_PatternChangeCallback ( void *pClass, int kb, int pat, int max )
+{
+ ARP700 *mymodule = (ARP700 *)pClass;
+
+ if( !mymodule || !mymodule->m_bInitialized )
+ return;
+
+ if( mymodule->m_PatCtrl.pat != pat )
+ {
+ if( !mymodule->m_bPauseState && mymodule->inputs[ ARP700::IN_CLOCK_TRIG ].active )
+ mymodule->SetPendingPattern( pat );
+ else
+ mymodule->ChangePattern( pat, false );
+
+ }
+ else if( mymodule->m_PatCtrl.used != max )
+ mymodule->SetPatternSteps( max );
+}
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+struct ARP700_Widget : ModuleWidget {
+ ARP700_Widget(ARP700 *module) : ModuleWidget(module)
+{
+ int x, y, note, param;
+// ##HS endIntro }
+
+ for( int i = 0; i < 37; i++ )
+ module->m_fKeyNotes[ i ] = (float)i * SEMI;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/ARP700.svg")));
+
+ //module->lg.Open("ARP700.txt");
+
+ // pause button
+ module->m_pButtonPause = new MyLEDButton( 75, 22, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, 0, module, ARP700_Pause );
+ addChild( module->m_pButtonPause );
+
+ // keyboard widget
+ module->pKeyboardWidget = new Keyboard_3Oct_Widget( 75, 38, MAX_ARP_NOTES, 0, DWRGB( 255, 128, 64 ), module, ARP700_Widget_NoteChangeCallback, &module->lg );
+ addChild( module->pKeyboardWidget );
+
+ // octave select
+ module->m_pButtonOctaveSelect = new MyLEDButtonStrip( 307, 104, 11, 11, 3, 8.0, 4, false, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, 0, module, ARP700_OctSelect );
+ addChild( module->m_pButtonOctaveSelect );
+
+ // pattern selects
+ module->m_pPatternSelect = new PatternSelectStrip( 75, 104, 9, 7, DWRGB( 200, 200, 200 ), DWRGB( 40, 40, 40 ), DWRGB( 112, 104, 102 ), DWRGB( 40, 40, 40 ), MAX_ARP_PATTERNS, 0, module, ARP700_Widget_PatternChangeCallback );
+ addChild( module->m_pPatternSelect );
+
+ x = 60;
+
+ for( note = 0; note < MAX_ARP_NOTES; note++ )
+ {
+ for( param = 0; param < SUBSTEP_PER_NOTE; param++ )
+ {
+ y = 140;
+
+ module->m_pButtonOnOff[ note ][ param ] = new MyLEDButtonStrip( x, y, 12, 12, 2, 10.0, 3, true, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, (note * SUBSTEP_PER_NOTE ) + param, module, ARP700_NoteOnOff );
+ addChild( module->m_pButtonOnOff[ note ][ param ] );
+
+ module->m_pButtonOnOff[ note ][ param ]->SetLEDCol( 1, DWRGB( 0, 255, 0 ) );
+ module->m_pButtonOnOff[ note ][ param ]->SetLEDCol( 2, DWRGB( 255, 255, 0 ) );
+
+ y += 43;
+
+ module->m_pButtonLen[ note ][ param ] = new MyLEDButtonStrip( x, y, 12, 12, 2, 10.0, 6, true, DWRGB( 180, 180, 180 ), DWRGB( 255, 128, 0 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, (note * SUBSTEP_PER_NOTE ) + param, module, ARP700_NoteLenSelect );
+ addChild( module->m_pButtonLen[ note ][ param ] );
+
+ y += 89;
+
+ module->m_pButtonLenMod[ note ][ param ] = new MyLEDButtonStrip( x, y, 12, 12, 2, 10.0, 3, true, DWRGB( 180, 180, 180 ), DWRGB( 255, 128, 0 ), MyLEDButtonStrip::TYPE_EXCLUSIVE_WOFF, (note * SUBSTEP_PER_NOTE ) + param, module, ARP700_mod );
+ addChild( module->m_pButtonLenMod[ note ][ param ] );
+
+ if( param == 1 )
+ {
+ y += 43;
+
+ module->m_pButtonGlide[ note ] = new MyLEDButton( x, y, 12, 12, 10.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, note, module, ARP700_Glide );
+ addChild( module->m_pButtonGlide[ note ] );
+
+ y += 16;
+
+ module->m_pButtonTrig[ note ] = new MyLEDButton( x, y, 12, 12, 10.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, note, module, ARP700_Trig );
+ addChild( module->m_pButtonTrig[ note ] );
+ }
+
+ x += 14;
+ }
+
+ x += 5;
+ }
+
+ // clock trigger
+ addChild(Port::create( Vec( 44, 21 ), Port::INPUT, module, ARP700::IN_CLOCK_TRIG ) );
+
+ // VOCT offset input
+ addChild(Port::create( Vec( 44, 52 ), Port::INPUT, module, ARP700::IN_VOCT_OFF ) );
+
+ // prog change trigger
+ addChild(Port::create( Vec( 44, 102 ), Port::INPUT, module, ARP700::IN_PROG_CHANGE ) );
+
+ // outputs
+ addChild(Port::create( Vec( 365, 38 ), Port::OUTPUT, module, ARP700::OUT_VOCTS ) );
+ addChild(Port::create( Vec( 365, 79 ), Port::OUTPUT, module, ARP700::OUT_TRIG ) );
+
+ // reset inputs
+ addChild(Port::create( Vec( 14, 21 ), Port::INPUT, module, ARP700::IN_CLOCK_RESET ) );
+
+ // mode buttons
+ module->m_pButtonMode = new MyLEDButtonStrip( 154, 360, 12, 12, 7, 10.0, 7, false, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, 0, module, ARP700_ModeSelect );
+ addChild( module->m_pButtonMode );
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ module->m_bInitialized = true;
+
+ module->reset();
+}
+};
+
+Model *modelARP700_Widget = Model::create( "mscHack", "ARP700", "ARP 700", SEQUENCER_TAG, OSCILLATOR_TAG );
+
+
+//-----------------------------------------------------
+// Procedure: JsonParams
+//
+//-----------------------------------------------------
+void ARP700::JsonParams( bool bTo, json_t *root)
+{
+ JsonDataBool ( bTo, "m_bPauseState", root, &m_bPauseState, 1 );
+ JsonDataInt ( bTo, "m_CurrentPattern", root, &m_PatCtrl.pat, 1 );
+ JsonDataInt ( bTo, "m_PatternSave", root, (int*)m_PatternSave, sizeof( m_PatternSave ) / 4 );
+ JsonDataInt ( bTo, "m_PatternsUsed", root, &m_PatCtrl.used, 1 );
+}
+
+//-----------------------------------------------------
+// Procedure: toJson
+//
+//-----------------------------------------------------
+json_t *ARP700::toJson()
+{
+ json_t *root = json_object();
+
+ if( !root )
+ return NULL;
+
+ JsonParams( TOJSON, root );
+
+ return root;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void ARP700::fromJson( json_t *root )
+{
+ JsonParams( FROMJSON, root );
+
+ m_pButtonPause->Set( m_bPauseState );
+ pKeyboardWidget->setkey( m_PatternSave[ m_PatCtrl.pat ].notes );
+
+ ChangePattern( m_PatCtrl.pat, true );
+
+ ArpStep( true );
+}
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void ARP700::reset()
+{
+ int pat, note;
+
+ if( !m_bInitialized )
+ return;
+
+ m_pButtonOctaveSelect->Set( 0, true );
+
+ m_PatCtrl.fCvStartOut = 0;
+ m_PatCtrl.fCvEndOut = 0;
+ memset( m_PatternSave, 0, sizeof(m_PatternSave) );
+
+ for( pat = 0; pat < MAX_ARP_PATTERNS; pat++ )
+ {
+ for( note = 0; note < MAX_ARP_NOTES; note++ )
+ m_PatternSave[ pat ].notes[ note ] = -1;
+ }
+
+ SetPatternSteps( MAX_ARP_PATTERNS - 1 );
+ ChangePattern( 0, true );
+}
+
+//-----------------------------------------------------
+// Procedure: randomize
+//
+//-----------------------------------------------------
+void ARP700::randomize()
+{
+}
+
+//-----------------------------------------------------
+// Procedure: SetPhraseSteps
+//
+//-----------------------------------------------------
+void ARP700::SetPatternSteps( int nSteps )
+{
+ if( nSteps < 0 || nSteps >= MAX_ARP_PATTERNS )
+ nSteps = 0;
+
+ m_PatCtrl.used = nSteps;
+}
+
+//-----------------------------------------------------
+// Procedure: SetOut
+//
+//-----------------------------------------------------
+void ARP700::SetOut( void )
+{
+ int note = 0, nstep, substep;
+ float foct;
+
+ m_VoctOffsetIn = inputs[ IN_VOCT_OFF ].normalize( 0.0 );
+
+ nstep = m_PatCtrl.step / SUBSTEP_PER_NOTE;
+ substep = m_PatCtrl.step - ( nstep * SUBSTEP_PER_NOTE );
+
+ if( m_PatternSave[ m_PatCtrl.pat ].onoffsel[ nstep ][ substep ] == ARP_ON )
+ {
+ note = m_PatternSave[ m_PatCtrl.pat ].notes[ nstep ];
+ pKeyboardWidget->setkeyhighlight( note );
+ }
+ else
+ return;
+
+ if( note > 36 || note < 0 )
+ note = 0;
+
+ foct = (float)m_PatternSave[ m_PatCtrl.pat ].oct;
+
+ m_PatCtrl.fCvEndOut = foct + m_fKeyNotes[ note ] + m_VoctOffsetIn;
+
+ // start glide note (last pattern note)
+ if( m_PatCtrl.bWasLastNotePlayed )
+ {
+ m_PatCtrl.fCvStartOut = m_PatCtrl.fLastNotePlayed + m_VoctOffsetIn;
+ }
+ else
+ {
+ m_PatCtrl.bWasLastNotePlayed = true;
+ m_PatCtrl.fCvStartOut = m_PatCtrl.fCvEndOut + m_VoctOffsetIn;
+ }
+
+ m_PatCtrl.fLastNotePlayed = m_PatCtrl.fCvEndOut + m_VoctOffsetIn;
+
+ if( m_PatternSave[ m_PatCtrl.pat ].glide[ nstep ] )
+ {
+ // glide time
+ m_PatCtrl.glideCount = 0.2 * engineGetSampleRate();
+ m_PatCtrl.fglideInc = 1.0 / (float)m_PatCtrl.glideCount;
+
+ m_PatCtrl.fglide = 1.0;
+ }
+ else
+ {
+ m_PatCtrl.fglide = 0.0;
+ m_PatCtrl.glideCount = 0;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: SetPendingPattern
+//
+//-----------------------------------------------------
+void ARP700::SetPendingPattern( int patin )
+{
+ int pattern;
+
+ if( patin < 0 || patin >= MAX_ARP_PATTERNS )
+ pattern = ( m_PatCtrl.pat + 1 ) & 0x7;
+ else
+ pattern = patin;
+
+ if( pattern > m_PatCtrl.used )
+ pattern = 0;
+
+ m_PatCtrl.pending.bPending = true;
+ m_PatCtrl.pending.pat = pattern;
+ m_pPatternSelect->SetPat( m_PatCtrl.pat, false );
+ m_pPatternSelect->SetPat( pattern, true );
+}
+
+//-----------------------------------------------------
+// Procedure: ChangePattern
+//
+//-----------------------------------------------------
+void ARP700::ChangePattern( int index, bool bForce )
+{
+ int note, param;
+
+ if( !bForce && index == m_PatCtrl.pat )
+ return;
+
+ if( index < 0 )
+ index = MAX_ARP_PATTERNS - 1;
+ else if( index >= MAX_ARP_PATTERNS )
+ index = 0;
+
+ m_PatCtrl.pat = index;
+
+ for( note = 0; note < MAX_ARP_NOTES; note++ )
+ {
+ m_pButtonGlide[ note ]->Set( m_PatternSave[ m_PatCtrl.pat ].glide[ note ] );
+ m_pButtonTrig[ note ]->Set( m_PatternSave[ m_PatCtrl.pat ].legato[ note ] );
+
+ for( param = 0; param < SUBSTEP_PER_NOTE; param++ )
+ {
+ m_pButtonOnOff[ note ][ param ]->Set( m_PatternSave[ m_PatCtrl.pat ].onoffsel[ note ][ param ], true );
+ m_pButtonLen[ note ][ param ]->Set( m_PatternSave[ m_PatCtrl.pat ].lensel[ note ][ param ], true );
+ m_pButtonLenMod[ note ][ param ]->Set( m_PatternSave[ m_PatCtrl.pat ].lenmod[ note ][ param ], true );
+ }
+ }
+
+ m_pButtonOctaveSelect->Set( m_PatternSave[ m_PatCtrl.pat ].oct, true );
+ m_pButtonMode->Set( m_PatternSave[ m_PatCtrl.pat ].mode, true );
+ m_pPatternSelect->SetPat( index, false );
+ m_pPatternSelect->SetMax( m_PatCtrl.used );
+
+ // set keyboard keys
+ pKeyboardWidget->setkey( m_PatternSave[ m_PatCtrl.pat ].notes );
+}
+
+//-----------------------------------------------------
+// Procedure: IncStep
+//
+//-----------------------------------------------------
+#define BASE_TICK_16th 48 // for 16th note
+
+const float fbasenotelen[ 6 ] = { BASE_TICK_16th * 4, BASE_TICK_16th * 2, BASE_TICK_16th, BASE_TICK_16th / 2, BASE_TICK_16th / 4, BASE_TICK_16th / 8};
+
+const int patmode[ 7 ][ 42 ] =
+{
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1 },
+ { 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, -1 },
+ { 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 },
+ { 0, 20, 1, 19, 2, 18, 3, 17, 4, 16, 5, 15, 6, 14, 7, 13, 8, 12, 9, 11, 10, 10, 11, 9, 12, 8, 13, 7, 14, 6, 15, 5, 16, 4, 17, 3, 18, 2, 19, 1, 20, 0 },
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }
+};
+
+void ARP700::ArpStep( bool bReset )
+{
+ int i, nstep, substep, modestp;
+
+ if( !m_PatternSave[ m_PatCtrl.pat ].notesused )
+ {
+ m_PatCtrl.virtstep = -1;
+ m_PatCtrl.bTrig = false;
+ return;
+ }
+ else
+ {
+ if( bReset )
+ m_PatCtrl.virtstep = -1;
+
+ // find next used step
+ for( i = 0; i <= ( SUBSTEP_PER_NOTE * MAX_ARP_NOTES * 2 ); i++ )
+ {
+ m_PatCtrl.virtstep++;
+
+ if( m_PatCtrl.virtstep >= ( SUBSTEP_PER_NOTE * MAX_ARP_NOTES * 2 ) )
+ m_PatCtrl.virtstep = 0;
+
+ if( m_PatternSave[ m_PatCtrl.pat ].mode == 6 )
+ modestp = (int)( randomUniform() * 20.0f );
+ else
+ modestp = patmode[ m_PatternSave[ m_PatCtrl.pat ].mode ][ m_PatCtrl.virtstep ];
+
+ if( modestp != -1 )
+ {
+ nstep = modestp / SUBSTEP_PER_NOTE;
+ substep = modestp - ( nstep * SUBSTEP_PER_NOTE );
+
+ if( m_PatternSave[ m_PatCtrl.pat ].onoffsel[ nstep ][ substep ] != ARP_OFF )
+ {
+ m_PatCtrl.step = modestp;
+ goto stepfound;
+ }
+ }
+ else
+ {
+ m_PatCtrl.virtstep = -1;
+ }
+ }
+ }
+
+ // if we are here, no step was found
+ m_PatCtrl.step = -1;
+ m_PatCtrl.bTrig = false;
+ return;
+
+stepfound:
+
+ if( m_PatCtrl.step == 0 )
+ {
+ if( m_PatCtrl.pending.bPending )
+ {
+ m_PatCtrl.pending.bPending = false;
+ ChangePattern( m_PatCtrl.pending.pat, true );
+ }
+ }
+
+ nstep = m_PatCtrl.step / SUBSTEP_PER_NOTE;
+ substep = m_PatCtrl.step - ( nstep * SUBSTEP_PER_NOTE );
+
+ if( m_plastbut )
+ m_plastbut->SetHiLightOn( -1 );
+
+ m_pButtonLen[ nstep ][ substep ]->SetHiLightOn( m_PatternSave[ m_PatCtrl.pat ].lensel[ nstep ][ substep ] );
+ m_plastbut = m_pButtonLen[ nstep ][ substep ];
+
+ // next step length
+ m_PatCtrl.nextcount = fbasenotelen[ m_PatternSave[ m_PatCtrl.pat ].lensel[ nstep ][ substep ] ];
+
+ switch( m_PatternSave[ m_PatCtrl.pat ].lenmod[ nstep ][ substep ] )
+ {
+ case 1: // x2
+ m_PatCtrl.nextcount *= 2;
+ break;
+ case 2: // dotted
+ m_PatCtrl.nextcount += m_PatCtrl.nextcount / 2;
+ break;
+ case 3: // triplet
+ m_PatCtrl.nextcount = m_PatCtrl.nextcount / 3;
+ break;
+ }
+
+ // if this is note on return true
+ if( m_PatternSave[ m_PatCtrl.pat ].onoffsel[ nstep ][ substep ] == ARP_ON )
+ {
+ SetOut();
+
+ if( m_PatternSave[ m_PatCtrl.pat ].legato[ nstep ] )
+ m_PatCtrl.bTrig = false;
+ else
+ m_PatCtrl.bTrig = true;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void ARP700::step()
+{
+ bool bSyncTick = false;
+
+ if( !m_bInitialized )
+ return;
+
+ if( !inputs[ IN_CLOCK_TRIG ].active )
+ {
+ outputs[ OUT_TRIG ].value = 0;
+
+ if( m_PatCtrl.pending.bPending )
+ {
+ m_PatCtrl.pending.bPending = false;
+ ChangePattern( m_PatCtrl.pending.pat, false );
+ }
+
+ return;
+ }
+
+ // global clock reset
+ if( m_SchTrigGlobalClkReset.process( inputs[ IN_CLOCK_RESET ].normalize( 0.0f ) ) )
+ {
+ m_Clock.bClockReset = true;
+ }
+
+ // pattern change trig
+ if( !m_bPauseState && m_SchTrigPatternChange.process( inputs[ IN_PROG_CHANGE ].normalize( 0.0f ) ) )
+ SetPendingPattern( -1 );
+
+ m_Clock.tickcount++;
+
+ // track clock period
+ if( m_SchTrigClk.process( inputs[ IN_CLOCK_TRIG ].normalize( 0.0f ) ) || m_Clock.bClockReset )
+ {
+ if( m_Clock.bClockReset )
+ {
+ ArpStep( true );
+ m_Clock.bClockReset = false;
+ }
+
+ // sliding clock rate average
+ m_Clock.Avg.tot += m_Clock.tickcount;
+ m_Clock.Avg.avg[ ( m_Clock.Avg.count++ & AVG_AND ) ] = m_Clock.tickcount;
+
+ if( m_Clock.Avg.count >= AVG_ARRAY_LEN )
+ m_Clock.ftickspersec = (float)m_Clock.Avg.tot / (float)AVG_ARRAY_LEN;
+ else
+ m_Clock.ftickspersec = (float)m_Clock.Avg.tot / (float)m_Clock.Avg.count;
+
+ m_Clock.Avg.tot -= m_Clock.Avg.avg[ ( m_Clock.Avg.count & AVG_AND ) ];
+
+ if( m_Clock.tickcount )
+ m_Clock.fsynclen = (float)( engineGetSampleRate() / (float)m_Clock.tickcount ) * 48.0;
+
+ m_Clock.tickcount = 0;
+ m_Clock.fsynccount = 0;
+
+ bSyncTick = true;
+ }
+ else
+ {
+ // keep track of sync tick (16th / 12 )
+ m_Clock.fsynccount += m_Clock.fsynclen;
+ if( m_Clock.fsynccount >= engineGetSampleRate() )
+ {
+ m_Clock.fsynccount = m_Clock.fsynccount - engineGetSampleRate();
+ bSyncTick = true;
+ }
+ }
+
+ if( m_bPauseState )
+ {
+ m_PatCtrl.bTrig = false;
+
+ if( m_PatCtrl.pending.bPending )
+ {
+ m_PatCtrl.pending.bPending = false;
+ ChangePattern( m_PatCtrl.pending.pat, false );
+ }
+ }
+ else if( bSyncTick )
+ {
+ m_PatCtrl.nextcount--;
+
+ // cut off note one tick early so they don't legato
+ if( m_PatCtrl.nextcount == 1 )
+ m_PatCtrl.bTrig = false;
+
+ else if( m_PatCtrl.nextcount <= 0 )
+ ArpStep( false );
+ }
+
+ outputs[ OUT_TRIG ].value = m_PatCtrl.bTrig ? CV_MAX : 0.0;
+
+ if( --m_PatCtrl.glideCount > 0 )
+ m_PatCtrl.fglide -= m_PatCtrl.fglideInc;
+ else
+ m_PatCtrl.fglide = 0.0;
+
+ outputs[ OUT_VOCTS ].value = ( m_PatCtrl.fCvStartOut * m_PatCtrl.fglide ) + ( m_PatCtrl.fCvEndOut * ( 1.0 - m_PatCtrl.fglide ) );
+
+}
diff --git a/src/CLog.cpp b/src/CLog.cpp
index a4cf391..884be4e 100644
--- a/src/CLog.cpp
+++ b/src/CLog.cpp
@@ -28,7 +28,7 @@ char Months[12][4]=
};
//-----------------------------------------------------
-// Function:
+// Function:
//
//-----------------------------------------------------
CLog::CLog()
@@ -39,7 +39,7 @@ CLog::CLog()
}
//-----------------------------------------------------
-// Function:
+// Function:
//
//-----------------------------------------------------
CLog::~CLog()
@@ -48,7 +48,7 @@ CLog::~CLog()
}
//-----------------------------------------------------
-// Function:
+// Function:
//
//-----------------------------------------------------
int CLog::Open( std::string strFileName )
@@ -59,10 +59,10 @@ int CLog::Open( std::string strFileName )
// return success
return 1;
-}
+}
//-----------------------------------------------------
-// Function:
+// Function:
//
//-----------------------------------------------------
void CLog::Close(void)
@@ -77,7 +77,7 @@ void CLog::Close(void)
}
//-----------------------------------------------------
-// Function: SetLogLvl
+// Function: SetLogLvl
//
//-----------------------------------------------------
void CLog::SetLogLvl( int level )
@@ -86,7 +86,7 @@ void CLog::SetLogLvl( int level )
}
//-----------------------------------------------------
-// Function: SetCallback
+// Function: SetCallback
//
//-----------------------------------------------------
void CLog::SetCallback ( LOGCALLBACK *func, void *pClass )
@@ -111,7 +111,7 @@ void CLog::f( std::string string, ... )
va_end( arglist );
// write string to file
- fprintf( fp,(char*)buffer.c_str() );
+ fprintf( fp, "%s", (char*)buffer.c_str() );
// write string to callback so user can display it somewhere
if( m_LogCallbackFunc )
@@ -121,12 +121,12 @@ void CLog::f( std::string string, ... )
}
//-----------------------------------------------------
-// Function: no timestamp
+// Function: no timestamp
//
//-----------------------------------------------------
void CLog::fnr( std::string string, ...)
{
-
+
va_list arglist; // variable argument list
// if file not open then bail
@@ -139,7 +139,7 @@ void CLog::fnr( std::string string, ...)
va_end( arglist );
// write string to file
- fprintf( fp, (char*)buffer.c_str() );
+ fprintf( fp, "%s", buffer.c_str());
// write string to callback so user can display it somewhere
if( m_LogCallbackFunc )
@@ -149,7 +149,7 @@ void CLog::fnr( std::string string, ...)
}
//-----------------------------------------------------
-// Function: mem - memory dump
+// Function: mem - memory dump
//
//-----------------------------------------------------
void CLog::mem( unsigned char *pBuff, unsigned int dwSize, unsigned int dwOff )
@@ -164,10 +164,10 @@ void CLog::mem( unsigned char *pBuff, unsigned int dwSize, unsigned int dwOff )
do{
f("0x%.4x: ", dwOff);
-
+
Offset+=16;
dwOff+=16;
-
+
for(i=0; i<16; i++)
{
// extra space every four
@@ -200,4 +200,4 @@ void CLog::mem( unsigned char *pBuff, unsigned int dwSize, unsigned int dwOff )
}while(index < dwSize);
f("\n\n");
-}
\ No newline at end of file
+}
diff --git a/src/Compressor.cpp b/src/Compressor.cpp
new file mode 100644
index 0000000..1048993
--- /dev/null
+++ b/src/Compressor.cpp
@@ -0,0 +1,404 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+typedef struct
+{
+ int state;
+ float finc;
+ int count;
+ float fade;
+}COMP_STATE;
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct Compressor : Module
+{
+ enum ParamIds
+ {
+ PARAM_INGAIN,
+ PARAM_OUTGAIN,
+ PARAM_THRESHOLD,
+ PARAM_RATIO,
+ PARAM_ATTACK,
+ PARAM_RELEASE,
+ PARAM_BYPASS,
+ PARAM_SIDE_CHAIN,
+ nPARAMS
+ };
+
+ enum InputIds
+ {
+ IN_AUDIOL,
+ IN_AUDIOR,
+ IN_SIDE_CHAIN,
+ nINPUTS
+ };
+
+ enum OutputIds
+ {
+ OUT_AUDIOL,
+ OUT_AUDIOR,
+ nOUTPUTS
+ };
+
+ enum CompState
+ {
+ COMP_DONE,
+ COMP_START,
+ COMP_ATTACK,
+ COMP_RELEASE,
+ COMP_IDLE
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ LEDMeterWidget *m_pLEDMeterIn[ 2 ] = {0};
+ CompressorLEDMeterWidget *m_pLEDMeterThreshold = NULL;
+ CompressorLEDMeterWidget *m_pLEDMeterComp[ 2 ] = {0};
+ LEDMeterWidget *m_pLEDMeterOut[ 2 ] = {0};
+
+ bool m_bBypass = false;
+ MyLEDButton *m_pButtonBypass = NULL;
+
+ COMP_STATE m_CompL = {};
+ COMP_STATE m_CompR = {};
+ float m_fThreshold;
+
+ // Contructor
+ Compressor() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0 ){}
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void reset() override;
+ void randomize() override;
+
+ bool ProcessCompState( COMP_STATE *pComp, bool bAboveThreshold );
+ float Compress( float *pDetectInL, float *pDetectInR );
+};
+
+//-----------------------------------------------------
+// Compressor_Bypass
+//-----------------------------------------------------
+void Compressor_Bypass( void *pClass, int id, bool bOn )
+{
+ Compressor *mymodule;
+ mymodule = (Compressor*)pClass;
+ mymodule->m_bBypass = bOn;
+}
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+struct Compressor_Widget : ModuleWidget {
+ Compressor_Widget(Compressor *module) : ModuleWidget(module)
+{
+ int x, y, y2;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/Compressor.svg")));
+
+ //module->lg.Open("Compressor.txt");
+ x = 10;
+ y = 34;
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ // bypass switch
+ module->m_pButtonBypass = new MyLEDButton( x, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, 0, module, Compressor_Bypass );
+ addChild( module->m_pButtonBypass );
+
+ // audio inputs
+ addChild(Port::create( Vec( x, y + 32 ), Port::INPUT, module, Compressor::IN_AUDIOL ) );
+ addChild(Port::create( Vec( x, y + 78 ), Port::INPUT, module, Compressor::IN_AUDIOR ) );
+ addChild(Port::create( Vec( x - 1, y + 210 ), Port::INPUT, module, Compressor::IN_SIDE_CHAIN ) );
+
+ // LED meters
+ module->m_pLEDMeterIn[ 0 ] = new LEDMeterWidget( x + 22, y + 25, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterIn[ 0 ] );
+ module->m_pLEDMeterIn[ 1 ] = new LEDMeterWidget( x + 28, y + 25, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterIn[ 1 ] );
+
+ module->m_pLEDMeterThreshold = new CompressorLEDMeterWidget( true, x + 39, y + 25, 5, 3, DWRGB( 245, 10, 174 ), DWRGB( 96, 4, 68 ) );
+ addChild( module->m_pLEDMeterThreshold );
+
+ module->m_pLEDMeterComp[ 0 ] = new CompressorLEDMeterWidget( true, x + 48, y + 25, 5, 3, DWRGB( 0, 128, 255 ), DWRGB( 0, 64, 128 ) );
+ addChild( module->m_pLEDMeterComp[ 0 ] );
+ module->m_pLEDMeterComp[ 1 ] = new CompressorLEDMeterWidget( true, x + 55, y + 25, 5, 3, DWRGB( 0, 128, 255 ), DWRGB( 0, 64, 128 ) );
+ addChild( module->m_pLEDMeterComp[ 1 ] );
+
+ module->m_pLEDMeterOut[ 0 ] = new LEDMeterWidget( x + 65, y + 25, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterOut[ 0 ] );
+ module->m_pLEDMeterOut[ 1 ] = new LEDMeterWidget( x + 72, y + 25, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterOut[ 1 ] );
+
+ // audio outputs
+ addChild(Port::create( Vec( x + 83, y + 32 ), Port::OUTPUT, module, Compressor::OUT_AUDIOL ) );
+ addChild(Port::create( Vec( x + 83, y + 78 ), Port::OUTPUT, module, Compressor::OUT_AUDIOR ) );
+
+ // add param knobs
+ y2 = y + 149;
+ addParam(ParamWidget::create( Vec( x + 11, y + 113 ), module, Compressor::PARAM_INGAIN, 0.0, 4.0, 1.0 ) );
+ addParam(ParamWidget::create( Vec( x + 62, y + 113 ), module, Compressor::PARAM_OUTGAIN, 0.0, 8.0, 1.0 ) );
+ addParam(ParamWidget::create( Vec( x - 5, y2 + 20 ), module, Compressor::PARAM_SIDE_CHAIN, 0.0, 1.0, 0.0 ) );
+ addParam(ParamWidget::create( Vec( x + 39, y2 ), module, Compressor::PARAM_THRESHOLD, 0.0, 0.99, 0.0 ) ); y2 += 40;
+ addParam(ParamWidget::create( Vec( x + 39, y2 ), module, Compressor::PARAM_RATIO, 0.0, 2.0, 0.0 ) ); y2 += 40;
+ addParam(ParamWidget::create( Vec( x + 39, y2 ), module, Compressor::PARAM_ATTACK, 0.0, 1.0, 0.0 ) ); y2 += 40;
+ addParam(ParamWidget::create( Vec( x + 39, y2 ), module, Compressor::PARAM_RELEASE, 0.0, 1.0, 0.0 ) );
+
+ //for( int i = 0; i < 15; i++ )
+ //module->lg.f("level %d = %.3f\n", i, module->m_pLEDMeterThreshold->flevels[ i ] );
+
+ module->m_bInitialized = true;
+}
+};
+
+Model *modelCompressor_Widget = Model::create( "mscHack", "Compressor1", "COMP Basic Compressor", DYNAMICS_TAG );
+
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *Compressor::toJson()
+{
+ json_t *rootJ = json_object();
+
+ // reverse state
+ json_object_set_new(rootJ, "m_bBypass", json_boolean (m_bBypass));
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void Compressor::fromJson(json_t *rootJ)
+{
+ // reverse state
+ json_t *revJ = json_object_get(rootJ, "m_bBypass");
+
+ if (revJ)
+ m_bBypass = json_is_true( revJ );
+
+ m_pButtonBypass->Set( m_bBypass );
+}
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void Compressor::reset()
+{
+}
+
+//-----------------------------------------------------
+// Procedure: randomize
+//
+//-----------------------------------------------------
+void Compressor::randomize()
+{
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessCompStatus
+//
+//-----------------------------------------------------
+#define MAX_ATT_TIME (0.5f) // 500ms
+#define MAX_REL_TIME (2.0f) // 2s
+bool Compressor::ProcessCompState( COMP_STATE *pComp, bool bAboveThreshold )
+{
+ bool bCompressing = true;
+
+ // restart compressor if it has finished
+ if( bAboveThreshold && ( pComp->state == COMP_IDLE ) )
+ {
+ pComp->state = COMP_START;
+ }
+ // ready compressor for restart
+ else if( !bAboveThreshold && ( pComp->state == COMP_DONE ) )
+ {
+ pComp->state = COMP_IDLE;
+ }
+
+ switch( pComp->state )
+ {
+ case COMP_START:
+ pComp->count = 10 + (int)( MAX_ATT_TIME * engineGetSampleRate() * params[ PARAM_ATTACK ].value );
+
+ pComp->state = COMP_ATTACK;
+ pComp->finc = (1.0f - pComp->fade) / (float)pComp->count;
+
+ break;
+
+ case COMP_ATTACK:
+ if( --pComp->count > 0 )
+ {
+ pComp->fade += pComp->finc;
+
+ if( pComp->fade > 1.0f )
+ pComp->fade = 1.0f;
+ }
+ else
+ {
+ pComp->count = 10 + (int)( MAX_REL_TIME * engineGetSampleRate() * params[ PARAM_RELEASE ].value );
+ pComp->fade = 1.0f;
+ pComp->finc = 1.0f / (float)pComp->count;
+ pComp->state = COMP_RELEASE;
+ }
+
+ break;
+
+ case COMP_RELEASE:
+ if( --pComp->count > 0 )
+ {
+ pComp->fade -= pComp->finc;
+
+ if( pComp->fade < 0.0f )
+ pComp->fade = 0.0f;
+ }
+ else
+ {
+ pComp->fade = 0.0f;
+ pComp->state = COMP_DONE;
+ bCompressing = false;
+ }
+ break;
+
+ case COMP_DONE:
+ pComp->fade = 0.0f;
+ bCompressing = false;
+ break;
+
+ case COMP_IDLE:
+ pComp->fade = 0.0f;
+ bCompressing = false;
+ break;
+ }
+
+ return bCompressing;
+}
+
+//-----------------------------------------------------
+// Procedure: Compress
+//
+//-----------------------------------------------------
+float Compressor::Compress( float *pDetectInL, float *pDetectInR )
+{
+ float rat, th, finL, finR, compL = 1.0f, compR = 1.0f;
+
+ m_fThreshold = params[ PARAM_THRESHOLD ].value;
+ th = 1.0f - m_fThreshold;
+ rat = params[ PARAM_RATIO ].value;
+
+ finL = fabs( *pDetectInL );
+ finR = pDetectInR ? fabs( *pDetectInR ) : 0.0f;
+
+ if( ProcessCompState( &m_CompL, ( finL > th ) ) )
+ compL = 1.0f - ( rat * m_CompL.fade );
+
+ if( pDetectInR )
+ {
+ if( ProcessCompState( &m_CompR, ( finR > th ) ) )
+ compR = 1.0f - ( rat * m_CompR.fade );
+ }
+ else
+ {
+ m_CompR.state = COMP_IDLE;
+ m_CompR.fade = 0.0;
+ }
+
+ return fmin( compL, compR );
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void Compressor::step()
+{
+ float outL, outR, diffL, diffR, fcomp, fside;
+
+ if( !m_bInitialized )
+ return;
+
+ outL = inputs[ IN_AUDIOL ].normalize( 0.0 ) / AUDIO_MAX;
+ outR = inputs[ IN_AUDIOR ].normalize( 0.0 ) / AUDIO_MAX;
+
+ if( !m_bBypass )
+ {
+ outL = clamp( outL * params[ PARAM_INGAIN ].value, -1.0, 1.0 );
+ outR = clamp( outR * params[ PARAM_INGAIN ].value, -1.0, 1.0 );
+ }
+
+ if( m_pLEDMeterIn[ 0 ] )
+ m_pLEDMeterIn[ 0 ]->Process( outL );
+
+ if( m_pLEDMeterIn[ 1 ] )
+ m_pLEDMeterIn[ 1 ]->Process( outR );
+
+ diffL = fabs( outL );
+ diffR = fabs( outR );
+
+ if( !m_bBypass )
+ {
+ // compress
+ if( inputs[ IN_SIDE_CHAIN ].active )
+ {
+ fside = clamp( ( inputs[ IN_SIDE_CHAIN ].normalize( 0.0 ) / AUDIO_MAX ) * params[ PARAM_SIDE_CHAIN ].value, -1.0, 1.0 );
+ fcomp = Compress( &fside, NULL );
+ }
+ else
+ {
+ fcomp = Compress( &outL, &outR );
+ }
+
+ outL *= fcomp;
+ outR *= fcomp;
+
+ diffL -= fabs( outL );
+ diffR -= fabs( outR );
+
+ if( m_pLEDMeterComp[ 0 ] )
+ m_pLEDMeterComp[ 0 ]->Process( diffL );
+
+ if( m_pLEDMeterComp[ 1 ] )
+ m_pLEDMeterComp[ 1 ]->Process( diffR );
+
+ if( m_pLEDMeterThreshold )
+ m_pLEDMeterThreshold->Process( m_fThreshold );
+
+ outL = clamp( outL * params[ PARAM_OUTGAIN ].value, -1.0, 1.0 );
+ outR = clamp( outR * params[ PARAM_OUTGAIN ].value, -1.0, 1.0 );
+ }
+ else
+ {
+ if( m_pLEDMeterComp[ 0 ] )
+ m_pLEDMeterComp[ 0 ]->Process( 0 );
+
+ if( m_pLEDMeterComp[ 1 ] )
+ m_pLEDMeterComp[ 1 ]->Process( 0 );
+
+ if( m_pLEDMeterThreshold )
+ m_pLEDMeterThreshold->Process( 0 );
+ }
+
+ if( m_pLEDMeterOut[ 0 ] )
+ m_pLEDMeterOut[ 0 ]->Process( outL );
+
+ if( m_pLEDMeterOut[ 1 ] )
+ m_pLEDMeterOut[ 1 ]->Process( outR );
+
+ outputs[ OUT_AUDIOL ].value = outL * AUDIO_MAX;
+ outputs[ OUT_AUDIOR ].value = outR * AUDIO_MAX;
+}
diff --git a/src/MasterClockx4.cpp b/src/MasterClockx4.cpp
new file mode 100644
index 0000000..f203afc
--- /dev/null
+++ b/src/MasterClockx4.cpp
@@ -0,0 +1,601 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define nCHANNELS 4
+const int multdisplayval[ 25 ] = { 32, 24, 16, 12, 9, 8, 7, 6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 16, 24, 32 };
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct MasterClockx4 : Module
+{
+ enum ParamIds
+ {
+ PARAM_BPM,
+ PARAM_MULT,
+ PARAM_HUMANIZE = PARAM_MULT + nCHANNELS,
+ nPARAMS
+ };
+
+ enum InputIds
+ {
+ INPUT_EXT_SYNC,
+ INPUT_CHAIN = INPUT_EXT_SYNC + nCHANNELS,
+ nINPUTS
+ };
+
+ enum OutputIds
+ {
+ OUTPUT_CLK,
+ OUTPUT_TRIG = OUTPUT_CLK + ( nCHANNELS * 4 ),
+ OUTPUT_CHAIN = OUTPUT_TRIG + ( nCHANNELS * 4 ),
+ nOUTPUTS
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ float m_fBPM = 120;
+
+ MyLED7DigitDisplay *m_pDigitDisplayMult[ nCHANNELS ] = {};
+ MyLED7DigitDisplay *m_pDigitDisplayBPM = NULL;
+
+ MyLEDButton *m_pButtonGlobalStop = NULL;
+ MyLEDButton *m_pButtonGlobalTrig = NULL;
+ MyLEDButton *m_pButtonStop[ nCHANNELS ] = {};
+ MyLEDButton *m_pButtonTrig[ nCHANNELS ] = {};
+
+ bool m_bChannelSync[ nCHANNELS ] = {};
+ bool m_bGlobalSync = false;
+ bool m_bStopState[ nCHANNELS ] = {};
+ bool m_bGlobalStopState = false;
+
+ int m_ChannelDivBeatCount[ nCHANNELS ] = {};
+ float m_fChannelBeatsPers[ nCHANNELS ] = {};
+ float m_fChannelClockCount[ nCHANNELS ] = {};
+ int m_ChannelMultSelect[ nCHANNELS ] = {};
+
+ float m_fHumanize = 0;
+ float m_bWasChained = false;
+
+ float m_fBeatsPers;
+ float m_fMainClockCount;
+
+ PulseGenerator m_PulseClock[ nCHANNELS ];
+ PulseGenerator m_PulseSync[ nCHANNELS ];
+ SchmittTrigger m_SchmittSyncIn[ nCHANNELS ];
+
+ ParamWidget *m_pBpmKnob = NULL;
+ ParamWidget *m_pHumanKnob = NULL;
+
+ // Contructor
+ MasterClockx4() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0){}
+
+ //-----------------------------------------------------
+ // MyHumanize_Knob
+ //-----------------------------------------------------
+ struct MyHumanize_Knob : Yellow2_Small
+ {
+ MasterClockx4 *mymodule;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (MasterClockx4*)module;
+
+ if( mymodule && !mymodule->inputs[ INPUT_CHAIN ].active )
+ mymodule->GetNewHumanizeVal();
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyBPM_Knob
+ //-----------------------------------------------------
+ struct MyBPM_Knob : Yellow2_Huge
+ {
+ MasterClockx4 *mymodule;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (MasterClockx4*)module;
+
+ if( mymodule && !mymodule->inputs[ INPUT_CHAIN ].active )
+ mymodule->BPMChange( value, false );
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyMult_Knob
+ //-----------------------------------------------------
+
+ struct MyMult_Knob : Yellow2_Snap
+ {
+ MasterClockx4 *mymodule;
+ int param, col;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (MasterClockx4*)module;
+
+ if( mymodule )
+ {
+ param = paramId - MasterClockx4::PARAM_MULT;
+
+ if( mymodule->m_ChannelMultSelect[ param ] != (int)value )
+ {
+ if( (int)value < 12 )
+ {
+ col = DWRGB( 0xFF, 0, 0 );
+ }
+ else if( (int)value > 12 )
+ {
+ col = DWRGB( 0, 0xFF, 0xFF );
+ }
+ else
+ {
+ col = DWRGB( 0xFF, 0xFF, 0xFF );
+ }
+
+ if( mymodule->m_pDigitDisplayMult[ param ] )
+ {
+ mymodule->m_ChannelMultSelect[ param ] = (int)value;
+ mymodule->m_pDigitDisplayMult[ param ]->SetLEDCol( col );
+ mymodule->m_pDigitDisplayMult[ param ]->SetInt( multdisplayval[ (int)value ] );
+ mymodule->CalcChannelClockRate( param );
+ }
+ }
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void reset() override;
+
+ void GetNewHumanizeVal( void );
+ void BPMChange( float fbmp, bool bforce );
+ void CalcChannelClockRate( int ch );
+};
+
+//-----------------------------------------------------
+// MyLEDButton_GlobalStop
+//-----------------------------------------------------
+void MyLEDButton_GlobalStop( void *pClass, int id, bool bOn )
+{
+ MasterClockx4 *mymodule;
+ mymodule = (MasterClockx4*)pClass;
+ mymodule->m_bGlobalStopState = bOn;
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GlobalTrig
+//-----------------------------------------------------
+void MyLEDButton_GlobalTrig( void *pClass, int id, bool bOn )
+{
+ MasterClockx4 *mymodule;
+ mymodule = (MasterClockx4*)pClass;
+ mymodule->m_bGlobalSync = true;
+
+ if( mymodule->m_pButtonTrig[ 0 ] )
+ mymodule->m_pButtonTrig[ 0 ]->Set( true );
+
+ if( mymodule->m_pButtonTrig[ 1 ] )
+ mymodule->m_pButtonTrig[ 1 ]->Set( true );
+
+ if( mymodule->m_pButtonTrig[ 2 ] )
+ mymodule->m_pButtonTrig[ 2 ]->Set( true );
+
+ if( mymodule->m_pButtonTrig[ 3 ] )
+ mymodule->m_pButtonTrig[ 3 ]->Set( true );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChannelStop
+//-----------------------------------------------------
+void MyLEDButton_ChannelStop ( void *pClass, int id, bool bOn )
+{
+ MasterClockx4 *mymodule;
+ mymodule = (MasterClockx4*)pClass;
+ mymodule->m_bStopState[ id ] = bOn;
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChannelSync
+//-----------------------------------------------------
+void MyLEDButton_ChannelSync( void *pClass, int id, bool bOn )
+{
+ MasterClockx4 *mymodule;
+ mymodule = (MasterClockx4*)pClass;
+ mymodule->m_bChannelSync[ id ] = true;
+}
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+struct MasterClockx4_Widget : ModuleWidget {
+ MasterClockx4_Widget(MasterClockx4 *module) : ModuleWidget(module)
+{
+ int ch, x, y;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/MasterClockx4.svg")));
+
+ //module->lg.Open("MasterClockx4.txt");
+
+ // bpm knob
+ module->m_pBpmKnob = ParamWidget::create( Vec( 9, 50 ), module, MasterClockx4::PARAM_BPM, 60.0, 220.0, 120.0 );
+ addParam( module->m_pBpmKnob );
+
+ // bpm display
+ module->m_pDigitDisplayBPM = new MyLED7DigitDisplay( 5, 115, 0.055, DWRGB( 0, 0, 0 ), DWRGB( 0xFF, 0xFF, 0xFF ), MyLED7DigitDisplay::TYPE_FLOAT, 5 );
+ addChild( module->m_pDigitDisplayBPM );
+
+ // global stop switch
+ module->m_pButtonGlobalStop = new MyLEDButton( 22, 144, 25, 25, 20.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, 0, module, MyLEDButton_GlobalStop );
+ addChild( module->m_pButtonGlobalStop );
+
+ // global sync button
+ module->m_pButtonGlobalTrig = new MyLEDButton( 22, 202, 25, 25, 20.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_MOMENTARY, 0, module, MyLEDButton_GlobalTrig );
+ addChild( module->m_pButtonGlobalTrig );
+
+ // humanize knob
+ module->m_pHumanKnob = ParamWidget::create( Vec( 22, 235 ), module, MasterClockx4::PARAM_HUMANIZE, 0.0, 1.0, 0.0 );
+ addParam( module->m_pHumanKnob );
+
+ // add chain out
+ addChild(Port::create( Vec( 30, 345 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_CHAIN ) );
+
+ // chain in
+ addChild(Port::create( Vec( 30, 13 ), Port::INPUT, module, MasterClockx4::INPUT_CHAIN ) );
+
+ x = 74;
+ y = 36;
+
+ for( ch = 0; ch < nCHANNELS; ch++ )
+ {
+ // clock mult knob
+ addParam(ParamWidget::create( Vec( x + 36, y + 8 ), module, MasterClockx4::PARAM_MULT + ch, 0, 24, 12 ) );
+
+ // mult display
+ module->m_pDigitDisplayMult[ ch ] = new MyLED7DigitDisplay( x + 33, y + 44, 0.07, DWRGB( 0, 0, 0 ), DWRGB( 0xFF, 0xFF, 0xFF ), MyLED7DigitDisplay::TYPE_INT, 2 );
+ addChild( module->m_pDigitDisplayMult[ ch ] );
+
+ // sync triggers
+ module->m_pButtonTrig[ ch ] = new MyLEDButton( x + 192, y + 6, 19, 19, 15.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_MOMENTARY, ch, module, MyLEDButton_ChannelSync );
+ addChild( module->m_pButtonTrig[ ch ] );
+
+ addChild(Port::create( Vec( x + 170, y + 7 ), Port::INPUT, module, MasterClockx4::INPUT_EXT_SYNC + ch ) );
+ addChild(Port::create( Vec( x + 170, y + 33 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_TRIG + (ch * 4) + 0 ) );
+ addChild(Port::create( Vec( x + 170, y + 55 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_TRIG + (ch * 4) + 1 ) );
+ addChild(Port::create( Vec( x + 193, y + 33 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_TRIG + (ch * 4) + 2 ) );
+ addChild(Port::create( Vec( x + 193, y + 55 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_TRIG + (ch * 4) + 3 ) );
+
+ // clock out
+ module->m_pButtonStop[ ch ] = new MyLEDButton( x + 192 + 56, y + 6, 19, 19, 15.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_ChannelStop );
+ addChild( module->m_pButtonStop[ ch ] );
+
+ addChild(Port::create( Vec( x + 170 + 56, y + 33 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_CLK + (ch * 4) + 0 ) );
+ addChild(Port::create( Vec( x + 170 + 56, y + 55 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_CLK + (ch * 4) + 1 ) );
+ addChild(Port::create( Vec( x + 193 + 56, y + 33 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_CLK + (ch * 4) + 2 ) );
+ addChild(Port::create( Vec( x + 193 + 56, y + 55 ), Port::OUTPUT, module, MasterClockx4::OUTPUT_CLK + (ch * 4) + 3 ) );
+
+ y += 80;
+ }
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ module->m_bInitialized = true;
+
+ reset();
+}
+};
+
+Model *modelMasterClockx4_Widget = Model::create( "mscHack", "MasterClockx4", "Master CLOCK x 4", CLOCK_TAG, QUAD_TAG );
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *MasterClockx4::toJson()
+{
+ bool *pbool;
+ json_t *rootJ = json_object();
+
+ // m_bGlobalStopState
+ json_object_set_new( rootJ, "m_bGlobalStopState", json_boolean (m_bGlobalStopState) );
+
+ // m_bStopState
+ pbool = &m_bStopState[ 0 ];
+
+ json_t *gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_boolean( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_bStopState", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void MasterClockx4::fromJson(json_t *rootJ)
+{
+ bool *pbool;
+
+ // m_bGlobalStopState
+ json_t *revJ = json_object_get(rootJ, "m_bGlobalStopState");
+
+ if (revJ)
+ m_bGlobalStopState = json_is_true( revJ );
+
+ // m_bPauseState
+ pbool = &m_bStopState[ 0 ];
+
+ json_t *StepsJ = json_object_get(rootJ, "m_bStopState");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_boolean_value( gateJ );
+ }
+ }
+
+ m_pButtonGlobalStop->Set( m_bGlobalStopState );
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ m_ChannelMultSelect[ ch ] = (int)params[ PARAM_MULT + ch ].value;
+
+ m_pButtonStop[ ch ]->Set( m_bStopState[ ch ] );
+
+ if( m_pDigitDisplayMult[ ch ] )
+ m_pDigitDisplayMult[ ch ]->SetInt( multdisplayval[ (int)params[ PARAM_MULT + ch ].value ] );
+ }
+
+ m_fMainClockCount = 0;
+ BPMChange( params[ PARAM_BPM ].value, true );
+
+ if( m_pDigitDisplayBPM )
+ m_pDigitDisplayBPM->SetFloat( m_fBPM );
+}
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void MasterClockx4::reset()
+{
+ if( !m_bInitialized )
+ return;
+
+ m_fBPM = 120;
+
+ if( m_pDigitDisplayBPM )
+ m_pDigitDisplayBPM->SetFloat( m_fBPM );
+
+ m_bGlobalStopState = false;
+ m_pButtonGlobalStop->Set( m_bGlobalStopState );
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ m_ChannelMultSelect[ ch ] = (int)params[ PARAM_MULT + ch ].value;
+
+ m_bStopState[ ch ] = false;
+ m_pButtonStop[ ch ]->Set( m_bStopState[ ch ] );
+
+ if( m_pDigitDisplayMult[ ch ] )
+ m_pDigitDisplayMult[ ch ]->SetInt( multdisplayval[ (int)params[ PARAM_MULT + ch ].value ] );
+ }
+
+ BPMChange( m_fBPM, true );
+}
+
+//-----------------------------------------------------
+// Procedure: GetNewHumanizeVal
+//
+//-----------------------------------------------------
+void MasterClockx4::GetNewHumanizeVal( void )
+{
+ m_fHumanize = randomUniform() * engineGetSampleRate() * 0.1 * params[ PARAM_HUMANIZE ].value;
+
+ if( randomUniform() > 0.5 )
+ m_fHumanize *= -1;
+}
+
+//-----------------------------------------------------
+// Procedure: BMPChange
+//
+//-----------------------------------------------------
+void MasterClockx4::BPMChange( float fbpm, bool bforce )
+{
+ // don't change if it is already the same
+ if( !bforce && ( (int)(fbpm * 1000.0f ) == (int)(m_fBPM * 1000.0f ) ) )
+ return;
+
+ m_fBPM = fbpm;
+ m_fBeatsPers = fbpm / 60.0;
+
+ if( m_pDigitDisplayBPM )
+ m_pDigitDisplayBPM->SetFloat( m_fBPM );
+
+ for( int i = 0; i < nCHANNELS; i++ )
+ CalcChannelClockRate( i );
+}
+
+//-----------------------------------------------------
+// Procedure: CalcChannelClockRate
+//
+//-----------------------------------------------------
+void MasterClockx4::CalcChannelClockRate( int ch )
+{
+ // for beat division just keep a count of beats
+ if( m_ChannelMultSelect[ ch ] <= 12 )
+ m_ChannelDivBeatCount[ ch ] = multdisplayval[ m_ChannelMultSelect[ ch ] ];
+ else
+ m_fChannelBeatsPers[ ch ] = m_fBeatsPers * (float)multdisplayval[ m_ChannelMultSelect[ ch ] ];
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void MasterClockx4::step()
+{
+ int ch;
+ float fSyncPulseOut, fClkPulseOut;
+ bool bMainClockTrig = false, bChannelClockTrig;
+
+ if( !m_bInitialized )
+ return;
+
+ // use the input chain trigger for our clock
+ if( inputs[ INPUT_CHAIN ].active )
+ {
+ if( !m_bWasChained )
+ {
+ m_pHumanKnob->visible = false;
+ m_pBpmKnob->visible = false;
+ }
+
+ m_bWasChained = true;
+
+ // value of less than zero is a trig
+ if( inputs[ INPUT_CHAIN ].value < 10.0 )
+ {
+ bMainClockTrig = true;
+ }
+ // values greater than zero are the bpm
+ else
+ {
+ BPMChange( inputs[ INPUT_CHAIN ].value, false );
+ }
+ }
+ else
+ {
+ // go back to our bpm if chain removed
+ if( m_bWasChained )
+ {
+ m_pHumanKnob->visible = true;
+ m_pBpmKnob->visible = true;
+ m_bWasChained = false;
+ BPMChange( params[ PARAM_BPM ].value, false );
+ }
+
+ // keep track of main bpm
+ m_fMainClockCount += m_fBeatsPers;
+ if( ( m_fMainClockCount + m_fHumanize ) >= engineGetSampleRate() )
+ {
+ m_fMainClockCount = ( m_fMainClockCount + m_fHumanize ) - engineGetSampleRate();
+
+ GetNewHumanizeVal();
+
+ bMainClockTrig = true;
+ }
+ }
+
+ // send chain
+ if( outputs[ OUTPUT_CHAIN ].active )
+ {
+ if( bMainClockTrig )
+ outputs[ OUTPUT_CHAIN ].value = -1.0;
+ else
+ outputs[ OUTPUT_CHAIN ].value = m_fBPM;
+ }
+
+ for( ch = 0; ch < nCHANNELS; ch++ )
+ {
+ bChannelClockTrig = false;
+
+ // do multiple clocks
+ if( m_ChannelMultSelect[ ch ] > 12 )
+ {
+ m_fChannelClockCount[ ch ] += m_fChannelBeatsPers[ ch ];
+ if( m_fChannelClockCount[ ch ] >= engineGetSampleRate() )
+ {
+ m_fChannelClockCount[ ch ] = m_fChannelClockCount[ ch ] - engineGetSampleRate();
+ bChannelClockTrig = true;
+ }
+ }
+
+ // sync all triggers to main clock pulse
+ if( bMainClockTrig )
+ {
+ // divisions of clock will count beats
+ if( m_ChannelMultSelect[ ch ] <= 12 )
+ {
+ if( ++m_ChannelDivBeatCount[ ch ] >= multdisplayval[ m_ChannelMultSelect[ ch ] ] )
+ {
+ m_ChannelDivBeatCount[ ch ] = 0;
+ bChannelClockTrig = true;
+ }
+ }
+ // multiples of clock will sync with every beat
+ else
+ {
+ m_fChannelClockCount[ ch ] = 0;
+ bChannelClockTrig = true;
+ }
+ }
+
+ if( bChannelClockTrig )
+ m_PulseClock[ ch ].trigger(1e-3);
+
+ // blinky sync LED on input trig
+ if( m_SchmittSyncIn[ ch ].process( inputs[ INPUT_EXT_SYNC + ch ].value ) )
+ {
+ m_bChannelSync[ ch ] = true;
+
+ if( m_pButtonTrig[ ch ] )
+ m_pButtonTrig[ ch ]->Set( true );
+ }
+
+ // sync triggers
+ if( m_bChannelSync[ ch ] || m_bGlobalSync || m_SchmittSyncIn[ ch ].process( inputs[ INPUT_EXT_SYNC + ch ].value ) )
+ {
+ m_bChannelSync[ ch ] = false;
+ m_PulseSync[ ch ].trigger(1e-3);
+ }
+
+ // syncs
+ fSyncPulseOut = m_PulseSync[ ch ].process( 1.0 / engineGetSampleRate() ) ? CV_MAX : 0.0;
+ outputs[ OUTPUT_TRIG + (ch * 4) + 0 ].value = fSyncPulseOut;
+ outputs[ OUTPUT_TRIG + (ch * 4) + 1 ].value = fSyncPulseOut;
+ outputs[ OUTPUT_TRIG + (ch * 4) + 2 ].value = fSyncPulseOut;
+ outputs[ OUTPUT_TRIG + (ch * 4) + 3 ].value = fSyncPulseOut;
+
+ // clocks
+ fClkPulseOut = m_PulseClock[ ch ].process( 1.0 / engineGetSampleRate() ) ? CV_MAX : 0.0;
+
+ if( !m_bGlobalStopState && !m_bStopState[ ch ] )
+ {
+ outputs[ OUTPUT_CLK + (ch * 4) + 0 ].value = fClkPulseOut;
+ outputs[ OUTPUT_CLK + (ch * 4) + 1 ].value = fClkPulseOut;
+ outputs[ OUTPUT_CLK + (ch * 4) + 2 ].value = fClkPulseOut;
+ outputs[ OUTPUT_CLK + (ch * 4) + 3 ].value = fClkPulseOut;
+ }
+ }
+
+ m_bGlobalSync = false;
+}
diff --git a/src/Mixer_1x4_Stereo.cpp b/src/Mixer_1x4_Stereo.cpp
new file mode 100644
index 0000000..d64bb4e
--- /dev/null
+++ b/src/Mixer_1x4_Stereo.cpp
@@ -0,0 +1,1073 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define GROUPS 1
+#define CH_PER_GROUP 4
+#define CHANNELS ( GROUPS * CH_PER_GROUP )
+#define nAUX 4
+
+#define GROUP_OFF_X 52
+#define CHANNEL_OFF_X 34
+
+#define FADE_MULT (0.0005f)
+
+#define L 0
+#define R 1
+
+#define MUTE_FADE_STATE_IDLE 0
+#define MUTE_FADE_STATE_INC 1
+#define MUTE_FADE_STATE_DEC 2
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct Mix_1x4_Stereo : Module
+{
+ enum ParamIds
+ {
+ PARAM_MAIN_LEVEL,
+ PARAM_LEVEL_IN,
+ PARAM_PAN_IN = PARAM_LEVEL_IN + CHANNELS,
+ PARAM_GROUP_LEVEL_IN = PARAM_PAN_IN + CHANNELS,
+ PARAM_GROUP_PAN_IN = PARAM_GROUP_LEVEL_IN + GROUPS,
+ PARAM_MUTE_BUTTON = PARAM_GROUP_PAN_IN + GROUPS,
+ PARAM_SOLO_BUTTON = PARAM_MUTE_BUTTON + CHANNELS,
+ PARAM_GROUP_MUTE = PARAM_SOLO_BUTTON + CHANNELS,
+
+ PARAM_EQ_HI = PARAM_GROUP_MUTE + GROUPS,
+ PARAM_EQ_MD = PARAM_EQ_HI + CHANNELS,
+ PARAM_EQ_LO = PARAM_EQ_MD + CHANNELS,
+
+ PARAM_AUX_KNOB = PARAM_EQ_LO + CHANNELS,
+ PARAM_AUX_PREFADE = PARAM_AUX_KNOB + (GROUPS * nAUX),
+ PARAM_AUX_OUT = PARAM_AUX_PREFADE + (GROUPS * nAUX),
+
+ nPARAMS = PARAM_AUX_OUT + (nAUX)
+ };
+
+ enum InputIds
+ {
+ IN_LEFT,
+ IN_RIGHT = IN_LEFT + CHANNELS,
+ IN_LEVEL = IN_RIGHT + CHANNELS,
+ IN_PAN = IN_LEVEL + CHANNELS,
+ IN_GROUP_LEVEL = IN_PAN + CHANNELS,
+ IN_GROUP_PAN = IN_GROUP_LEVEL + GROUPS,
+ nINPUTS = IN_GROUP_PAN + GROUPS
+ };
+
+ enum OutputIds
+ {
+ OUT_MAINL,
+ OUT_MAINR,
+
+ OUT_AUXL,
+ OUT_AUXR = OUT_AUXL + nAUX,
+
+ nOUTPUTS = OUT_AUXR + nAUX
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ // mute buttons
+ bool m_bMuteStates[ CHANNELS ] = {};
+ int m_FadeState[ CHANNELS ] = {MUTE_FADE_STATE_IDLE};
+ float m_fMuteFade[ CHANNELS ] = {1.0};
+
+ // solo buttons
+ bool m_bSoloStates[ CHANNELS ] = {};
+
+ // group mute buttons
+ bool m_bGroupMuteStates[ GROUPS ] = {};
+ float m_fGroupMuteFade[ GROUPS ] = {};
+ int m_GroupFadeState[ GROUPS ] = {MUTE_FADE_STATE_IDLE};
+
+ // processing
+ bool m_bMono[ CHANNELS ];
+ float m_fSubMix[ GROUPS ][ 3 ] = {};
+
+ // aux
+ bool m_bGroupPreFadeAuxStates[ GROUPS ][ nAUX ] = {};
+
+ // LED Meters
+ LEDMeterWidget *m_pLEDMeterChannel[ CHANNELS ][ 2 ] ={};
+ LEDMeterWidget *m_pLEDMeterGroup[ GROUPS ][ 2 ] ={};
+ LEDMeterWidget *m_pLEDMeterMain[ 2 ] ={};
+
+ // buttons
+ MyLEDButton *m_pButtonChannelMute[ CHANNELS ] = {};
+ MyLEDButton *m_pButtonChannelSolo[ CHANNELS ] = {};
+ MyLEDButton *m_pButtonGroupMute[ GROUPS ] = {};
+ //MyLEDButton *m_pButtonGroupSolo[ GROUPS ] = {};
+ MyLEDButton *m_pButtonAuxPreFader[ GROUPS ][ nAUX ] = {};
+
+ // EQ Rez
+ float lp1[ CHANNELS ][ 2 ] = {}, bp1[ CHANNELS ][ 2 ] = {};
+ float m_hpIn[ CHANNELS ];
+ float m_lpIn[ CHANNELS ];
+ float m_mpIn[ CHANNELS ];
+ float m_rezIn[ CHANNELS ] = {0};
+ float m_Freq;
+
+#define L 0
+#define R 1
+
+ // Contructor
+ Mix_1x4_Stereo() : Module( nPARAMS, nINPUTS, nOUTPUTS, 0 ){}
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQHi_Knob : Green1_Tiny
+ {
+ Mix_1x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_1x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_1x4_Stereo::PARAM_EQ_HI;
+
+ mymodule->m_hpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQMid_Knob : Green1_Tiny
+ {
+ Mix_1x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_1x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_1x4_Stereo::PARAM_EQ_MD;
+ mymodule->m_mpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQLo_Knob : Green1_Tiny
+ {
+ Mix_1x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_1x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_1x4_Stereo::PARAM_EQ_LO;
+ mymodule->m_lpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override{}
+ void reset() override;
+
+ void ProcessMuteSolo( int channel, bool bMute, bool bGroup );
+ void ProcessEQ( int ch, float *pL, float *pR );
+};
+
+//-----------------------------------------------------
+// MyLEDButton_Aux
+//-----------------------------------------------------
+void MyLEDButton_Aux( void *pClass, int id, bool bOn )
+{
+ Mix_1x4_Stereo *mymodule;
+ mymodule = (Mix_1x4_Stereo*)pClass;
+
+ mymodule->m_bGroupPreFadeAuxStates[ 0 ][ id ] = !mymodule->m_bGroupPreFadeAuxStates[ 0 ][ id ];
+ mymodule->m_pButtonAuxPreFader[ 0 ][ id ]->Set( mymodule->m_bGroupPreFadeAuxStates[ 0 ][ id ] );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChMute
+//-----------------------------------------------------
+void MyLEDButton_ChMute( void *pClass, int id, bool bOn )
+{
+ Mix_1x4_Stereo *mymodule;
+ mymodule = (Mix_1x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, true, false );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChSolo
+//-----------------------------------------------------
+void MyLEDButton_ChSolo( void *pClass, int id, bool bOn )
+{
+ Mix_1x4_Stereo *mymodule;
+ mymodule = (Mix_1x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, false, false );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GroupMute
+//-----------------------------------------------------
+void MyLEDButton_GroupMute( void *pClass, int id, bool bOn )
+{
+ Mix_1x4_Stereo *mymodule;
+ mymodule = (Mix_1x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, true, true );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GroupSolo
+//-----------------------------------------------------
+/*void MyLEDButton_GroupSolo( void *pClass, int id, bool bOn )
+{
+ Mix_1x4_Stereo *mymodule;
+ mymodule = (Mix_1x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, false, true );
+}*/
+
+#define CUTOFF (0.025f)
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+#define AMP_MAX 2.0
+
+struct Mix_1x4_Stereo_Widget : ModuleWidget {
+ Mix_1x4_Stereo_Widget(Mix_1x4_Stereo *module) : ModuleWidget(module)
+{
+ float fx, fx2, fx3, fx5, fx7;
+ int ch, x, y, i, ybase, x2, y2;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/Mix_1x4_Stereo.svg")));
+
+ //module->lg.Open("Mix_1x4_Stereo.txt");
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ //----------------------------------------------------
+ // Add mix sliders
+ x = 23;
+ y = 38;
+
+ // main channel
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ // Left channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_1x4_Stereo::IN_LEFT + ch ) );
+
+ y += 25;
+
+ // Right channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_1x4_Stereo::IN_RIGHT + ch ) );
+
+ y += 26;
+
+ // Level knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_1x4_Stereo::PARAM_LEVEL_IN + ch, 0.0, AMP_MAX, 0.0 ) );
+
+ y += 31;
+
+ // Level inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_1x4_Stereo::IN_LEVEL + ch ) );
+
+ y += 23;
+
+ // pan knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_1x4_Stereo::PARAM_PAN_IN + ch, -1.0, 1.0, 0.0 ) );
+
+ y += 31;
+
+ // Pan inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_1x4_Stereo::IN_PAN + ch ) );
+
+ y += 22;
+
+ // mute buttons
+ module->m_pButtonChannelMute[ ch ] = new MyLEDButton( x - 5, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_ChMute );
+ addChild( module->m_pButtonChannelMute[ ch ] );
+ //y += 26;
+
+ // solo buttons
+ module->m_pButtonChannelSolo[ ch ] = new MyLEDButton( x + 11, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_ChSolo );
+ addChild( module->m_pButtonChannelSolo[ ch ] );
+
+ y += 22;
+ y2 = y;
+
+ // eq and rez
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_1x4_Stereo::PARAM_EQ_HI + ch, 0.0, 1.0, 0.5 ) );
+
+ y += 19;
+
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_1x4_Stereo::PARAM_EQ_MD + ch, 0.0, 1.0, 0.5 ) );
+
+ y += 19;
+
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_1x4_Stereo::PARAM_EQ_LO + ch, 0.0, 1.0, 0.5 ) );
+
+ // LED Meters
+ module->m_pLEDMeterChannel[ ch ][ 0 ] = new LEDMeterWidget( x + 13, y2 + 30, 4, 1, 1, true );
+ addChild( module->m_pLEDMeterChannel[ ch ][ 0 ] );
+ module->m_pLEDMeterChannel[ ch ][ 1 ] = new LEDMeterWidget( x + 18, y2 + 30, 4, 1, 1, true );
+ addChild( module->m_pLEDMeterChannel[ ch ][ 1 ] );
+
+ if( ( ch & 3 ) == 3 )
+ {
+ x += GROUP_OFF_X;
+ }
+ else
+ {
+ x += CHANNEL_OFF_X;
+ }
+
+ y = 38;
+ }
+
+ // group mixers
+ ybase = 278;
+ x = 12;
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // mute/solo buttons
+ x2 = x + 81;
+ y2 = ybase;
+
+ module->m_pButtonGroupMute[ i ] = new MyLEDButton( x2, y2 + 4, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, i, module, MyLEDButton_GroupMute );
+ addChild( module->m_pButtonGroupMute[ i ] );
+
+ // group level and pan inputs
+ x2 = x + 79;
+ y2 = ybase + 23;
+
+ // group VU Meters
+ module->m_pLEDMeterGroup[ i ][ 0 ] = new LEDMeterWidget( x2 + 2, y2 + 21, 5, 2, 1, true );
+ addChild( module->m_pLEDMeterGroup[ i ][ 0 ] );
+ module->m_pLEDMeterGroup[ i ][ 1 ] = new LEDMeterWidget( x2 + 9, y2 + 21, 5, 2, 1, true );
+ addChild( module->m_pLEDMeterGroup[ i ][ 1 ] );
+
+ // group level and pan knobs
+ x2 = x + 105;
+ y2 = ybase + 17;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_GROUP_LEVEL_IN + i, 0.0, AMP_MAX, 0.0 ) );
+
+ y2 += 32;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_GROUP_PAN_IN + i, -1.0, 1.0, 0.0 ) );
+
+ // aux 1/3
+#define AUX_H 29
+ x2 = x + 6;
+ y2 = ybase + 20;
+
+ module->m_pButtonAuxPreFader[ 0 ][ 0 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, 0, module, MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ 0 ][ 0 ] );
+
+ y2 += AUX_H;
+
+ module->m_pButtonAuxPreFader[ 0 ][ 2 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, 2, module, MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ 0 ][ 2 ] );
+
+ x2 = x + 20;
+ y2 = ybase + 16;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 0, 0.0, AMP_MAX, 0.0 ) );
+ y2 += AUX_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 2, 0.0, AMP_MAX, 0.0 ) );
+
+ // aux 2/4
+ x2 = x + 38;
+ y2 = ybase + 28;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 1, 0.0, AMP_MAX, 0.0 ) );
+ y2 += AUX_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 3, 0.0, AMP_MAX, 0.0 ) );
+
+ x2 = x + 62;
+ y2 = ybase + 32;
+
+ module->m_pButtonAuxPreFader[ 0 ][ 1 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, 1, module, MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ 0 ][ 1 ] );
+
+ y2 += AUX_H;
+
+ module->m_pButtonAuxPreFader[ 0 ][ 3 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, 3, module, MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ 0 ][ 3 ] );
+
+ // account for slight error in pixel conversion to svg area
+ x += 155;
+ }
+
+ // main mixer knob
+ addParam(ParamWidget::create( Vec( 161, 237 ), module, Mix_1x4_Stereo::PARAM_MAIN_LEVEL, 0.0, AMP_MAX, 0.0 ) );
+
+ module->m_pLEDMeterMain[ 0 ] = new LEDMeterWidget( 161 + 58, 242, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterMain[ 0 ] );
+ module->m_pLEDMeterMain[ 1 ] = new LEDMeterWidget( 161 + 65, 242, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterMain[ 1 ] );
+
+ // outputs
+ addChild(Port::create( Vec( 172, 305 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_MAINL ) );
+ addChild(Port::create( Vec( 204, 335 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_MAINR ) );
+
+ // AUX out
+#define AUX_OUT_H 42
+ x2 = 185;
+ y2 = 25;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_OUT + 0, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_OUT + 1, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_OUT + 2, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_1x4_Stereo::PARAM_AUX_OUT + 3, 0.0, AMP_MAX, 0.0 ) );
+
+ x2 = 171;
+ y2 = 45;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXL ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXL + 1 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXL + 2 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXL + 3 ) );
+
+ x2 = 200;
+ y2 = 45;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXR ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXR + 1 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXR + 2 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_1x4_Stereo::OUT_AUXR + 3 ) );
+
+ // calculate eq rez freq
+ fx = 3.141592 * (CUTOFF * 0.026315789473684210526315789473684) * 2 * 3.141592;
+ fx2 = fx*fx;
+ fx3 = fx2*fx;
+ fx5 = fx3*fx2;
+ fx7 = fx5*fx2;
+
+ module->m_Freq = 2.0 * (fx
+ - (fx3 * 0.16666666666666666666666666666667)
+ + (fx5 * 0.0083333333333333333333333333333333)
+ - (fx7 * 0.0001984126984126984126984126984127));
+
+ module->m_bInitialized = true;
+ module->reset();
+}
+};
+
+Model *modelMix_1x4_Stereo_Widget = Model::create( "mscHack", "Mix_1x4_Stereo", "MIXER 1x4 Stereo/Mono", MIXER_TAG, EQUALIZER_TAG, PANNING_TAG, AMPLIFIER_TAG, MULTIPLE_TAG );
+
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void Mix_1x4_Stereo::reset()
+{
+ int ch, i, aux;
+
+ if( !m_bInitialized )
+ return;
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+
+ m_pButtonChannelMute[ ch ]->Set( false );
+ m_pButtonChannelSolo[ ch ]->Set( false );
+
+ m_bMuteStates[ ch ] = false;
+ m_bSoloStates[ ch ] = false;
+ m_fMuteFade[ ch ] = 1.0;
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ for( aux = 0; aux < nAUX; aux++ )
+ {
+ m_bGroupPreFadeAuxStates[ i ][ aux ] = false;
+ m_pButtonAuxPreFader[ i ][ aux ]->Set( false );
+ }
+
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_IDLE;
+ m_pButtonGroupMute[ i ]->Set( false );
+ //m_pButtonGroupSolo[ i ]->Set( false );
+ m_bGroupMuteStates[ i ] = false;
+ m_fGroupMuteFade[ i ] = 1.0;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *Mix_1x4_Stereo::toJson()
+{
+ bool *pbool;
+ json_t *gatesJ;
+ json_t *rootJ = json_object();
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "Mix_1x4_Stereo_channel_mutes", gatesJ );
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "Mix_1x4_Stereo_channel_solos", gatesJ );
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "Mix_1x4_Stereo_group_mutes", gatesJ );
+
+ // AUX states
+ pbool = &m_bGroupPreFadeAuxStates[ 0 ][ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS * nAUX; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "Mix_1x4_Stereo_group_AUX_prefade_states", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void Mix_1x4_Stereo::fromJson(json_t *rootJ)
+{
+ int ch, i, aux;
+ bool *pbool;
+ json_t *StepsJ;
+ bool bSolo[ GROUPS ] = {0};
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "Mix_1x4_Stereo_channel_mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "Mix_1x4_Stereo_channel_solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "Mix_1x4_Stereo_group_mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // AUX states
+ pbool = &m_bGroupPreFadeAuxStates[ 0 ][ 0 ];
+
+ StepsJ = json_object_get( rootJ, "Mix_1x4_Stereo_group_AUX_prefade_states" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS * nAUX; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // anybody soloing?
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( m_bSoloStates[ ch ] )
+ {
+ bSolo[ ch / CH_PER_GROUP ] = true;
+ }
+ }
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( bSolo[ ch / CH_PER_GROUP ] )
+ {
+ // only open soloing channels
+ if( m_bSoloStates[ ch ] )
+ m_fMuteFade[ ch ] = 1.0;
+ else
+ m_fMuteFade[ ch ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fMuteFade[ ch ] = m_bMuteStates[ ch ] ? 0.0: 1.0;
+ }
+
+ m_pButtonChannelMute[ ch ]->Set( m_bMuteStates[ ch ] );
+ m_pButtonChannelSolo[ ch ]->Set( m_bSoloStates[ ch ] );
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ for( aux = 0; aux < nAUX; aux++ )
+ m_pButtonAuxPreFader[ i ][ aux ]->Set( m_bGroupPreFadeAuxStates[ i ][ aux ] );
+
+ m_fGroupMuteFade[ i ] = m_bGroupMuteStates[ i ] ? 0.0: 1.0;
+ m_pButtonGroupMute[ i ]->Set( m_bGroupMuteStates[ i ] );
+ //m_pButtonGroupSolo[ i ]->Set( m_bGroupSoloStates[ i ] );
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessMuteSolo
+//
+//-----------------------------------------------------
+void Mix_1x4_Stereo::ProcessMuteSolo( int index, bool bMute, bool bGroup )
+{
+ int i, group, si, ei;
+ bool bSoloEnabled = false, bSoloOff = false;
+
+ if( bGroup )
+ {
+ if( bMute )
+ {
+ m_bGroupMuteStates[ index ] = !m_bGroupMuteStates[ index ];
+
+ // if mute is off then set volume
+ if( m_bGroupMuteStates[ index ] )
+ {
+ m_pButtonGroupMute[ index ]->Set( true );
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_pButtonGroupMute[ index ]->Set( false );
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // !bGroup
+ else
+ {
+ group = index / CH_PER_GROUP;
+
+ si = group * CH_PER_GROUP;
+ ei = si + CH_PER_GROUP;
+
+ if( bMute )
+ {
+ m_bMuteStates[ index ] = !m_bMuteStates[ index ];
+
+ // turn solo off
+ if( m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bSoloStates[ index ] = false;
+ m_pButtonChannelSolo[ index ]->Set( false );
+ }
+
+ // if mute is off then set volume
+ if( m_bMuteStates[ index ] )
+ {
+ m_pButtonChannelMute[ index ]->Set( true );
+ m_FadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_pButtonChannelMute[ index ]->Set( false );
+ m_FadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bSoloStates[ index ] = !m_bSoloStates[ index ];
+
+ // turn mute off
+ if( m_bMuteStates[ index ] )
+ {
+ m_bMuteStates[ index ] = false;
+ m_pButtonChannelMute[ index ]->Set( false );
+ }
+
+ // toggle solo
+ if( !m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_pButtonChannelSolo[ index ]->Set( false );
+ }
+ else
+ {
+ m_pButtonChannelSolo[ index ]->Set( true );
+ }
+ }
+
+ // is a track soloing?
+ for( i = si; i < ei; i++ )
+ {
+ if( m_bSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // shut down volume of all not in solo
+ if( !m_bSoloStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // bring back if not muted
+ if( !m_bMuteStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessEQ
+//
+//-----------------------------------------------------
+#define MULTI (0.33333333333333333333333333333333f)
+void Mix_1x4_Stereo::ProcessEQ( int ch, float *pL, float *pR )
+{
+ float rez, hp1;
+ float input[ 2 ], out[ 2 ], lowpass, bandpass, highpass;
+
+ input[ L ] = *pL / AUDIO_MAX;
+ input[ R ] = *pR / AUDIO_MAX;
+
+ rez = 1.00;
+
+ // do left and right channels
+ for( int i = 0; i < 2; i++ )
+ {
+ input[ i ] = input[ i ] + 0.000000001;
+
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+ lowpass = lp1[ ch ][ i ];
+ highpass = hp1;
+ bandpass = bp1[ ch ][ i ];
+
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+ lowpass = lowpass + lp1[ ch ][ i ];
+ highpass = highpass + hp1;
+ bandpass = bandpass + bp1[ ch ][ i ];
+
+ input[ i ] = input[ i ] - 0.000000001;
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+
+ lowpass = (lowpass + lp1[ ch ][ i ]) * MULTI;
+ highpass = (highpass + hp1) * MULTI;
+ bandpass = (bandpass + bp1[ ch ][ i ]) * MULTI;
+
+ out[ i ] = ( highpass * m_hpIn[ ch ] ) + ( lowpass * m_lpIn[ ch ] ) + ( bandpass * m_mpIn[ ch ] );
+ }
+
+ *pL = clamp( out[ L ] * AUDIO_MAX, -AUDIO_MAX, AUDIO_MAX );
+ *pR = clamp( out[ R ] * AUDIO_MAX, -AUDIO_MAX, AUDIO_MAX );
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void Mix_1x4_Stereo::step()
+{
+ int ch, group, aux;
+ float inL = 0.0, inR = 0.0, inLClean, inRClean, outL, outR, mainL = 0.0, mainR = 0.0;
+ float inLvl, inPan;
+ float auxL[ nAUX ] = {}, auxR[ nAUX ] = {};
+ bool bGroupActive[ GROUPS ] = {0};
+
+ if( !m_bInitialized )
+ return;
+
+ memset( m_fSubMix, 0, sizeof(m_fSubMix) );
+
+ // channel mixers
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ group = ch / CH_PER_GROUP;
+
+ inLClean = 0.0;
+ inRClean = 0.0;
+ inL = 0.0;
+ inR = 0.0;
+
+ if( inputs[ IN_RIGHT + ch ].active || inputs[ IN_LEFT + ch ].active )
+ {
+ inLvl = clamp( ( params[ PARAM_LEVEL_IN + ch ].value + ( inputs[ IN_LEVEL + ch ].normalize( 0.0 ) / CV_MAX ) ), 0.0, AMP_MAX );
+
+ bGroupActive[ group ] = true;
+
+ // check right channel first for possible mono
+ if( inputs[ IN_RIGHT + ch ].active )
+ {
+ inRClean = inputs[ IN_RIGHT + ch ].value;
+ inR = inRClean * inLvl;
+ m_bMono[ ch ] = false;
+ }
+ else
+ m_bMono[ ch ] = true;
+
+ // left channel
+ if( inputs[ IN_LEFT + ch ].active )
+ {
+ inLClean = inputs[ IN_LEFT + ch ].value;
+ inL = inLClean * inLvl;
+
+ if( m_bMono[ ch ] )
+ {
+ inRClean = inLClean;
+ inR = inL;
+ }
+ }
+
+ // put output to aux if pre fader
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ if( m_bGroupPreFadeAuxStates[ group ][ aux ] )
+ {
+ auxL[ aux ] += inLClean * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ auxR[ aux ] += inRClean * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ }
+ }
+
+ if( m_FadeState[ ch ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fMuteFade[ ch ] -= FADE_MULT;
+
+ if( m_fMuteFade[ ch ] <= 0.0 )
+ {
+ m_fMuteFade[ ch ] = 0.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_FadeState[ ch ] == MUTE_FADE_STATE_INC )
+ {
+ m_fMuteFade[ ch ] += FADE_MULT;
+
+ if( m_fMuteFade[ ch ] >= 1.0 )
+ {
+ m_fMuteFade[ ch ] = 1.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ ProcessEQ( ch, &inL, &inR );
+
+ inL *= m_fMuteFade[ ch ];
+ inR *= m_fMuteFade[ ch ];
+
+ // pan
+ inPan = clamp( params[ PARAM_PAN_IN + ch ].value + ( inputs[ IN_PAN + ch ].normalize( 0.0 ) / CV_MAX ), -1.0, 1.0 );
+
+ //lg.f("pan = %.3f\n", inputs[ IN_PAN + ch ].value );
+
+ if( inPan <= 0.0 )
+ inR *= ( 1.0 + inPan );
+ else
+ inL *= ( 1.0 - inPan );
+
+ // put output to aux if not pre fader
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ if( !m_bGroupPreFadeAuxStates[ group ][ aux ] )
+ {
+ auxL[ aux ] += inL * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ auxR[ aux ] += inR * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ }
+ }
+ }
+ // this channel not active
+ else
+ {
+
+ }
+
+ m_fSubMix[ group ][ L ] += inL;
+ m_fSubMix[ group ][ R ] += inR;
+
+ if( m_pLEDMeterChannel[ ch ][ 0 ] )
+ m_pLEDMeterChannel[ ch ][ 0 ]->Process( inL / AUDIO_MAX );
+ if( m_pLEDMeterChannel[ ch ][ 1 ] )
+ m_pLEDMeterChannel[ ch ][ 1 ]->Process( inR / AUDIO_MAX);
+ }
+
+ // group mixers
+ for ( group = 0; group < GROUPS; group++ )
+ {
+ outL = 0.0;
+ outR = 0.0;
+
+ if( bGroupActive[ group ] )
+ {
+ inLvl = clamp( ( params[ PARAM_GROUP_LEVEL_IN + group ].value + ( inputs[ IN_GROUP_LEVEL + group ].normalize( 0.0 ) / CV_MAX ) ), 0.0, AMP_MAX );
+
+ outL = m_fSubMix[ group ][ L ] * inLvl;
+ outR = m_fSubMix[ group ][ R ] * inLvl;
+
+ // pan
+ inPan = clamp( params[ PARAM_GROUP_PAN_IN + group ].value + ( inputs[ IN_GROUP_PAN + group ].normalize( 0.0 ) / CV_MAX ), -1.0, 1.0 );
+
+ if( inPan <= 0.0 )
+ outR *= ( 1.0 + inPan );
+ else
+ outL *= ( 1.0 - inPan );
+
+ if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fGroupMuteFade[ group ] -= FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] <= 0.0 )
+ {
+ m_fGroupMuteFade[ group ] = 0.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_INC )
+ {
+ m_fGroupMuteFade[ group ] += FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] >= 1.0 )
+ {
+ m_fGroupMuteFade[ group ] = 1.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ outL *= m_fGroupMuteFade[ group ];
+ outR *= m_fGroupMuteFade[ group ];
+ }
+
+ if( m_pLEDMeterGroup[ group ][ 0 ] )
+ m_pLEDMeterGroup[ group ][ 0 ]->Process( outL / AUDIO_MAX );
+ if( m_pLEDMeterGroup[ group ][ 1 ] )
+ m_pLEDMeterGroup[ group ][ 1 ]->Process( outR / AUDIO_MAX );
+
+ mainL += outL;
+ mainR += outR;
+ }
+
+ if( m_pLEDMeterMain[ 0 ] )
+ m_pLEDMeterMain[ 0 ]->Process( ( mainL / AUDIO_MAX ) * params[ PARAM_MAIN_LEVEL ].value );
+ if( m_pLEDMeterMain[ 1 ] )
+ m_pLEDMeterMain[ 1 ]->Process( ( mainR / AUDIO_MAX ) * params[ PARAM_MAIN_LEVEL ].value );
+
+ // put aux output
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ outputs[ OUT_AUXL + aux ].value = clamp( auxL[ aux ] * params[ PARAM_AUX_OUT + aux ].value, -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_AUXR + aux ].value = clamp( auxR[ aux ] * params[ PARAM_AUX_OUT + aux ].value, -AUDIO_MAX, AUDIO_MAX );
+ }
+
+ outputs[ OUT_MAINL ].value = clamp( mainL * params[ PARAM_MAIN_LEVEL ].value, -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_MAINR ].value = clamp( mainR * params[ PARAM_MAIN_LEVEL ].value, -AUDIO_MAX, AUDIO_MAX );
+}
diff --git a/src/Mixer_2x4_Stereo.cpp b/src/Mixer_2x4_Stereo.cpp
new file mode 100644
index 0000000..a8aa5be
--- /dev/null
+++ b/src/Mixer_2x4_Stereo.cpp
@@ -0,0 +1,1211 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define GROUPS 2
+#define CH_PER_GROUP 4
+#define CHANNELS ( GROUPS * CH_PER_GROUP )
+#define nAUX 4
+
+#define GROUP_OFF_X 52
+#define CHANNEL_OFF_X 34
+
+#define FADE_MULT (0.0005f)
+
+#define L 0
+#define R 1
+
+#define MUTE_FADE_STATE_IDLE 0
+#define MUTE_FADE_STATE_INC 1
+#define MUTE_FADE_STATE_DEC 2
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct Mix_2x4_Stereo : Module
+{
+ enum ParamIds
+ {
+ PARAM_MAIN_LEVEL,
+ PARAM_LEVEL_IN,
+ PARAM_PAN_IN = PARAM_LEVEL_IN + CHANNELS,
+ PARAM_GROUP_LEVEL_IN = PARAM_PAN_IN + CHANNELS,
+ PARAM_GROUP_PAN_IN = PARAM_GROUP_LEVEL_IN + GROUPS,
+ PARAM_MUTE_BUTTON = PARAM_GROUP_PAN_IN + GROUPS,
+ PARAM_SOLO_BUTTON = PARAM_MUTE_BUTTON + CHANNELS,
+ PARAM_GROUP_MUTE = PARAM_SOLO_BUTTON + CHANNELS,
+ PARAM_GROUP_SOLO = PARAM_GROUP_MUTE + GROUPS,
+
+ PARAM_EQ_HI = PARAM_GROUP_SOLO + GROUPS,
+ PARAM_EQ_MD = PARAM_EQ_HI + CHANNELS,
+ PARAM_EQ_LO = PARAM_EQ_MD + CHANNELS,
+
+ PARAM_AUX_KNOB = PARAM_EQ_LO + CHANNELS,
+ PARAM_AUX_PREFADE = PARAM_AUX_KNOB + (GROUPS * nAUX),
+ PARAM_AUX_OUT = PARAM_AUX_PREFADE + (GROUPS * nAUX),
+
+ nPARAMS = PARAM_AUX_OUT + (nAUX)
+ };
+
+ enum InputIds
+ {
+ IN_LEFT,
+ IN_RIGHT = IN_LEFT + CHANNELS,
+ IN_LEVEL = IN_RIGHT + CHANNELS,
+ IN_PAN = IN_LEVEL + CHANNELS,
+ IN_GROUP_LEVEL = IN_PAN + CHANNELS,
+ IN_GROUP_PAN = IN_GROUP_LEVEL + GROUPS,
+ nINPUTS = IN_GROUP_PAN + GROUPS
+ };
+
+ enum OutputIds
+ {
+ OUT_MAINL,
+ OUT_MAINR,
+
+ OUT_AUXL,
+ OUT_AUXR = OUT_AUXL + nAUX,
+
+ nOUTPUTS = OUT_AUXR + nAUX
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ // mute buttons
+ bool m_bMuteStates[ CHANNELS ] = {};
+ float m_fMuteFade[ CHANNELS ] = {1.0};
+
+ int m_FadeState[ CHANNELS ] = {MUTE_FADE_STATE_IDLE};
+
+ // solo buttons
+ bool m_bSoloStates[ CHANNELS ] = {};
+
+ // group mute buttons
+ bool m_bGroupMuteStates[ GROUPS ] = {};
+ float m_fGroupMuteFade[ GROUPS ] = {1.0};
+
+ int m_GroupFadeState[ GROUPS ] = {MUTE_FADE_STATE_IDLE};
+
+ // group solo buttons
+ bool m_bGroupSoloStates[ GROUPS ] = {};
+
+ // processing
+ bool m_bMono[ CHANNELS ];
+ float m_fSubMix[ GROUPS ][ 3 ] = {};
+
+ // aux
+ bool m_bGroupPreFadeAuxStates[ GROUPS ][ nAUX ] = {};
+
+ // LED Meters
+ LEDMeterWidget *m_pLEDMeterChannel[ CHANNELS ][ 2 ] ={};
+ LEDMeterWidget *m_pLEDMeterGroup[ GROUPS ][ 2 ] ={};
+ LEDMeterWidget *m_pLEDMeterMain[ 2 ] ={};
+
+ // EQ Rez
+ float lp1[ CHANNELS ][ 2 ] = {}, bp1[ CHANNELS ][ 2 ] = {};
+ float m_hpIn[ CHANNELS ];
+ float m_lpIn[ CHANNELS ];
+ float m_mpIn[ CHANNELS ];
+ float m_rezIn[ CHANNELS ] = {0};
+ float m_Freq;
+
+ // buttons
+ MyLEDButton *m_pButtonChannelMute[ CHANNELS ] = {};
+ MyLEDButton *m_pButtonChannelSolo[ CHANNELS ] = {};
+ MyLEDButton *m_pButtonGroupMute[ GROUPS ] = {};
+ MyLEDButton *m_pButtonGroupSolo[ GROUPS ] = {};
+ MyLEDButton *m_pButtonAuxPreFader[ GROUPS ][ nAUX ] = {};
+
+#define L 0
+#define R 1
+
+ // Contructor
+ Mix_2x4_Stereo() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0 ){}
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQHi_Knob : Green1_Tiny
+ {
+ Mix_2x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_2x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_2x4_Stereo::PARAM_EQ_HI;
+
+ mymodule->m_hpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQMid_Knob : Green1_Tiny
+ {
+ Mix_2x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_2x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_2x4_Stereo::PARAM_EQ_MD;
+ mymodule->m_mpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQLo_Knob : Green1_Tiny
+ {
+ Mix_2x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_2x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_2x4_Stereo::PARAM_EQ_LO;
+ mymodule->m_lpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override{}
+ void reset() override;
+
+ void ProcessMuteSolo( int channel, bool bMute, bool bGroup );
+ void ProcessEQ( int ch, float *pL, float *pR );
+};
+
+//-----------------------------------------------------
+// MyLEDButton_Aux
+//-----------------------------------------------------
+void Mix_2x4_Stereo_MyLEDButton_Aux( void *pClass, int id, bool bOn )
+{
+ int ch, i;
+
+ Mix_2x4_Stereo *mymodule;
+ mymodule = (Mix_2x4_Stereo*)pClass;
+
+ ch = id / nAUX;
+ i = id - (nAUX * ch);
+
+ mymodule->m_bGroupPreFadeAuxStates[ ch ][ i ] = !mymodule->m_bGroupPreFadeAuxStates[ ch ][ i ];
+ mymodule->m_pButtonAuxPreFader[ ch ][ i ]->Set( mymodule->m_bGroupPreFadeAuxStates[ ch ][ i ] );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChMute
+//-----------------------------------------------------
+void Mix_2x4_Stereo_MyLEDButton_ChMute( void *pClass, int id, bool bOn )
+{
+ Mix_2x4_Stereo *mymodule;
+ mymodule = (Mix_2x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, true, false );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChSolo
+//-----------------------------------------------------
+void Mix_2x4_Stereo_MyLEDButton_ChSolo( void *pClass, int id, bool bOn )
+{
+ Mix_2x4_Stereo *mymodule;
+ mymodule = (Mix_2x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, false, false );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GroupMute
+//-----------------------------------------------------
+void Mix_2x4_Stereo_MyLEDButton_GroupMute( void *pClass, int id, bool bOn )
+{
+ Mix_2x4_Stereo *mymodule;
+ mymodule = (Mix_2x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, true, true );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GroupSolo
+//-----------------------------------------------------
+void Mix_2x4_Stereo_MyLEDButton_GroupSolo( void *pClass, int id, bool bOn )
+{
+ Mix_2x4_Stereo *mymodule;
+ mymodule = (Mix_2x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, false, true );
+}
+
+#define CUTOFF (0.025f)
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+#define AMP_MAX 2.0
+
+struct Mix_2x4_Stereo_Widget : ModuleWidget {
+ Mix_2x4_Stereo_Widget(Mix_2x4_Stereo *module) : ModuleWidget(module)
+{
+ float fx, fx2, fx3, fx5, fx7;
+ int ch, x, y, i, ybase, x2, y2;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/Mix_2x4_Stereo.svg")));
+
+ //module->lg.Open("Mix_2x4_Stereo.txt");
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ //----------------------------------------------------
+ // Add mix sliders
+ x = 23;
+ y = 38;
+
+ // main channel
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ // Left channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_2x4_Stereo::IN_LEFT + ch ) );
+
+ y += 25;
+
+ // Right channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_2x4_Stereo::IN_RIGHT + ch ) );
+
+ y += 26;
+
+ // Level knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_2x4_Stereo::PARAM_LEVEL_IN + ch, 0.0, AMP_MAX, 0.0 ) );
+
+ y += 31;
+
+ // Level inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_2x4_Stereo::IN_LEVEL + ch ) );
+
+ y += 23;
+
+ // pan knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_2x4_Stereo::PARAM_PAN_IN + ch, -1.0, 1.0, 0.0 ) );
+
+ y += 31;
+
+ // Pan inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_2x4_Stereo::IN_PAN + ch ) );
+
+ y += 22;
+
+ // mute buttons
+ module->m_pButtonChannelMute[ ch ] = new MyLEDButton( x - 5, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, ch, module, Mix_2x4_Stereo_MyLEDButton_ChMute );
+ addChild( module->m_pButtonChannelMute[ ch ] );
+ //y += 26;
+
+ // solo buttons
+ module->m_pButtonChannelSolo[ ch ] = new MyLEDButton( x + 11, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, ch, module, Mix_2x4_Stereo_MyLEDButton_ChSolo );
+ addChild( module->m_pButtonChannelSolo[ ch ] );
+
+ y += 22;
+ y2 = y;
+
+ // eq and rez
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_2x4_Stereo::PARAM_EQ_HI + ch, 0.0, 1.0, 0.5 ) );
+
+ y += 19;
+
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_2x4_Stereo::PARAM_EQ_MD + ch, 0.0, 1.0, 0.5 ) );
+
+ y += 19;
+
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_2x4_Stereo::PARAM_EQ_LO + ch, 0.0, 1.0, 0.5 ) );
+
+ // LED Meters
+ module->m_pLEDMeterChannel[ ch ][ 0 ] = new LEDMeterWidget( x + 13, y2 + 30, 4, 1, 1, true );
+ addChild( module->m_pLEDMeterChannel[ ch ][ 0 ] );
+ module->m_pLEDMeterChannel[ ch ][ 1 ] = new LEDMeterWidget( x + 18, y2 + 30, 4, 1, 1, true );
+ addChild( module->m_pLEDMeterChannel[ ch ][ 1 ] );
+
+ if( ( ch & 3 ) == 3 )
+ {
+ x += GROUP_OFF_X;
+ }
+ else
+ {
+ x += CHANNEL_OFF_X;
+ }
+
+ y = 38;
+ }
+
+ // group mixera
+ ybase = 278;
+ x = 12;
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // mute/solo buttons
+ x2 = x + 81;
+ y2 = ybase;
+
+ module->m_pButtonGroupMute[ i ] = new MyLEDButton( x2, y2 + 4, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, i, module, Mix_2x4_Stereo_MyLEDButton_GroupMute );
+ addChild( module->m_pButtonGroupMute[ i ] );
+ x2 += 28;
+
+ module->m_pButtonGroupSolo[ i ] = new MyLEDButton( x2, y2 + 4, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, i, module, Mix_2x4_Stereo_MyLEDButton_GroupSolo );
+ addChild( module->m_pButtonGroupSolo[ i ] );
+
+ // group level and pan inputs
+ x2 = x + 79;
+ y2 = ybase + 23;
+
+ // group VU Meters
+ module->m_pLEDMeterGroup[ i ][ 0 ] = new LEDMeterWidget( x2 + 2, y2 + 21, 5, 2, 1, true );
+ addChild( module->m_pLEDMeterGroup[ i ][ 0 ] );
+ module->m_pLEDMeterGroup[ i ][ 1 ] = new LEDMeterWidget( x2 + 9, y2 + 21, 5, 2, 1, true );
+ addChild( module->m_pLEDMeterGroup[ i ][ 1 ] );
+
+ // group level and pan knobs
+ x2 = x + 105;
+ y2 = ybase + 17;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_GROUP_LEVEL_IN + i, 0.0, AMP_MAX, 0.0 ) );
+
+ y2 += 32;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_GROUP_PAN_IN + i, -1.0, 1.0, 0.0 ) );
+
+ // aux 1/3
+#define AUX_H 29
+ x2 = x + 6;
+ y2 = ybase + 20;
+
+ module->m_pButtonAuxPreFader[ i ][ 0 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 0, module, Mix_2x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 0 ] );
+
+ y2 += AUX_H;
+
+ module->m_pButtonAuxPreFader[ i ][ 2 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 2, module, Mix_2x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 2 ] );
+
+ x2 = x + 20;
+ y2 = ybase + 16;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 0, 0.0, AMP_MAX, 0.0 ) );
+ y2 += AUX_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 2, 0.0, AMP_MAX, 0.0 ) );
+
+ // aux 2/4
+ x2 = x + 38;
+ y2 = ybase + 28;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 1, 0.0, AMP_MAX, 0.0 ) );
+ y2 += AUX_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 3, 0.0, AMP_MAX, 0.0 ) );
+
+ x2 = x + 62;
+ y2 = ybase + 32;
+
+ module->m_pButtonAuxPreFader[ i ][ 1 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 1, module, Mix_2x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 1 ] );
+
+ y2 += AUX_H;
+
+ module->m_pButtonAuxPreFader[ i ][ 3 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 3, module, Mix_2x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 3 ] );
+
+ // account for slight error in pixel conversion to svg area
+ x += 155;
+ }
+
+ // main mixer knob
+ addParam(ParamWidget::create( Vec( 317, 237 ), module, Mix_2x4_Stereo::PARAM_MAIN_LEVEL, 0.0, AMP_MAX, 0.0 ) );
+
+ module->m_pLEDMeterMain[ 0 ] = new LEDMeterWidget( 317 + 58, 242, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterMain[ 0 ] );
+ module->m_pLEDMeterMain[ 1 ] = new LEDMeterWidget( 317 + 65, 242, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterMain[ 1 ] );
+
+ // outputs
+
+ addChild(Port::create( Vec( 327, 305 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_MAINL ) );
+ addChild(Port::create( Vec( 359, 335 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_MAINR ) );
+
+ // AUX out
+#define AUX_OUT_H 42
+ x2 = 340;
+ y2 = 25;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_OUT + 0, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_OUT + 1, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_OUT + 2, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_2x4_Stereo::PARAM_AUX_OUT + 3, 0.0, AMP_MAX, 0.0 ) );
+
+ x2 = 326;
+ y2 = 45;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXL ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXL + 1 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXL + 2 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXL + 3 ) );
+
+ x2 = 355;
+ y2 = 45;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXR ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXR + 1 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXR + 2 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_2x4_Stereo::OUT_AUXR + 3 ) );
+
+ // calculate eq rez freq
+ fx = 3.141592 * (CUTOFF * 0.026315789473684210526315789473684) * 2 * 3.141592;
+ fx2 = fx*fx;
+ fx3 = fx2*fx;
+ fx5 = fx3*fx2;
+ fx7 = fx5*fx2;
+
+ module->m_Freq = 2.0 * (fx
+ - (fx3 * 0.16666666666666666666666666666667)
+ + (fx5 * 0.0083333333333333333333333333333333)
+ - (fx7 * 0.0001984126984126984126984126984127));
+
+ module->m_bInitialized = true;
+ module->reset();
+}
+};
+
+Model *modelMix_2x4_Stereo_Widget = Model::create( "mscHack", "Mix_2x4_Stereo", "MIXER 2x4 Stereo/Mono", MIXER_TAG, EQUALIZER_TAG, DUAL_TAG, PANNING_TAG, AMPLIFIER_TAG, MULTIPLE_TAG );
+
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void Mix_2x4_Stereo::reset()
+{
+ int ch, i, aux;
+
+ if( !m_bInitialized )
+ return;
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+
+ m_pButtonChannelMute[ ch ]->Set( false );
+ m_pButtonChannelSolo[ ch ]->Set( false );
+
+ m_bMuteStates[ ch ] = false;
+ m_bSoloStates[ ch ] = false;
+ m_fMuteFade[ ch ] = 1.0;
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ for( aux = 0; aux < nAUX; aux++ )
+ {
+ m_bGroupPreFadeAuxStates[ i ][ aux ] = false;
+ m_pButtonAuxPreFader[ i ][ aux ]->Set( false );
+ }
+
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_IDLE;
+ m_pButtonGroupMute[ i ]->Set( false );
+ m_pButtonGroupSolo[ i ]->Set( false );
+ m_bGroupMuteStates[ i ] = false;
+ m_fGroupMuteFade[ i ] = 1.0;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *Mix_2x4_Stereo::toJson()
+{
+ bool *pbool;
+ json_t *gatesJ;
+ json_t *rootJ = json_object();
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "channel mutes", gatesJ );
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "channel solos", gatesJ );
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group mutes", gatesJ );
+
+ // group solos
+ pbool = &m_bGroupSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group solos", gatesJ );
+
+ // AUX states
+ pbool = &m_bGroupPreFadeAuxStates[ 0 ][ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS * nAUX; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group AUX prefade states", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void Mix_2x4_Stereo::fromJson(json_t *rootJ)
+{
+ int ch, i, aux;
+ bool *pbool;
+ json_t *StepsJ;
+ bool bSolo[ GROUPS ] = {0}, bGroupSolo = false;
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "channel mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "channel solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group solos
+ pbool = &m_bGroupSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // AUX states
+ pbool = &m_bGroupPreFadeAuxStates[ 0 ][ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group AUX prefade states" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS * nAUX; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // anybody soloing?
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( m_bSoloStates[ ch ] )
+ {
+ bSolo[ ch / CH_PER_GROUP ] = true;
+ }
+ }
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( bSolo[ ch / CH_PER_GROUP ] )
+ {
+ // only open soloing channels
+ if( m_bSoloStates[ ch ] )
+ m_fMuteFade[ ch ] = 1.0;
+ else
+ m_fMuteFade[ ch ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fMuteFade[ ch ] = m_bMuteStates[ ch ] ? 0.0: 1.0;
+ }
+
+ m_pButtonChannelMute[ ch ]->Set( m_bMuteStates[ ch ] );
+ m_pButtonChannelSolo[ ch ]->Set( m_bSoloStates[ ch ] );
+ }
+
+ // anybody group soloing?
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( m_bGroupSoloStates[ i ] )
+ {
+ bGroupSolo = true;
+ break;
+ }
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ for( aux = 0; aux < nAUX; aux++ )
+ m_pButtonAuxPreFader[ i ][ aux ]->Set( m_bGroupPreFadeAuxStates[ i ][ aux ] );
+
+ if( bGroupSolo )
+ {
+ // only open soloing channels
+ if( m_bGroupSoloStates[ i ] )
+ m_fGroupMuteFade[ i ] = 1.0;
+ else
+ m_fGroupMuteFade[ i ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fGroupMuteFade[ i ] = m_bGroupMuteStates[ i ] ? 0.0: 1.0;
+ }
+
+ m_pButtonGroupMute[ i ]->Set( m_bGroupMuteStates[ i ] );
+ m_pButtonGroupSolo[ i ]->Set( m_bGroupSoloStates[ i ] );
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessMuteSolo
+//
+//-----------------------------------------------------
+void Mix_2x4_Stereo::ProcessMuteSolo( int index, bool bMute, bool bGroup )
+{
+ int i, group, si, ei;
+ bool bSoloEnabled = false, bSoloOff = false;
+
+ if( bGroup )
+ {
+ if( bMute )
+ {
+ m_bGroupMuteStates[ index ] = !m_bGroupMuteStates[ index ];
+
+ // turn solo off
+ if( m_bGroupSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bGroupSoloStates[ index ] = false;
+ m_pButtonGroupSolo[ index ]->Set( false );
+ }
+
+ // if mute is off then set volume
+ if( m_bGroupMuteStates[ index ] )
+ {
+ m_pButtonGroupMute[ index ]->Set( true );
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_pButtonGroupMute[ index ]->Set( false );
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bGroupSoloStates[ index ] = !m_bGroupSoloStates[ index ];
+
+ // turn mute off
+ if( m_bGroupMuteStates[ index ] )
+ {
+ m_bGroupMuteStates[ index ] = false;
+ m_pButtonGroupMute[ index ]->Set( false );
+ }
+
+ // shut down volume of all groups not in solo
+ if( !m_bGroupSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_pButtonGroupSolo[ index ]->Set( false );
+ }
+ else
+ {
+ m_pButtonGroupSolo[ index ]->Set( true );
+ }
+ }
+
+ // is a track soloing?
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( m_bGroupSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // shut down volume of all groups not in solo
+ if( !m_bGroupSoloStates[ i ] )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // bring back if not muted
+ if( !m_bGroupMuteStates[ i ] )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+ // !bGroup
+ else
+ {
+ group = index / CH_PER_GROUP;
+
+ si = group * CH_PER_GROUP;
+ ei = si + CH_PER_GROUP;
+
+ if( bMute )
+ {
+ m_bMuteStates[ index ] = !m_bMuteStates[ index ];
+
+ // turn solo off
+ if( m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bSoloStates[ index ] = false;
+ m_pButtonChannelSolo[ index ]->Set( false );
+ }
+
+ // if mute is off then set volume
+ if( m_bMuteStates[ index ] )
+ {
+ m_pButtonChannelMute[ index ]->Set( true );
+ m_FadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_pButtonChannelMute[ index ]->Set( false );
+ m_FadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bSoloStates[ index ] = !m_bSoloStates[ index ];
+
+ // turn mute off
+ if( m_bMuteStates[ index ] )
+ {
+ m_bMuteStates[ index ] = false;
+ m_pButtonChannelMute[ index ]->Set( false );
+ }
+
+ // toggle solo
+ if( !m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_pButtonChannelSolo[ index ]->Set( false );
+ }
+ else
+ {
+ m_pButtonChannelSolo[ index ]->Set( true );
+ }
+ }
+
+ // is a track soloing?
+ for( i = si; i < ei; i++ )
+ {
+ if( m_bSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // shut down volume of all not in solo
+ if( !m_bSoloStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // bring back if not muted
+ if( !m_bMuteStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessEQ
+//
+//-----------------------------------------------------
+#define MULTI (0.33333333333333333333333333333333f)
+void Mix_2x4_Stereo::ProcessEQ( int ch, float *pL, float *pR )
+{
+ float rez, hp1;
+ float input[ 2 ], out[ 2 ], lowpass, bandpass, highpass;
+
+ input[ L ] = *pL / AUDIO_MAX;
+ input[ R ] = *pR / AUDIO_MAX;
+
+ rez = 1.00;
+
+ // do left and right channels
+ for( int i = 0; i < 2; i++ )
+ {
+ input[ i ] = input[ i ] + 0.000000001;
+
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+ lowpass = lp1[ ch ][ i ];
+ highpass = hp1;
+ bandpass = bp1[ ch ][ i ];
+
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+ lowpass = lowpass + lp1[ ch ][ i ];
+ highpass = highpass + hp1;
+ bandpass = bandpass + bp1[ ch ][ i ];
+
+ input[ i ] = input[ i ] - 0.000000001;
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+
+ lowpass = (lowpass + lp1[ ch ][ i ]) * MULTI;
+ highpass = (highpass + hp1) * MULTI;
+ bandpass = (bandpass + bp1[ ch ][ i ]) * MULTI;
+
+ out[ i ] = ( highpass * m_hpIn[ ch ] ) + ( lowpass * m_lpIn[ ch ] ) + ( bandpass * m_mpIn[ ch ] );
+ }
+
+ *pL = clamp( out[ L ] * AUDIO_MAX, -AUDIO_MAX, AUDIO_MAX );
+ *pR = clamp( out[ R ] * AUDIO_MAX, -AUDIO_MAX, AUDIO_MAX );
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void Mix_2x4_Stereo::step()
+{
+ int ch, group, aux;
+ float inL = 0.0, inR = 0.0, inLClean, inRClean, outL, outR, mainL = 0.0, mainR = 0.0;
+ float inLvl, inPan;
+ float auxL[ nAUX ] = {}, auxR[ nAUX ] = {};
+ bool bGroupActive[ GROUPS ] = {0};
+
+ if( !m_bInitialized )
+ return;
+
+ memset( m_fSubMix, 0, sizeof(m_fSubMix) );
+
+ // channel mixers
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ group = ch / CH_PER_GROUP;
+
+ inLClean = 0.0;
+ inRClean = 0.0;
+ inL = 0.0;
+ inR = 0.0;
+
+ if( inputs[ IN_RIGHT + ch ].active || inputs[ IN_LEFT + ch ].active )
+ {
+ inLvl = clamp( ( params[ PARAM_LEVEL_IN + ch ].value + ( inputs[ IN_LEVEL + ch ].normalize( 0.0 ) / CV_MAX ) ), 0.0, AMP_MAX );
+
+ bGroupActive[ group ] = true;
+
+ // check right channel first for possible mono
+ if( inputs[ IN_RIGHT + ch ].active )
+ {
+ inRClean = inputs[ IN_RIGHT + ch ].value;
+ inR = inRClean * inLvl;
+ m_bMono[ ch ] = false;
+ }
+ else
+ m_bMono[ ch ] = true;
+
+ // left channel
+ if( inputs[ IN_LEFT + ch ].active )
+ {
+ inLClean = inputs[ IN_LEFT + ch ].value;
+ inL = inLClean * inLvl;
+
+ if( m_bMono[ ch ] )
+ {
+ inRClean = inLClean;
+ inR = inL;
+ }
+ }
+
+ // put output to aux if pre fader
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ if( m_bGroupPreFadeAuxStates[ group ][ aux ] )
+ {
+ auxL[ aux ] += inLClean * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ auxR[ aux ] += inRClean * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ }
+ }
+
+ if( m_FadeState[ ch ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fMuteFade[ ch ] -= FADE_MULT;
+
+ if( m_fMuteFade[ ch ] <= 0.0 )
+ {
+ m_fMuteFade[ ch ] = 0.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_FadeState[ ch ] == MUTE_FADE_STATE_INC )
+ {
+ m_fMuteFade[ ch ] += FADE_MULT;
+
+ if( m_fMuteFade[ ch ] >= 1.0 )
+ {
+ m_fMuteFade[ ch ] = 1.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ ProcessEQ( ch, &inL, &inR );
+
+ inL *= m_fMuteFade[ ch ];
+ inR *= m_fMuteFade[ ch ];
+
+ // pan
+ inPan = clamp( params[ PARAM_PAN_IN + ch ].value + ( inputs[ IN_PAN + ch ].normalize( 0.0 ) / CV_MAX ), -1.0, 1.0 );
+
+ //lg.f("pan = %.3f\n", inputs[ IN_PAN + ch ].value );
+
+ if( inPan <= 0.0 )
+ inR *= ( 1.0 + inPan );
+ else
+ inL *= ( 1.0 - inPan );
+
+ // put output to aux if not pre fader
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ if( !m_bGroupPreFadeAuxStates[ group ][ aux ] )
+ {
+ auxL[ aux ] += inL * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ auxR[ aux ] += inR * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ }
+ }
+ }
+ // this channel not active
+ else
+ {
+
+ }
+
+ m_fSubMix[ group ][ L ] += inL;
+ m_fSubMix[ group ][ R ] += inR;
+
+ if( m_pLEDMeterChannel[ ch ][ 0 ] )
+ m_pLEDMeterChannel[ ch ][ 0 ]->Process( inL / AUDIO_MAX );
+ if( m_pLEDMeterChannel[ ch ][ 1 ] )
+ m_pLEDMeterChannel[ ch ][ 1 ]->Process( inR / AUDIO_MAX);
+ }
+
+ // group mixers
+ for ( group = 0; group < GROUPS; group++ )
+ {
+ outL = 0.0;
+ outR = 0.0;
+
+ if( bGroupActive[ group ] )
+ {
+ inLvl = clamp( ( params[ PARAM_GROUP_LEVEL_IN + group ].value + ( inputs[ IN_GROUP_LEVEL + group ].normalize( 0.0 ) / CV_MAX ) ), 0.0, AMP_MAX );
+
+ outL = m_fSubMix[ group ][ L ] * inLvl;
+ outR = m_fSubMix[ group ][ R ] * inLvl;
+
+ // pan
+ inPan = clamp( params[ PARAM_GROUP_PAN_IN + group ].value + ( inputs[ IN_GROUP_PAN + group ].normalize( 0.0 ) / CV_MAX ), -1.0, 1.0 );
+
+ if( inPan <= 0.0 )
+ outR *= ( 1.0 + inPan );
+ else
+ outL *= ( 1.0 - inPan );
+
+ if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fGroupMuteFade[ group ] -= FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] <= 0.0 )
+ {
+ m_fGroupMuteFade[ group ] = 0.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_INC )
+ {
+ m_fGroupMuteFade[ group ] += FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] >= 1.0 )
+ {
+ m_fGroupMuteFade[ group ] = 1.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ outL *= m_fGroupMuteFade[ group ];
+ outR *= m_fGroupMuteFade[ group ];
+ }
+
+ if( m_pLEDMeterGroup[ group ][ 0 ] )
+ m_pLEDMeterGroup[ group ][ 0 ]->Process( outL / AUDIO_MAX );
+ if( m_pLEDMeterGroup[ group ][ 1 ] )
+ m_pLEDMeterGroup[ group ][ 1 ]->Process( outR / AUDIO_MAX );
+
+ mainL += outL;
+ mainR += outR;
+ }
+
+ if( m_pLEDMeterMain[ 0 ] )
+ m_pLEDMeterMain[ 0 ]->Process( ( mainL / AUDIO_MAX ) * params[ PARAM_MAIN_LEVEL ].value );
+ if( m_pLEDMeterMain[ 1 ] )
+ m_pLEDMeterMain[ 1 ]->Process( ( mainR / AUDIO_MAX ) * params[ PARAM_MAIN_LEVEL ].value );
+
+ // put aux output
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ outputs[ OUT_AUXL + aux ].value = clamp( auxL[ aux ] * params[ PARAM_AUX_OUT + aux ].value, -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_AUXR + aux ].value = clamp( auxR[ aux ] * params[ PARAM_AUX_OUT + aux ].value, -AUDIO_MAX, AUDIO_MAX );
+ }
+
+ outputs[ OUT_MAINL ].value = clamp( mainL * params[ PARAM_MAIN_LEVEL ].value, -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_MAINR ].value = clamp( mainR * params[ PARAM_MAIN_LEVEL ].value, -AUDIO_MAX, AUDIO_MAX );
+}
diff --git a/src/Mixer_4x4_Stereo.cpp b/src/Mixer_4x4_Stereo.cpp
new file mode 100644
index 0000000..93f9d05
--- /dev/null
+++ b/src/Mixer_4x4_Stereo.cpp
@@ -0,0 +1,1214 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define GROUPS 4
+#define CH_PER_GROUP 4
+#define CHANNELS ( GROUPS * CH_PER_GROUP )
+#define nAUX 4
+
+#define GROUP_OFF_X 52
+#define CHANNEL_OFF_X 34
+
+#define FADE_MULT (0.0005f)
+
+#define L 0
+#define R 1
+
+#define MUTE_FADE_STATE_IDLE 0
+#define MUTE_FADE_STATE_INC 1
+#define MUTE_FADE_STATE_DEC 2
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct Mix_4x4_Stereo : Module
+{
+ enum ParamIds
+ {
+ PARAM_MAIN_LEVEL,
+ PARAM_LEVEL_IN,
+ PARAM_PAN_IN = PARAM_LEVEL_IN + CHANNELS,
+ PARAM_GROUP_LEVEL_IN = PARAM_PAN_IN + CHANNELS,
+ PARAM_GROUP_PAN_IN = PARAM_GROUP_LEVEL_IN + GROUPS,
+ PARAM_MUTE_BUTTON = PARAM_GROUP_PAN_IN + GROUPS,
+ PARAM_SOLO_BUTTON = PARAM_MUTE_BUTTON + CHANNELS,
+ PARAM_GROUP_MUTE = PARAM_SOLO_BUTTON + CHANNELS,
+ PARAM_GROUP_SOLO = PARAM_GROUP_MUTE + GROUPS,
+
+ PARAM_EQ_HI = PARAM_GROUP_SOLO + GROUPS,
+ PARAM_EQ_MD = PARAM_EQ_HI + CHANNELS,
+ PARAM_EQ_LO = PARAM_EQ_MD + CHANNELS,
+
+ PARAM_AUX_KNOB = PARAM_EQ_LO + CHANNELS,
+ PARAM_AUX_PREFADE = PARAM_AUX_KNOB + (GROUPS * nAUX),
+ PARAM_AUX_OUT = PARAM_AUX_PREFADE + (GROUPS * nAUX),
+
+ nPARAMS = PARAM_AUX_OUT + (nAUX)
+ };
+
+ enum InputIds
+ {
+ IN_LEFT,
+ IN_RIGHT = IN_LEFT + CHANNELS,
+ IN_LEVEL = IN_RIGHT + CHANNELS,
+ IN_PAN = IN_LEVEL + CHANNELS,
+ IN_GROUP_LEVEL = IN_PAN + CHANNELS,
+ IN_GROUP_PAN = IN_GROUP_LEVEL + GROUPS,
+ nINPUTS = IN_GROUP_PAN + GROUPS
+ };
+
+ enum OutputIds
+ {
+ OUT_MAINL,
+ OUT_MAINR,
+
+ OUT_AUXL,
+ OUT_AUXR = OUT_AUXL + nAUX,
+
+ nOUTPUTS = OUT_AUXR + nAUX
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ // mute buttons
+ bool m_bMuteStates[ CHANNELS ] = {};
+ float m_fMuteFade[ CHANNELS ] = {};
+
+ int m_FadeState[ CHANNELS ] = {MUTE_FADE_STATE_IDLE};
+
+ // solo buttons
+ bool m_bSoloStates[ CHANNELS ] = {};
+
+ // group mute buttons
+ bool m_bGroupMuteStates[ GROUPS ] = {};
+ float m_fGroupMuteFade[ GROUPS ] = {};
+
+ int m_GroupFadeState[ GROUPS ] = {MUTE_FADE_STATE_IDLE};
+
+ // group solo buttons
+ bool m_bGroupSoloStates[ GROUPS ] = {};
+
+ // processing
+ bool m_bMono[ CHANNELS ];
+ float m_fSubMix[ GROUPS ][ 3 ] = {};
+
+ // aux
+ bool m_bGroupPreFadeAuxStates[ GROUPS ][ nAUX ] = {};
+
+ // LED Meters
+ LEDMeterWidget *m_pLEDMeterChannel[ CHANNELS ][ 2 ] ={};
+ LEDMeterWidget *m_pLEDMeterGroup[ GROUPS ][ 2 ] ={};
+ LEDMeterWidget *m_pLEDMeterMain[ 2 ] ={};
+
+ // EQ Rez
+ float lp1[ CHANNELS ][ 2 ] = {}, bp1[ CHANNELS ][ 2 ] = {};
+ float m_hpIn[ CHANNELS ];
+ float m_lpIn[ CHANNELS ];
+ float m_mpIn[ CHANNELS ];
+ float m_rezIn[ CHANNELS ] = {0};
+ float m_Freq;
+
+ // buttons
+ MyLEDButton *m_pButtonChannelMute[ CHANNELS ] = {};
+ MyLEDButton *m_pButtonChannelSolo[ CHANNELS ] = {};
+ MyLEDButton *m_pButtonGroupMute[ GROUPS ] = {};
+ MyLEDButton *m_pButtonGroupSolo[ GROUPS ] = {};
+ MyLEDButton *m_pButtonAuxPreFader[ GROUPS ][ nAUX ] = {};
+
+#define L 0
+#define R 1
+
+ // Contructor
+ Mix_4x4_Stereo() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0){}
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQHi_Knob : Green1_Tiny
+ {
+ Mix_4x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_4x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_4x4_Stereo::PARAM_EQ_HI;
+
+ mymodule->m_hpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQMid_Knob : Green1_Tiny
+ {
+ Mix_4x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_4x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_4x4_Stereo::PARAM_EQ_MD;
+ mymodule->m_mpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ //-----------------------------------------------------
+ // MyEQHi_Knob
+ //-----------------------------------------------------
+ struct MyEQLo_Knob : Green1_Tiny
+ {
+ Mix_4x4_Stereo *mymodule;
+ int param;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (Mix_4x4_Stereo*)module;
+
+ if( mymodule )
+ {
+ param = paramId - Mix_4x4_Stereo::PARAM_EQ_LO;
+ mymodule->m_lpIn[ param ] = value;
+ }
+
+ RoundKnob::onChange( e );
+ }
+ };
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override{}
+ void reset() override;
+
+ void ProcessMuteSolo( int channel, bool bMute, bool bGroup );
+ void ProcessEQ( int ch, float *pL, float *pR );
+};
+
+//-----------------------------------------------------
+// MyLEDButton_Aux
+//-----------------------------------------------------
+void Mix_4x4_Stereo_MyLEDButton_Aux( void *pClass, int id, bool bOn )
+{
+ int ch, i;
+
+ Mix_4x4_Stereo *mymodule;
+ mymodule = (Mix_4x4_Stereo*)pClass;
+
+ ch = id / nAUX;
+ i = id - (nAUX * ch);
+
+ mymodule->m_bGroupPreFadeAuxStates[ ch ][ i ] = !mymodule->m_bGroupPreFadeAuxStates[ ch ][ i ];
+ mymodule->m_pButtonAuxPreFader[ ch ][ i ]->Set( mymodule->m_bGroupPreFadeAuxStates[ ch ][ i ] );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChMute
+//-----------------------------------------------------
+void Mix_4x4_Stereo_MyLEDButton_ChMute( void *pClass, int id, bool bOn )
+{
+ Mix_4x4_Stereo *mymodule;
+ mymodule = (Mix_4x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, true, false );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_ChSolo
+//-----------------------------------------------------
+void Mix_4x4_Stereo_MyLEDButton_ChSolo( void *pClass, int id, bool bOn )
+{
+ Mix_4x4_Stereo *mymodule;
+ mymodule = (Mix_4x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, false, false );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GroupMute
+//-----------------------------------------------------
+void Mix_4x4_Stereo_MyLEDButton_GroupMute( void *pClass, int id, bool bOn )
+{
+ Mix_4x4_Stereo *mymodule;
+ mymodule = (Mix_4x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, true, true );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_GroupSolo
+//-----------------------------------------------------
+void Mix_4x4_Stereo_MyLEDButton_GroupSolo( void *pClass, int id, bool bOn )
+{
+ Mix_4x4_Stereo *mymodule;
+ mymodule = (Mix_4x4_Stereo*)pClass;
+ mymodule->ProcessMuteSolo( id, false, true );
+}
+
+#define CUTOFF (0.025f)
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+#define AMP_MAX 2.0
+
+struct Mix_4x4_Stereo_Widget : ModuleWidget {
+ Mix_4x4_Stereo_Widget(Mix_4x4_Stereo *module) : ModuleWidget(module)
+{
+ float fx, fx2, fx3, fx5, fx7;
+ int ch, x, y, i, ybase, x2, y2;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/Mix_4x4_Stereo.svg")));
+
+ //module->lg.Open("Mix_4x4_Stereo.txt");
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ //----------------------------------------------------
+ // Add mix sliders
+ x = 23;
+ y = 38;
+
+ // main channel
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ // Left channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo::IN_LEFT + ch ) );
+
+ y += 25;
+
+ // Right channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo::IN_RIGHT + ch ) );
+
+ y += 26;
+
+ // Level knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo::PARAM_LEVEL_IN + ch, 0.0, AMP_MAX, 0.0 ) );
+
+ y += 31;
+
+ // Level inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo::IN_LEVEL + ch ) );
+
+ y += 23;
+
+ // pan knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo::PARAM_PAN_IN + ch, -1.0, 1.0, 0.0 ) );
+
+ y += 31;
+
+ // Pan inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo::IN_PAN + ch ) );
+
+ y += 22;
+
+ // mute buttons
+ module->m_pButtonChannelMute[ ch ] = new MyLEDButton( x - 5, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, ch, module, Mix_4x4_Stereo_MyLEDButton_ChMute );
+ addChild( module->m_pButtonChannelMute[ ch ] );
+ //y += 26;
+
+ // solo buttons
+ module->m_pButtonChannelSolo[ ch ] = new MyLEDButton( x + 11, y, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, ch, module, Mix_4x4_Stereo_MyLEDButton_ChSolo );
+ addChild( module->m_pButtonChannelSolo[ ch ] );
+
+ y += 22;
+ y2 = y;
+
+ // eq and rez
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo::PARAM_EQ_HI + ch, 0.0, 1.0, 0.5 ) );
+
+ y += 19;
+
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo::PARAM_EQ_MD + ch, 0.0, 1.0, 0.5 ) );
+
+ y += 19;
+
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo::PARAM_EQ_LO + ch, 0.0, 1.0, 0.5 ) );
+
+ // LED Meters
+ module->m_pLEDMeterChannel[ ch ][ 0 ] = new LEDMeterWidget( x + 13, y2 + 30, 4, 1, 1, true );
+ addChild( module->m_pLEDMeterChannel[ ch ][ 0 ] );
+ module->m_pLEDMeterChannel[ ch ][ 1 ] = new LEDMeterWidget( x + 18, y2 + 30, 4, 1, 1, true );
+ addChild( module->m_pLEDMeterChannel[ ch ][ 1 ] );
+
+ if( ( ch & 3 ) == 3 )
+ {
+ x += GROUP_OFF_X;
+ }
+ else
+ {
+ x += CHANNEL_OFF_X;
+ }
+
+ y = 38;
+ }
+
+ // group mixera
+ ybase = 278;
+ x = 12;
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // mute/solo buttons
+ x2 = x + 81;
+ y2 = ybase;
+
+ module->m_pButtonGroupMute[ i ] = new MyLEDButton( x2, y2 + 4, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, i, module, Mix_4x4_Stereo_MyLEDButton_GroupMute );
+ addChild( module->m_pButtonGroupMute[ i ] );
+ x2 += 28;
+
+ module->m_pButtonGroupSolo[ i ] = new MyLEDButton( x2, y2 + 4, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButton::TYPE_SWITCH, i, module, Mix_4x4_Stereo_MyLEDButton_GroupSolo );
+ addChild( module->m_pButtonGroupSolo[ i ] );
+
+ // group level and pan inputs
+ x2 = x + 79;
+ y2 = ybase + 23;
+
+ // group VU Meters
+ module->m_pLEDMeterGroup[ i ][ 0 ] = new LEDMeterWidget( x2 + 2, y2 + 21, 5, 2, 1, true );
+ addChild( module->m_pLEDMeterGroup[ i ][ 0 ] );
+ module->m_pLEDMeterGroup[ i ][ 1 ] = new LEDMeterWidget( x2 + 9, y2 + 21, 5, 2, 1, true );
+ addChild( module->m_pLEDMeterGroup[ i ][ 1 ] );
+
+ // group level and pan knobs
+ x2 = x + 105;
+ y2 = ybase + 17;
+
+ addParam(ParamWidget::create( Vec( x2, y2 + 3 ), module, Mix_4x4_Stereo::PARAM_GROUP_LEVEL_IN + i, 0.0, AMP_MAX, 0.0 ) );
+
+ y2 += 32;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_GROUP_PAN_IN + i, -1.0, 1.0, 0.0 ) );
+
+ // aux 1/3
+#define AUX_H 29
+ x2 = x + 6;
+ y2 = ybase + 20;
+
+ module->m_pButtonAuxPreFader[ i ][ 0 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 0, module, Mix_4x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 0 ] );
+
+ y2 += AUX_H;
+
+ module->m_pButtonAuxPreFader[ i ][ 2 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 2, module, Mix_4x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 2 ] );
+
+ x2 = x + 20;
+ y2 = ybase + 16;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 0, 0.0, AMP_MAX, 0.0 ) );
+ y2 += AUX_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 2, 0.0, AMP_MAX, 0.0 ) );
+
+ // aux 2/4
+ x2 = x + 38;
+ y2 = ybase + 28;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 1, 0.0, AMP_MAX, 0.0 ) );
+ y2 += AUX_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_KNOB + (i * nAUX) + 3, 0.0, AMP_MAX, 0.0 ) );
+
+ x2 = x + 62;
+ y2 = ybase + 32;
+
+ module->m_pButtonAuxPreFader[ i ][ 1 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 1, module, Mix_4x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 1 ] );
+
+ y2 += AUX_H;
+
+ module->m_pButtonAuxPreFader[ i ][ 3 ] = new MyLEDButton( x2, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, (i * nAUX) + 3, module, Mix_4x4_Stereo_MyLEDButton_Aux );
+ addChild( module->m_pButtonAuxPreFader[ i ][ 3 ] );
+
+ // account for slight error in pixel conversion to svg area
+ x += 155;
+ }
+
+ //for( int i = 0; i < 15; i++ )
+ //module->lg.f("level %d = %.3f\n", i, module->m_pLEDMeterChannel[ 0 ][ 0 ]->flevels[ i ] );
+
+ // main mixer knob
+ addParam(ParamWidget::create( Vec( 626, 237 ), module, Mix_4x4_Stereo::PARAM_MAIN_LEVEL, 0.0, AMP_MAX, 0.0 ) );
+
+ module->m_pLEDMeterMain[ 0 ] = new LEDMeterWidget( 684, 242, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterMain[ 0 ] );
+ module->m_pLEDMeterMain[ 1 ] = new LEDMeterWidget( 691, 242, 5, 3, 2, true );
+ addChild( module->m_pLEDMeterMain[ 1 ] );
+
+ // outputs
+
+ addChild(Port::create( Vec( 636, 305 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_MAINL ) );
+ addChild(Port::create( Vec( 668, 335 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_MAINR ) );
+
+ // AUX out
+#define AUX_OUT_H 42
+ x2 = 649;
+ y2 = 25;
+
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_OUT + 0, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_OUT + 1, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_OUT + 2, 0.0, AMP_MAX, 0.0 ) ); y2 += AUX_OUT_H;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, Mix_4x4_Stereo::PARAM_AUX_OUT + 3, 0.0, AMP_MAX, 0.0 ) );
+
+ x2 = 635;
+ y2 = 45;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXL ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXL + 1 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXL + 2 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXL + 3 ) );
+
+ x2 = 664;
+ y2 = 45;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXR ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXR + 1 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXR + 2 ) ); y2 += AUX_OUT_H;
+ addChild(Port::create( Vec( x2, y2 ), Port::OUTPUT, module, Mix_4x4_Stereo::OUT_AUXR + 3 ) );
+
+ // calculate eq rez freq
+ fx = 3.141592 * (CUTOFF * 0.026315789473684210526315789473684) * 2 * 3.141592;
+ fx2 = fx*fx;
+ fx3 = fx2*fx;
+ fx5 = fx3*fx2;
+ fx7 = fx5*fx2;
+
+ module->m_Freq = 2.0 * (fx
+ - (fx3 * 0.16666666666666666666666666666667)
+ + (fx5 * 0.0083333333333333333333333333333333)
+ - (fx7 * 0.0001984126984126984126984126984127));
+
+ module->m_bInitialized = true;
+ module->reset();
+}
+};
+
+Model *modelMix_4x4_Stereo_Widget = Model::create( "mscHack", "Mix_4x4_Stereo(2)", "MIXER 4x4 Stereo/Mono", MIXER_TAG, EQUALIZER_TAG, QUAD_TAG, PANNING_TAG, AMPLIFIER_TAG, MULTIPLE_TAG );
+
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo::reset()
+{
+ int ch, i, aux;
+
+ if( !m_bInitialized )
+ return;
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+
+ m_pButtonChannelMute[ ch ]->Set( false );
+ m_pButtonChannelSolo[ ch ]->Set( false );
+
+ m_bMuteStates[ ch ] = false;
+ m_bSoloStates[ ch ] = false;
+ m_fMuteFade[ ch ] = 1.0;
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ for( aux = 0; aux < nAUX; aux++ )
+ {
+ m_bGroupPreFadeAuxStates[ i ][ aux ] = false;
+ m_pButtonAuxPreFader[ i ][ aux ]->Set( false );
+ }
+
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_IDLE;
+ m_pButtonGroupMute[ i ]->Set( false );
+ m_pButtonGroupSolo[ i ]->Set( false );
+ m_bGroupMuteStates[ i ] = false;
+ m_fGroupMuteFade[ i ] = 1.0;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *Mix_4x4_Stereo::toJson()
+{
+ bool *pbool;
+ json_t *gatesJ;
+ json_t *rootJ = json_object();
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "channel mutes", gatesJ );
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "channel solos", gatesJ );
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group mutes", gatesJ );
+
+ // group solos
+ pbool = &m_bGroupSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group solos", gatesJ );
+
+ // AUX states
+ pbool = &m_bGroupPreFadeAuxStates[ 0 ][ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS * nAUX; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group AUX prefade states", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo::fromJson(json_t *rootJ)
+{
+ int ch, i, aux;
+ bool *pbool;
+ json_t *StepsJ;
+ bool bSolo[ GROUPS ] = {0}, bGroupSolo = false;
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "channel mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "channel solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group solos
+ pbool = &m_bGroupSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // AUX states
+ pbool = &m_bGroupPreFadeAuxStates[ 0 ][ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group AUX prefade states" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS * nAUX; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // anybody soloing?
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( m_bSoloStates[ ch ] )
+ {
+ bSolo[ ch / CH_PER_GROUP ] = true;
+ }
+ }
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( bSolo[ ch / CH_PER_GROUP ] )
+ {
+ // only open soloing channels
+ if( m_bSoloStates[ ch ] )
+ m_fMuteFade[ ch ] = 1.0;
+ else
+ m_fMuteFade[ ch ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fMuteFade[ ch ] = m_bMuteStates[ ch ] ? 0.0: 1.0;
+ }
+
+ m_pButtonChannelMute[ ch ]->Set( m_bMuteStates[ ch ] );
+ m_pButtonChannelSolo[ ch ]->Set( m_bSoloStates[ ch ] );
+ }
+
+ // anybody group soloing?
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( m_bGroupSoloStates[ i ] )
+ {
+ bGroupSolo = true;
+ break;
+ }
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ for( aux = 0; aux < nAUX; aux++ )
+ m_pButtonAuxPreFader[ i ][ aux ]->Set( m_bGroupPreFadeAuxStates[ i ][ aux ] );
+
+ if( bGroupSolo )
+ {
+ // only open soloing channels
+ if( m_bGroupSoloStates[ i ] )
+ m_fGroupMuteFade[ i ] = 1.0;
+ else
+ m_fGroupMuteFade[ i ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fGroupMuteFade[ i ] = m_bGroupMuteStates[ i ] ? 0.0: 1.0;
+ }
+
+ m_pButtonGroupMute[ i ]->Set( m_bGroupMuteStates[ i ] );
+ m_pButtonGroupSolo[ i ]->Set( m_bGroupSoloStates[ i ] );
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessMuteSolo
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo::ProcessMuteSolo( int index, bool bMute, bool bGroup )
+{
+ int i, group, si, ei;
+ bool bSoloEnabled = false, bSoloOff = false;
+
+ if( bGroup )
+ {
+ if( bMute )
+ {
+ m_bGroupMuteStates[ index ] = !m_bGroupMuteStates[ index ];
+
+ // turn solo off
+ if( m_bGroupSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bGroupSoloStates[ index ] = false;
+ m_pButtonGroupSolo[ index ]->Set( false );
+ }
+
+ // if mute is off then set volume
+ if( m_bGroupMuteStates[ index ] )
+ {
+ m_pButtonGroupMute[ index ]->Set( true );
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_pButtonGroupMute[ index ]->Set( false );
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bGroupSoloStates[ index ] = !m_bGroupSoloStates[ index ];
+
+ // turn mute off
+ if( m_bGroupMuteStates[ index ] )
+ {
+ m_bGroupMuteStates[ index ] = false;
+ m_pButtonGroupMute[ index ]->Set( false );
+ }
+
+ // shut down volume of all groups not in solo
+ if( !m_bGroupSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_pButtonGroupSolo[ index ]->Set( false );
+ }
+ else
+ {
+ m_pButtonGroupSolo[ index ]->Set( true );
+ }
+ }
+
+ // is a track soloing?
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( m_bGroupSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // shut down volume of all groups not in solo
+ if( !m_bGroupSoloStates[ i ] )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // bring back if not muted
+ if( !m_bGroupMuteStates[ i ] )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+ // !bGroup
+ else
+ {
+ group = index / CH_PER_GROUP;
+
+ si = group * CH_PER_GROUP;
+ ei = si + CH_PER_GROUP;
+
+ if( bMute )
+ {
+ m_bMuteStates[ index ] = !m_bMuteStates[ index ];
+
+ // turn solo off
+ if( m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bSoloStates[ index ] = false;
+ m_pButtonChannelSolo[ index ]->Set( false );
+ }
+
+ // if mute is off then set volume
+ if( m_bMuteStates[ index ] )
+ {
+ m_pButtonChannelMute[ index ]->Set( true );
+ m_FadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_pButtonChannelMute[ index ]->Set( false );
+ m_FadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bSoloStates[ index ] = !m_bSoloStates[ index ];
+
+ // turn mute off
+ if( m_bMuteStates[ index ] )
+ {
+ m_bMuteStates[ index ] = false;
+ m_pButtonChannelMute[ index ]->Set( false );
+ }
+
+ // toggle solo
+ if( !m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_pButtonChannelSolo[ index ]->Set( false );
+ }
+ else
+ {
+ m_pButtonChannelSolo[ index ]->Set( true );
+ }
+ }
+
+ // is a track soloing?
+ for( i = si; i < ei; i++ )
+ {
+ if( m_bSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // shut down volume of all not in solo
+ if( !m_bSoloStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // bring back if not muted
+ if( !m_bMuteStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessEQ
+//
+//-----------------------------------------------------
+#define MULTI (0.33333333333333333333333333333333f)
+void Mix_4x4_Stereo::ProcessEQ( int ch, float *pL, float *pR )
+{
+ float rez, hp1;
+ float input[ 2 ], out[ 2 ], lowpass, bandpass, highpass;
+
+ input[ L ] = *pL / AUDIO_MAX;
+ input[ R ] = *pR / AUDIO_MAX;
+
+ rez = 1.00;
+
+ // do left and right channels
+ for( int i = 0; i < 2; i++ )
+ {
+ input[ i ] = input[ i ] + 0.000000001;
+
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+ lowpass = lp1[ ch ][ i ];
+ highpass = hp1;
+ bandpass = bp1[ ch ][ i ];
+
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+ lowpass = lowpass + lp1[ ch ][ i ];
+ highpass = highpass + hp1;
+ bandpass = bandpass + bp1[ ch ][ i ];
+
+ input[ i ] = input[ i ] - 0.000000001;
+ lp1[ ch ][ i ] = lp1[ ch ][ i ] + m_Freq * bp1[ ch ][ i ];
+ hp1 = input[ i ] - lp1[ ch ][ i ] - rez * bp1[ ch ][ i ];
+ bp1[ ch ][ i ] = m_Freq * hp1 + bp1[ ch ][ i ];
+
+ lowpass = (lowpass + lp1[ ch ][ i ]) * MULTI;
+ highpass = (highpass + hp1) * MULTI;
+ bandpass = (bandpass + bp1[ ch ][ i ]) * MULTI;
+
+ out[ i ] = ( highpass * m_hpIn[ ch ] ) + ( lowpass * m_lpIn[ ch ] ) + ( bandpass * m_mpIn[ ch ] );
+ }
+
+ *pL = clamp( out[ L ] * AUDIO_MAX, -AUDIO_MAX, AUDIO_MAX );
+ *pR = clamp( out[ R ] * AUDIO_MAX, -AUDIO_MAX, AUDIO_MAX );
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo::step()
+{
+ int ch, group, aux;
+ float inL = 0.0, inR = 0.0, inLClean, inRClean, outL, outR, mainL = 0.0, mainR = 0.0;
+ float inLvl, inPan;
+ float auxL[ nAUX ] = {}, auxR[ nAUX ] = {};
+ bool bGroupActive[ GROUPS ] = {0};
+
+ if( !m_bInitialized )
+ return;
+
+ memset( m_fSubMix, 0, sizeof(m_fSubMix) );
+
+ // channel mixers
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ group = ch / CH_PER_GROUP;
+
+ inLClean = 0.0;
+ inRClean = 0.0;
+ inL = 0.0;
+ inR = 0.0;
+
+ if( inputs[ IN_RIGHT + ch ].active || inputs[ IN_LEFT + ch ].active )
+ {
+ inLvl = clamp( ( params[ PARAM_LEVEL_IN + ch ].value + ( inputs[ IN_LEVEL + ch ].normalize( 0.0 ) / CV_MAX ) ), 0.0, AMP_MAX );
+
+ bGroupActive[ group ] = true;
+
+ // check right channel first for possible mono
+ if( inputs[ IN_RIGHT + ch ].active )
+ {
+ inRClean = inputs[ IN_RIGHT + ch ].value;
+ inR = inRClean * inLvl;
+ m_bMono[ ch ] = false;
+ }
+ else
+ m_bMono[ ch ] = true;
+
+ // left channel
+ if( inputs[ IN_LEFT + ch ].active )
+ {
+ inLClean = inputs[ IN_LEFT + ch ].value;
+ inL = inLClean * inLvl;
+
+ if( m_bMono[ ch ] )
+ {
+ inRClean = inLClean;
+ inR = inL;
+ }
+ }
+
+ // put output to aux if pre fader
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ if( m_bGroupPreFadeAuxStates[ group ][ aux ] )
+ {
+ auxL[ aux ] += inLClean * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ auxR[ aux ] += inRClean * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ }
+ }
+
+ if( m_FadeState[ ch ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fMuteFade[ ch ] -= FADE_MULT;
+
+ if( m_fMuteFade[ ch ] <= 0.0 )
+ {
+ m_fMuteFade[ ch ] = 0.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_FadeState[ ch ] == MUTE_FADE_STATE_INC )
+ {
+ m_fMuteFade[ ch ] += FADE_MULT;
+
+ if( m_fMuteFade[ ch ] >= 1.0 )
+ {
+ m_fMuteFade[ ch ] = 1.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ ProcessEQ( ch, &inL, &inR );
+
+ inL *= m_fMuteFade[ ch ];
+ inR *= m_fMuteFade[ ch ];
+
+ // pan
+ inPan = clamp( params[ PARAM_PAN_IN + ch ].value + ( inputs[ IN_PAN + ch ].normalize( 0.0 ) / CV_MAX ), -1.0, 1.0 );
+
+ //lg.f("pan = %.3f\n", inputs[ IN_PAN + ch ].value );
+
+ if( inPan <= 0.0 )
+ inR *= ( 1.0 + inPan );
+ else
+ inL *= ( 1.0 - inPan );
+
+ // put output to aux if not pre fader
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ if( !m_bGroupPreFadeAuxStates[ group ][ aux ] )
+ {
+ auxL[ aux ] += inL * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ auxR[ aux ] += inR * params[ PARAM_AUX_KNOB + (group * nAUX) + aux ].value;
+ }
+ }
+ }
+ // this channel not active
+ else
+ {
+
+ }
+
+ m_fSubMix[ group ][ L ] += inL;
+ m_fSubMix[ group ][ R ] += inR;
+
+ if( m_pLEDMeterChannel[ ch ][ 0 ] )
+ m_pLEDMeterChannel[ ch ][ 0 ]->Process( inL / AUDIO_MAX );
+ if( m_pLEDMeterChannel[ ch ][ 1 ] )
+ m_pLEDMeterChannel[ ch ][ 1 ]->Process( inR / AUDIO_MAX);
+ }
+
+ // group mixers
+ for ( group = 0; group < GROUPS; group++ )
+ {
+ outL = 0.0;
+ outR = 0.0;
+
+ if( bGroupActive[ group ] )
+ {
+ inLvl = clamp( ( params[ PARAM_GROUP_LEVEL_IN + group ].value + ( inputs[ IN_GROUP_LEVEL + group ].normalize( 0.0 ) / CV_MAX ) ), 0.0, AMP_MAX );
+
+ outL = m_fSubMix[ group ][ L ] * inLvl;
+ outR = m_fSubMix[ group ][ R ] * inLvl;
+
+ // pan
+ inPan = clamp( params[ PARAM_GROUP_PAN_IN + group ].value + ( inputs[ IN_GROUP_PAN + group ].normalize( 0.0 ) / CV_MAX ), -1.0, 1.0 );
+
+ if( inPan <= 0.0 )
+ outR *= ( 1.0 + inPan );
+ else
+ outL *= ( 1.0 - inPan );
+
+ if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fGroupMuteFade[ group ] -= FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] <= 0.0 )
+ {
+ m_fGroupMuteFade[ group ] = 0.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_INC )
+ {
+ m_fGroupMuteFade[ group ] += FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] >= 1.0 )
+ {
+ m_fGroupMuteFade[ group ] = 1.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ outL *= m_fGroupMuteFade[ group ];
+ outR *= m_fGroupMuteFade[ group ];
+ }
+
+ if( m_pLEDMeterGroup[ group ][ 0 ] )
+ m_pLEDMeterGroup[ group ][ 0 ]->Process( outL / AUDIO_MAX );
+ if( m_pLEDMeterGroup[ group ][ 1 ] )
+ m_pLEDMeterGroup[ group ][ 1 ]->Process( outR / AUDIO_MAX );
+
+ mainL += outL;
+ mainR += outR;
+ }
+
+ if( m_pLEDMeterMain[ 0 ] )
+ m_pLEDMeterMain[ 0 ]->Process( ( mainL / AUDIO_MAX ) * params[ PARAM_MAIN_LEVEL ].value );
+ if( m_pLEDMeterMain[ 1 ] )
+ m_pLEDMeterMain[ 1 ]->Process( ( mainR / AUDIO_MAX ) * params[ PARAM_MAIN_LEVEL ].value );
+
+ // put aux output
+ for ( aux = 0; aux < nAUX; aux++ )
+ {
+ outputs[ OUT_AUXL + aux ].value = clamp( auxL[ aux ] * params[ PARAM_AUX_OUT + aux ].value, -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_AUXR + aux ].value = clamp( auxR[ aux ] * params[ PARAM_AUX_OUT + aux ].value, -AUDIO_MAX, AUDIO_MAX );
+ }
+
+ outputs[ OUT_MAINL ].value = clamp( mainL * params[ PARAM_MAIN_LEVEL ].value, -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_MAINR ].value = clamp( mainR * params[ PARAM_MAIN_LEVEL ].value, -AUDIO_MAX, AUDIO_MAX );
+}
diff --git a/src/Mixer_4x4_Stereo_old.cpp b/src/Mixer_4x4_Stereo_old.cpp
new file mode 100644
index 0000000..eeabf72
--- /dev/null
+++ b/src/Mixer_4x4_Stereo_old.cpp
@@ -0,0 +1,907 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define GROUPS 4
+#define CH_PER_GROUP 4
+#define CHANNELS ( GROUPS * CH_PER_GROUP )
+
+#define GROUP_OFF_X 52
+#define CHANNEL_OFF_X 34
+
+#define FADE_MULT (0.0005f)
+
+#define L 0
+#define R 1
+
+#define MUTE_FADE_STATE_IDLE 0
+#define MUTE_FADE_STATE_INC 1
+#define MUTE_FADE_STATE_DEC 2
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct Mix_4x4_Stereo_old : Module
+{
+ enum ParamIds
+ {
+ PARAM_MAIN_LEVEL,
+ PARAM_LEVEL_IN,
+ PARAM_PAN_IN = PARAM_LEVEL_IN + CHANNELS,
+ PARAM_GROUP_LEVEL_IN = PARAM_PAN_IN + CHANNELS,
+ PARAM_GROUP_PAN_IN = PARAM_GROUP_LEVEL_IN + GROUPS,
+ PARAM_MUTE_BUTTON = PARAM_GROUP_PAN_IN + GROUPS,
+ PARAM_SOLO_BUTTON = PARAM_MUTE_BUTTON + CHANNELS,
+ PARAM_GROUP_MUTE = PARAM_SOLO_BUTTON + CHANNELS,
+ PARAM_GROUP_SOLO = PARAM_GROUP_MUTE + GROUPS,
+
+ nPARAMS = PARAM_GROUP_SOLO + GROUPS
+ };
+
+ enum InputIds
+ {
+ IN_LEFT,
+ IN_RIGHT = IN_LEFT + CHANNELS,
+ IN_LEVEL = IN_RIGHT + CHANNELS,
+ IN_PAN = IN_LEVEL + CHANNELS,
+ IN_GROUP_LEVEL = IN_PAN + CHANNELS,
+ IN_GROUP_PAN = IN_GROUP_LEVEL + GROUPS,
+ IN_GROUP_MUTE = IN_GROUP_PAN + GROUPS,
+ IN_GROUP_SOLO = IN_GROUP_MUTE + GROUPS,
+ IN_MUTES = IN_GROUP_SOLO + GROUPS,
+ IN_SOLOS = IN_MUTES + CHANNELS,
+ nINPUTS = IN_SOLOS + CHANNELS
+ };
+
+ enum OutputIds
+ {
+ OUT_MAINL,
+ OUT_MAINR,
+ OUT_GROUPL,
+ OUT_GROUPR = OUT_GROUPL + GROUPS,
+
+ nOUTPUTS = OUT_GROUPR + GROUPS
+ };
+
+ enum LightIds
+ {
+ LIGHT_MUTE,
+ LIGHT_SOLO = LIGHT_MUTE + CHANNELS,
+ LIGHT_GROUP_MUTE = LIGHT_SOLO + CHANNELS,
+ LIGHT_GROUP_SOLO = LIGHT_GROUP_MUTE + GROUPS,
+ nLIGHTS = LIGHT_GROUP_SOLO + GROUPS
+ };
+
+ CLog lg;
+
+ // mute buttons
+ SchmittTrigger m_SchTrigMutes[ CHANNELS ];
+ bool m_bMuteStates[ CHANNELS ] = {};
+ float m_fMuteFade[ CHANNELS ] = {};
+
+ int m_FadeState[ CHANNELS ] = {MUTE_FADE_STATE_IDLE};
+
+ // solo buttons
+ SchmittTrigger m_SchTrigSolos[ CHANNELS ];
+ bool m_bSoloStates[ CHANNELS ] = {};
+
+ // mute and solo input triggers
+ SchmittTrigger m_SchTrigInSolos[ CHANNELS ];
+ SchmittTrigger m_SchTrigInMutes[ CHANNELS ];
+
+ // group mute buttons
+ SchmittTrigger m_SchTrigGroupMutes[ GROUPS ];
+ bool m_bGroupMuteStates[ GROUPS ] = {};
+ float m_fGroupMuteFade[ GROUPS ] = {};
+
+ int m_GroupFadeState[ GROUPS ] = {MUTE_FADE_STATE_IDLE};
+
+ // group solo buttons
+ SchmittTrigger m_SchTrigGroupSolos[ GROUPS ];
+ bool m_bGroupSoloStates[ GROUPS ] = {};
+
+ // group mute and solo input triggers
+ SchmittTrigger m_SchTrigGroupInSolos[ GROUPS ];
+ SchmittTrigger m_SchTrigGroupInMutes[ GROUPS ];
+
+ // processing
+ bool m_bMono[ CHANNELS ] = {};
+ float m_fSubMix[ GROUPS ][ 3 ] = {};
+
+ // Contructor
+ Mix_4x4_Stereo_old() : Module( nPARAMS, nINPUTS, nOUTPUTS, nLIGHTS ){}
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override{}
+ void reset() override;
+
+ void ProcessMuteSolo( int channel, bool bMute, bool bGroup );
+
+};
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+struct Mix_4x4_Stereo_Widget_old : ModuleWidget {
+ Mix_4x4_Stereo_Widget_old(Mix_4x4_Stereo_old *module) : ModuleWidget(module)
+{
+ int ch, x, y, i, ybase;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/Mix_4x4_Stereo_old.svg")));
+
+ //module->lg.Open("Mix_4x4_Stereo_old.txt");
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ //addInput(createInput( Vec( 6, 40 ), module, Mix_4x4_Stereo_old::IN_WAVE ) );
+ //addOutput(createOutput( Vec( 50, 40 ), module, Mix_4x4_Stereo_old::OUT_WAVE ) );
+
+ //----------------------------------------------------
+ // Add mix sliders
+ x = 23;
+ y = 39;
+
+ // main channel
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ // Left channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_LEFT + ch ) );
+
+ y += 27;
+
+ // Right channel inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_RIGHT + ch ) );
+
+ y += 30;
+
+ // Level knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo_old::PARAM_LEVEL_IN + ch, 0.0, 2.0, 0.0 ) );
+
+ y += 34;
+
+ // Level inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_LEVEL + ch ) );
+
+ y += 31;
+
+ // pan knobs
+ addParam(ParamWidget::create( Vec( x - 5, y ), module, Mix_4x4_Stereo_old::PARAM_PAN_IN + ch, -1.0, 1.0, 0.0 ) );
+
+ y += 34;
+
+ // Pan inputs
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_PAN + ch ) );
+
+ y += 35;
+
+ // mute buttons
+ addChild(Port::create( Vec( x - 8, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_MUTES + ch ) );
+
+ addParam(ParamWidget::create( Vec( x + 11, y + 1 ), module, Mix_4x4_Stereo_old::PARAM_MUTE_BUTTON + ch, 0.0, 1.0, 0.0 ) );
+ addChild(ModuleLightWidget::create>( Vec( x + 14, y + 5 ), module, Mix_4x4_Stereo_old::LIGHT_MUTE + ch ) );
+
+ y += 26;
+
+ // solo buttons
+ addChild(Port::create( Vec( x - 8, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_SOLOS + ch ) );
+
+ addParam(ParamWidget::create( Vec( x + 11, y ), module, Mix_4x4_Stereo_old::PARAM_SOLO_BUTTON + ch, 0.0, 1.0, 0.0 ) );
+ addChild(ModuleLightWidget::create>( Vec( x + 14, y + 5 ), module, Mix_4x4_Stereo_old::LIGHT_SOLO + ch ) );
+
+ if( ( ch & 3 ) == 3 )
+ {
+ x += GROUP_OFF_X;
+ }
+ else
+ {
+ x += CHANNEL_OFF_X;
+ }
+
+ y = 39;
+ }
+
+ // group mixera
+ ybase = 278;
+ x = 21;
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // mute/solo inputs
+ y = ybase + 23;
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_GROUP_MUTE + i ) );
+
+ y += 30;
+
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_GROUP_SOLO + i ) );
+
+ // mute/solo buttons
+ x += 23;
+ y = ybase + 21;
+
+ addParam(ParamWidget::create( Vec( x, y ), module, Mix_4x4_Stereo_old::PARAM_GROUP_MUTE + i, 0.0, 1.0, 0.0 ) );
+ addChild(ModuleLightWidget::create>( Vec( x + 3, y + 4 ), module, Mix_4x4_Stereo_old::LIGHT_GROUP_MUTE + i ) );
+ y += 30;
+
+ addParam(ParamWidget::create( Vec( x, y ), module, Mix_4x4_Stereo_old::PARAM_GROUP_SOLO + i, 0.0, 1.0, 0.0 ) );
+ addChild(ModuleLightWidget::create>( Vec( x + 3, y + 4 ), module, Mix_4x4_Stereo_old::LIGHT_GROUP_SOLO + i ) );
+
+ // group level and pan inputs
+ x += 24;
+ y = ybase + 11;
+
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_GROUP_LEVEL + i ) );
+
+ y += 40;
+
+ addChild(Port::create( Vec( x, y ), Port::INPUT, module, Mix_4x4_Stereo_old::IN_GROUP_PAN + i ) );
+
+ // group level and pan knobs
+ x += 24;
+ y = ybase + 6;
+
+ addParam(ParamWidget::create( Vec( x, y ), module, Mix_4x4_Stereo_old::PARAM_GROUP_LEVEL_IN + i, 0.0, 2.0, 0.0 ) );
+
+ y += 39;
+
+ addParam(ParamWidget::create( Vec( x, y ), module, Mix_4x4_Stereo_old::PARAM_GROUP_PAN_IN + i, -1.0, 1.0, 0.0 ) );
+
+ // group outputs
+ x += 36;
+ y = ybase + 29;
+
+ addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, Mix_4x4_Stereo_old::OUT_GROUPL + i ) );
+
+ y += 31;
+
+ addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, Mix_4x4_Stereo_old::OUT_GROUPR + i ) );
+
+ // account for slight error in pixel conversion to svg area
+ x += 45 + ( i * 2 );
+ }
+
+ // main mixer knob
+ addParam(ParamWidget::create( Vec( 633, 237 ), module, Mix_4x4_Stereo_old::PARAM_MAIN_LEVEL, 0.0, 2.0, 0.0 ) );
+
+ // outputs
+ addChild(Port::create( Vec( 636, 305 ), Port::OUTPUT, module, Mix_4x4_Stereo_old::OUT_MAINL ) );
+ addChild(Port::create( Vec( 668, 335 ), Port::OUTPUT, module, Mix_4x4_Stereo_old::OUT_MAINR ) );
+
+ reset();
+}
+};
+
+Model *modelMix_4x4_Stereo_Widget_old = Model::create( "mscHack", "Mix_4x4_Stereo", "MIXER 4x4 (old) OBSOLETE!", MIXER_TAG, QUAD_TAG, PANNING_TAG, AMPLIFIER_TAG, MULTIPLE_TAG );
+
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo_old::reset()
+{
+ int ch, i;
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ lights[ LIGHT_MUTE + ch ].value = 0.0;
+ lights[ LIGHT_SOLO + ch ].value = 0.0;
+ m_bMuteStates[ ch ] = false;
+ m_bSoloStates[ ch ] = false;
+ m_fMuteFade[ ch ] = 1.0;
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_IDLE;
+ lights[ LIGHT_GROUP_MUTE + i ].value = 0.0;
+ lights[ LIGHT_GROUP_SOLO + i ].value = 0.0;
+ m_bGroupMuteStates[ i ] = false;
+ m_bGroupSoloStates[ i ] = false;
+ m_fGroupMuteFade[ i ] = 1.0;
+ }
+
+ params[ PARAM_MAIN_LEVEL ].value = 0.0;
+}
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *Mix_4x4_Stereo_old::toJson()
+{
+ bool *pbool;
+ json_t *gatesJ;
+ json_t *rootJ = json_object();
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "channel mutes", gatesJ );
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "channel solos", gatesJ );
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group mutes", gatesJ );
+
+ // group solos
+ pbool = &m_bGroupSoloStates[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_integer( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "group solos", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo_old::fromJson(json_t *rootJ)
+{
+ int ch, i;
+ bool *pbool;
+ json_t *StepsJ;
+ bool bSolo[ GROUPS ] = {0}, bGroupSolo = false;
+
+ // channel mutes
+ pbool = &m_bMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "channel mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // channel solos
+ pbool = &m_bSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "channel solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < CHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group mutes
+ pbool = &m_bGroupMuteStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group mutes" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // group solos
+ pbool = &m_bGroupSoloStates[ 0 ];
+
+ StepsJ = json_object_get( rootJ, "group solos" );
+
+ if (StepsJ)
+ {
+ for ( i = 0; i < GROUPS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // anybody soloing?
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( m_bSoloStates[ ch ] )
+ {
+ bSolo[ ch / CH_PER_GROUP ] = true;
+ }
+ }
+
+ for( ch = 0; ch < CHANNELS; ch++ )
+ {
+ if( bSolo[ ch / CH_PER_GROUP ] )
+ {
+ // only open soloing channels
+ if( m_bSoloStates[ ch ] )
+ m_fMuteFade[ ch ] = 1.0;
+ else
+ m_fMuteFade[ ch ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fMuteFade[ ch ] = m_bMuteStates[ ch ] ? 0.0: 1.0;
+ }
+
+ lights[ LIGHT_MUTE + ch ].value = m_bMuteStates[ ch ] ? 1.0: 0.0;
+ lights[ LIGHT_SOLO + ch ].value = m_bSoloStates[ ch ] ? 1.0: 0.0;
+ }
+
+ // anybody group soloing?
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( m_bGroupSoloStates[ i ] )
+ {
+ bGroupSolo = true;
+ break;
+ }
+ }
+
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( bGroupSolo )
+ {
+ // only open soloing channels
+ if( m_bGroupSoloStates[ i ] )
+ m_fGroupMuteFade[ i ] = 1.0;
+ else
+ m_fGroupMuteFade[ i ] = 0.0;
+ }
+ else
+ {
+ // nobody is soloing so just open the non muted channels
+ m_fGroupMuteFade[ i ] = m_bGroupMuteStates[ i ] ? 0.0: 1.0;
+ }
+
+ lights[ LIGHT_GROUP_MUTE + i ].value = m_bGroupMuteStates[ i ] ? 1.0: 0.0;
+ lights[ LIGHT_GROUP_SOLO + i ].value = m_bGroupSoloStates[ i ] ? 1.0: 0.0;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: ProcessMuteSolo
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo_old::ProcessMuteSolo( int index, bool bMute, bool bGroup )
+{
+ int i, group, si, ei;
+ bool bSoloEnabled = false, bSoloOff = false;
+
+ if( bGroup )
+ {
+ if( bMute )
+ {
+ m_bGroupMuteStates[ index ] = !m_bGroupMuteStates[ index ];
+
+ // turn solo off
+ if( m_bGroupSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bGroupSoloStates[ index ] = false;
+ lights[ LIGHT_GROUP_SOLO + index ].value = 0.0;
+ }
+
+ // if mute is off then set volume
+ if( m_bGroupMuteStates[ index ] )
+ {
+ lights[ LIGHT_GROUP_MUTE + index ].value = 1.0;
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ lights[ LIGHT_GROUP_MUTE + index ].value = 0.0;
+ m_GroupFadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bGroupSoloStates[ index ] = !m_bGroupSoloStates[ index ];
+
+ // turn mute off
+ if( m_bGroupMuteStates[ index ] )
+ {
+ m_bGroupMuteStates[ index ] = false;
+ lights[ LIGHT_GROUP_MUTE + index ].value = 0.0;
+ }
+
+ // shut down volume of all groups not in solo
+ if( !m_bGroupSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ lights[ LIGHT_GROUP_SOLO + index ].value = 0.0;
+ }
+ else
+ {
+ lights[ LIGHT_GROUP_SOLO + index ].value = 1.0;
+ }
+ }
+
+ // is a track soloing?
+ for( i = 0; i < GROUPS; i++ )
+ {
+ if( m_bGroupSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // shut down volume of all groups not in solo
+ if( !m_bGroupSoloStates[ i ] )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = 0; i < GROUPS; i++ )
+ {
+ // bring back if not muted
+ if( !m_bGroupMuteStates[ i ] )
+ {
+ m_GroupFadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+ // !bGroup
+ else
+ {
+ group = index / CH_PER_GROUP;
+
+ si = group * CH_PER_GROUP;
+ ei = si + CH_PER_GROUP;
+
+ if( bMute )
+ {
+ m_bMuteStates[ index ] = !m_bMuteStates[ index ];
+
+ // turn solo off
+ if( m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ m_bSoloStates[ index ] = false;
+ lights[ LIGHT_SOLO + index ].value = 0.0;
+ }
+
+ // if mute is off then set volume
+ if( m_bMuteStates[ index ] )
+ {
+ lights[ LIGHT_MUTE + index ].value = 1.0;
+ m_FadeState[ index ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ lights[ LIGHT_MUTE + index ].value = 0.0;
+ m_FadeState[ index ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ else
+ {
+ m_bSoloStates[ index ] = !m_bSoloStates[ index ];
+
+ // turn mute off
+ if( m_bMuteStates[ index ] )
+ {
+ m_bMuteStates[ index ] = false;
+ lights[ LIGHT_MUTE + index ].value = 0.0;
+ }
+
+ // toggle solo
+ if( !m_bSoloStates[ index ] )
+ {
+ bSoloOff = true;
+ lights[ LIGHT_SOLO + index ].value = 0.0;
+ }
+ else
+ {
+ lights[ LIGHT_SOLO + index ].value = 1.0;
+ }
+ }
+
+ // is a track soloing?
+ for( i = si; i < ei; i++ )
+ {
+ if( m_bSoloStates[ i ] )
+ {
+ bSoloEnabled = true;
+ break;
+ }
+ }
+
+ if( bSoloEnabled )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // shut down volume of all not in solo
+ if( !m_bSoloStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_DEC;
+ }
+ else
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ // nobody soloing and just turned solo off then enable all channels that aren't muted
+ else if( bSoloOff )
+ {
+ // process solo
+ for( i = si; i < ei; i++ )
+ {
+ // bring back if not muted
+ if( !m_bMuteStates[ i ] )
+ {
+ m_FadeState[ i ] = MUTE_FADE_STATE_INC;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void Mix_4x4_Stereo_old::step()
+{
+ int ch, group;
+ float inL = 0.0, inR = 0.0, outL, outR, pan, mainL = 0.0, mainR = 0.0;
+ bool bGroupActive[ 4 ] = {0};
+
+ memset( m_fSubMix, 0, sizeof(m_fSubMix) );
+
+ // channel mixers
+ for ( ch = 0; ch < CHANNELS; ch++ )
+ {
+ group = ch / 4;
+
+ inL = 0.0;
+ inR = 0.0;
+
+ if( inputs[ IN_RIGHT + ch ].active || inputs[ IN_LEFT + ch ].active )
+ {
+ bGroupActive[ group ] = true;
+
+ if( inputs[ IN_MUTES + ch ].active )
+ {
+ if( m_SchTrigInMutes[ ch ].process( inputs[ IN_MUTES + ch ].value ) )
+ {
+ ProcessMuteSolo( ch, true, false );
+ }
+ }
+
+ if( inputs[ IN_SOLOS + ch ].active )
+ {
+ // External clock
+ if( m_SchTrigInSolos[ ch ].process( inputs[ IN_SOLOS + ch ].value ) )
+ {
+ ProcessMuteSolo( ch, false, false );
+ }
+ }
+
+ // process mute buttons
+ if( m_SchTrigMutes[ ch ].process( params[ PARAM_MUTE_BUTTON + ch ].value ) )
+ {
+ ProcessMuteSolo( ch, true, false );
+ }
+
+ // process solo buttons
+ if( m_SchTrigSolos[ ch ].process( params[ PARAM_SOLO_BUTTON + ch ].value ) )
+ {
+ ProcessMuteSolo( ch, false, false );
+ }
+
+ // check right channel first for possible mono
+ if( inputs[ IN_RIGHT + ch ].active )
+ {
+ inR = inputs[ IN_RIGHT + ch ].value * clamp( ( params[ PARAM_LEVEL_IN + ch ].value + ( inputs[ IN_LEVEL + ch ].normalize( 0.0 ) / 10.0 ) ), -1.0f, 1.0f );
+ m_bMono[ ch ] = false;
+ }
+ else
+ m_bMono[ ch ] = true;
+
+ // left channel
+ if( inputs[ IN_LEFT + ch ].active )
+ {
+ inL = inputs[ IN_LEFT + ch ].value * clamp( ( params[ PARAM_LEVEL_IN + ch ].value + ( inputs[ IN_LEVEL + ch ].normalize( 0.0 ) / 10.0 ) ), -1.0f, 1.0f );
+
+ if( m_bMono[ ch ] )
+ inR = inL;
+ }
+
+ if( m_FadeState[ ch ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fMuteFade[ ch ] -= FADE_MULT;
+
+ if( m_fMuteFade[ ch ] <= 0.0 )
+ {
+ m_fMuteFade[ ch ] = 0.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_FadeState[ ch ] == MUTE_FADE_STATE_INC )
+ {
+ m_fMuteFade[ ch ] += FADE_MULT;
+
+ if( m_fMuteFade[ ch ] >= 1.0 )
+ {
+ m_fMuteFade[ ch ] = 1.0;
+ m_FadeState[ ch ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ inL *= m_fMuteFade[ ch ];
+ inR *= m_fMuteFade[ ch ];
+
+ // pan
+ pan = clamp( params[ PARAM_PAN_IN + ch ].value + ( inputs[ IN_PAN + ch ].normalize( 0.0 ) / 10.0 ), -1.0f, 1.0f );
+
+ //lg.f("pan = %.3f\n", inputs[ IN_PAN + ch ].value );
+
+ if( pan <= 0.0 )
+ inR *= ( 1.0 + pan );
+ else
+ inL *= ( 1.0 - pan );
+ }
+ // this channel not active
+ else
+ {
+
+ }
+
+ m_fSubMix[ group ][ L ] += inL;
+ m_fSubMix[ group ][ R ] += inR;
+ }
+
+ // group mixers
+ for ( group = 0; group < GROUPS; group++ )
+ {
+ outL = 0.0;
+ outR = 0.0;
+
+ if( bGroupActive[ group ] )
+ {
+ if( inputs[ IN_GROUP_MUTE + group ].active )
+ {
+ // External clock
+ if( m_SchTrigGroupInMutes[ group ].process( inputs[ IN_GROUP_MUTE + group ].value ) )
+ {
+ ProcessMuteSolo( group, true, true );
+ }
+ }
+
+ if( inputs[ IN_GROUP_SOLO + group ].active )
+ {
+ // External clock
+ if( m_SchTrigGroupInSolos[ group ].process( inputs[ IN_GROUP_SOLO + group ].value ) )
+ {
+ ProcessMuteSolo( group, false, true );
+ }
+ }
+
+ // process mute buttons
+ if( m_SchTrigGroupMutes[ group ].process( params[ PARAM_GROUP_MUTE + group ].value ) )
+ {
+ ProcessMuteSolo( group, true, true );
+ }
+
+ if( m_SchTrigGroupSolos[ group ].process( params[ PARAM_GROUP_SOLO + group ].value ) )
+ {
+ ProcessMuteSolo( group, false, true );
+ }
+
+ outL = m_fSubMix[ group ][ L ] * clamp( params[ PARAM_GROUP_LEVEL_IN + group ].value + ( inputs[ IN_GROUP_LEVEL + group ].normalize( 0.0 ) / 10.0 ), -1.0f, 1.0f );
+ outR = m_fSubMix[ group ][ R ] * clamp( params[ PARAM_GROUP_LEVEL_IN + group ].value + ( inputs[ IN_GROUP_LEVEL + group ].normalize( 0.0 ) / 10.0 ), -1.0f, 1.0f );
+
+ // pan
+ pan = clamp( params[ PARAM_GROUP_PAN_IN + group ].value + ( inputs[ IN_GROUP_PAN + group ].normalize( 0.0 ) / 10.0 ), -1.0f, 1.0f );
+
+ if( pan <= 0.0 )
+ outR *= ( 1.0 + pan );
+ else
+ outL *= ( 1.0 - pan );
+
+ if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_DEC )
+ {
+ m_fGroupMuteFade[ group ] -= FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] <= 0.0 )
+ {
+ m_fGroupMuteFade[ group ] = 0.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+ else if( m_GroupFadeState[ group ] == MUTE_FADE_STATE_INC )
+ {
+ m_fGroupMuteFade[ group ] += FADE_MULT;
+
+ if( m_fGroupMuteFade[ group ] >= 1.0 )
+ {
+ m_fGroupMuteFade[ group ] = 1.0;
+ m_GroupFadeState[ group ] = MUTE_FADE_STATE_IDLE;
+ }
+ }
+
+ outL *= m_fGroupMuteFade[ group ];
+ outR *= m_fGroupMuteFade[ group ];
+
+ outputs[ OUT_GROUPL + group ].value = outL;
+ outputs[ OUT_GROUPR + group ].value = outR;
+ }
+
+ mainL += outL;
+ mainR += outR;
+ }
+
+ outputs[ OUT_MAINL ].value = mainL * params[ PARAM_MAIN_LEVEL ].value;
+ outputs[ OUT_MAINR ].value = mainR * params[ PARAM_MAIN_LEVEL ].value;
+}
diff --git a/src/PingPong.cpp b/src/PingPong.cpp
new file mode 100644
index 0000000..204debc
--- /dev/null
+++ b/src/PingPong.cpp
@@ -0,0 +1,475 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+typedef struct
+{
+ float lp1, bp1;
+ float hpIn;
+ float lpIn;
+ float mpIn;
+
+}FILTER_PARAM_STRUCT;
+
+#define L 0
+#define R 1
+
+#define DELAY_BUFF_LEN 0x80000
+
+#define MAC_DELAY_SECONDS 4.0f
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct PingPong : Module
+{
+ enum ParamIds
+ {
+ PARAM_DELAYL,
+ PARAM_DELAYR,
+ PARAM_LEVEL_FB_LR,
+ PARAM_LEVEL_FB_LL,
+ PARAM_LEVEL_FB_RL,
+ PARAM_LEVEL_FB_RR,
+ PARAM_CUTOFF,
+ PARAM_Q,
+ PARAM_MIX,
+ PARAM_FILTER_MODE,
+ PARAM_REVERSE,
+ nPARAMS
+ };
+
+ enum InputIds
+ {
+ INPUT_L,
+ INPUT_R,
+ INPUT_SYNC,
+ nINPUTS
+ };
+
+ enum OutputIds
+ {
+ OUT_L,
+ OUT_R,
+ nOUTPUTS
+ };
+
+ enum FILTER_TYPES
+ {
+ FILTER_OFF,
+ FILTER_LP,
+ FILTER_HP,
+ FILTER_BP,
+ FILTER_NT
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ FILTER_PARAM_STRUCT m_Filter[ 2 ];
+
+ float m_fCutoff = 0.0;
+ float m_LastOut[ 2 ] = {};
+ float m_DelayBuffer[ 2 ][ DELAY_BUFF_LEN ];
+
+ int m_DelayIn = 0;
+ int m_DelayOut[ 2 ] = {0};
+
+ bool m_bReverseState = false;
+
+ // sync clock
+ SchmittTrigger m_SchmittSync;
+ int m_LastSyncCount = 0;
+ int m_SyncCount = 0;
+ int m_SyncTime = 0;
+
+ // LAST
+ int m_LastDelayKnob[ 2 ] = {};
+ bool m_bWasSynced = false;
+
+ MyLEDButton *m_pButtonReverse = NULL;
+
+ // Contructor
+ PingPong() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0){}
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override;
+ void reset() override;
+
+ void ChangeFilterCutoff( float cutfreq );
+ float Filter( int ch, float in );
+};
+
+//-----------------------------------------------------
+// PingPong_Reverse
+//-----------------------------------------------------
+void PingPong_Reverse( void *pClass, int id, bool bOn )
+{
+ float delay;
+ PingPong *mymodule;
+ mymodule = (PingPong*)pClass;
+
+ mymodule->m_bReverseState = bOn;
+
+ // recalc delay offsets when going back to forward mode
+ if( !mymodule->m_bReverseState )
+ {
+ delay = mymodule->params[ PingPong::PARAM_DELAYL ].value * MAC_DELAY_SECONDS * engineGetSampleRate();
+ mymodule->m_DelayOut[ L ] = ( mymodule->m_DelayIn - (int)delay ) & 0x7FFFF;
+
+ delay = mymodule->params[ PingPong::PARAM_DELAYR ].value * MAC_DELAY_SECONDS * engineGetSampleRate();
+ mymodule->m_DelayOut[ R ] = ( mymodule->m_DelayIn - (int)delay ) & 0x7FFFF;
+ }
+}
+
+//-----------------------------------------------------
+// MyEQHi_Knob
+//-----------------------------------------------------
+struct MyCutoffKnob : Green1_Big
+{
+ PingPong *mymodule;
+
+ void onChange( EventChange &e ) override
+ {
+ mymodule = (PingPong*)module;
+
+ if( mymodule )
+ {
+ mymodule->ChangeFilterCutoff( value );
+ }
+
+ RoundKnob::onChange( e );
+ }
+};
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+#define Y_OFF_H 40
+#define X_OFF_W 40
+
+struct PingPong_Widget : ModuleWidget {
+ PingPong_Widget(PingPong *module) : ModuleWidget(module)
+{
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/PingPong.svg")));
+
+ module->lg.Open("PingPong.txt");
+
+ // sync clock
+ addChild(Port::create( Vec( 10, 110 ), Port::INPUT, module, PingPong::INPUT_SYNC ) );
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ // Filter/Res knobs
+ addParam(ParamWidget::create( Vec( 66, 55 ), module, PingPong::PARAM_FILTER_MODE, 0.0, 4.0, 0.0 ) );
+ addParam(ParamWidget::create( Vec( 23, 60 ), module, PingPong::PARAM_CUTOFF, 0.0, 1.0, 0.0 ) );
+ addParam(ParamWidget::create( Vec( 73, 79 ), module, PingPong::PARAM_Q, 0.0, 1.0, 0.0 ) );
+
+ // L Feedback
+ addParam(ParamWidget::create( Vec( 49, 110 ), module, PingPong::PARAM_LEVEL_FB_LL, 0.0, 1.0, 0.0 ) );
+
+ // Left
+ addChild(Port::create( Vec( 10, 154 ), Port::INPUT, module, PingPong::INPUT_L ) );
+ addParam(ParamWidget::create( Vec( 38, 143 ), module, PingPong::PARAM_DELAYL, 0.0, 1.0, 0.0 ) );
+ addChild(Port::create( Vec( 90, 154 ), Port::OUTPUT, module, PingPong::OUT_L ) );
+
+ // R to L level and L to R levels
+ addParam(ParamWidget::create( Vec( 9, 191 ), module, PingPong::PARAM_LEVEL_FB_RL, 0.0, 1.0, 0.0 ) );
+ addParam(ParamWidget::create( Vec( 9, 226 ), module, PingPong::PARAM_LEVEL_FB_LR, 0.0, 1.0, 0.0 ) );
+
+ // mix knob
+ addParam(ParamWidget::create( Vec( 77, 199 ), module, PingPong::PARAM_MIX, 0.0, 1.0, 0.0 ) );
+
+ // Left
+ addChild(Port::create( Vec( 10, 266 ), Port::INPUT, module, PingPong::INPUT_R ) );
+ addParam(ParamWidget::create( Vec( 38, 255 ), module, PingPong::PARAM_DELAYR, 0.0, 1.0, 0.0 ) );
+ addChild(Port::create( Vec( 90, 266 ), Port::OUTPUT, module, PingPong::OUT_R ) );
+
+ // R Feedback
+ addParam(ParamWidget::create( Vec( 49, 308 ), module, PingPong::PARAM_LEVEL_FB_RR, 0.0, 1.0, 0.0 ) );
+
+ // reverse button
+ module->m_pButtonReverse = new MyLEDButton( 17, 343, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButton::TYPE_SWITCH, 0, module, PingPong_Reverse );
+ addChild( module->m_pButtonReverse );
+
+ module->m_bInitialized = true;
+}
+};
+
+Model *modelPingPong_Widget = Model::create( "mscHack", "PingPong_Widget", "DELAY Ping Pong", DELAY_TAG, PANNING_TAG );
+
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void PingPong::reset()
+{
+ if( !m_bInitialized )
+ return;
+
+ m_pButtonReverse->Set( false );
+ m_bReverseState = false;
+}
+
+//-----------------------------------------------------
+// Procedure: randomize
+//
+//-----------------------------------------------------
+void PingPong::randomize()
+{
+}
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *PingPong::toJson()
+{
+ json_t *rootJ = json_object();
+
+ // reverse state
+ json_object_set_new(rootJ, "ReverseState", json_boolean (m_bReverseState));
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void PingPong::fromJson(json_t *rootJ)
+{
+ // reverse state
+ json_t *revJ = json_object_get(rootJ, "ReverseState");
+
+ if (revJ)
+ m_bReverseState = json_is_true( revJ );
+
+ m_pButtonReverse->Set( m_bReverseState );
+}
+
+//-----------------------------------------------------
+// Procedure: ChangeFilterCutoff
+//
+//-----------------------------------------------------
+void PingPong::ChangeFilterCutoff( float cutfreq )
+{
+ float fx, fx2, fx3, fx5, fx7;
+
+ // clamp at 1.0 and 20/samplerate
+ cutfreq = fmax(cutfreq, 20 / engineGetSampleRate());
+ cutfreq = fmin(cutfreq, 1.0);
+
+ // calculate eq rez freq
+ fx = 3.141592 * (cutfreq * 0.026315789473684210526315789473684) * 2 * 3.141592;
+ fx2 = fx*fx;
+ fx3 = fx2*fx;
+ fx5 = fx3*fx2;
+ fx7 = fx5*fx2;
+
+ m_fCutoff = 2.0 * (fx
+ - (fx3 * 0.16666666666666666666666666666667)
+ + (fx5 * 0.0083333333333333333333333333333333)
+ - (fx7 * 0.0001984126984126984126984126984127));
+}
+
+//-----------------------------------------------------
+// Procedure: Filter
+//
+//-----------------------------------------------------
+#define MULTI (0.33333333333333333333333333333333f)
+float PingPong::Filter( int ch, float in )
+{
+ FILTER_PARAM_STRUCT *p;
+ float rez, hp1, out = 0.0;
+ float lowpass, highpass, bandpass;
+
+ if( (int)params[ PARAM_FILTER_MODE ].value == 0 )
+ return in;
+
+ p = &m_Filter[ ch ];
+
+ rez = 1.0 - params[ PARAM_Q ].value;
+
+ in = in + 0.000000001;
+
+ p->lp1 = p->lp1 + m_fCutoff * p->bp1;
+ hp1 = in - p->lp1 - rez * p->bp1;
+ p->bp1 = m_fCutoff * hp1 + p->bp1;
+ lowpass = p->lp1;
+ highpass = hp1;
+ bandpass = p->bp1;
+
+ p->lp1 = p->lp1 + m_fCutoff * p->bp1;
+ hp1 = in - p->lp1 - rez * p->bp1;
+ p->bp1 = m_fCutoff * hp1 + p->bp1;
+ lowpass = lowpass + p->lp1;
+ highpass = highpass + hp1;
+ bandpass = bandpass + p->bp1;
+
+ in = in - 0.000000001;
+
+ p->lp1 = p->lp1 + m_fCutoff * p->bp1;
+ hp1 = in - p->lp1 - rez * p->bp1;
+ p->bp1 = m_fCutoff * hp1 + p->bp1;
+
+ lowpass = (lowpass + p->lp1) * MULTI;
+ highpass = (highpass + hp1) * MULTI;
+ bandpass = (bandpass + p->bp1) * MULTI;
+
+ switch( (int)params[ PARAM_FILTER_MODE ].value )
+ {
+ case FILTER_LP:
+ out = lowpass;
+ break;
+ case FILTER_HP:
+ out = highpass;
+ break;
+ case FILTER_BP:
+ out = bandpass;
+ break;
+ case FILTER_NT:
+ out = lowpass + highpass;
+ break;
+ default:
+ break;
+ }
+
+ return out;
+}
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+float syncQuant[ 10 ] = { 0.125, 0.25, 0.333, 0.375, 0.5, 0.625, 0.666, 0.750, 0.875, 1.0 };
+void PingPong::step()
+{
+ float outL, outR, inL = 0.0, inR = 0.0, inOrigL = 0.0, inOrigR = 0.0, syncq = 0.0;
+ bool bMono = false;
+ int i, dR, dL;
+
+ if( !m_bInitialized )
+ return;
+
+ dL = params[ PARAM_DELAYL ].value * MAC_DELAY_SECONDS * engineGetSampleRate();
+ dR = params[ PARAM_DELAYR ].value * MAC_DELAY_SECONDS * engineGetSampleRate();
+
+ // check right channel first for possible mono
+ if( inputs[ INPUT_SYNC ].active )
+ {
+ m_SyncCount++;
+
+ // sync'd delay
+ if( m_SchmittSync.process( inputs[ INPUT_SYNC ].value ) )
+ {
+ if( !m_bWasSynced || ( (m_SyncTime / 10) != (m_SyncCount / 10) ) || ( m_LastDelayKnob[ L ] != dL ) || ( m_LastDelayKnob[ R ] != dR ) )
+ {
+ m_SyncTime = m_SyncCount;
+
+ for( i = 0; i < 10; i++ )
+ {
+ if( params[ PARAM_DELAYL ].value <= syncQuant[ i ] )
+ {
+ syncq = syncQuant[ i ] * MAC_DELAY_SECONDS;
+ break;
+ }
+ }
+
+ m_DelayOut[ L ] = ( m_DelayIn - (int)(syncq * m_SyncTime) ) & 0x7FFFF;
+
+ for( i = 0; i < 10; i++ )
+ {
+ if( params[ PARAM_DELAYR ].value <= syncQuant[ i ] )
+ {
+ syncq = syncQuant[ i ] * MAC_DELAY_SECONDS;
+ break;
+ }
+ }
+
+ m_DelayOut[ R ] = ( m_DelayIn - (int)(syncq * m_SyncTime) ) & 0x7FFFF;
+ }
+
+ m_SyncCount = 0;
+ }
+
+ m_bWasSynced = true;
+ }
+ else
+ {
+ // non sync'd delay
+ if( m_bWasSynced || ( m_LastDelayKnob[ L ] != dL ) )
+ m_DelayOut[ L ] = ( m_DelayIn - (int)dL ) & 0x7FFFF;
+
+ if( m_bWasSynced || ( m_LastDelayKnob[ R ] != dR ) )
+ m_DelayOut[ R ] = ( m_DelayIn - (int)dR ) & 0x7FFFF;
+
+ m_bWasSynced = false;
+ m_SyncCount = 0;
+ }
+
+ m_LastDelayKnob[ L ] = dL;
+ m_LastDelayKnob[ R ] = dR;
+
+ // check right channel first for possible mono
+ if( inputs[ INPUT_R ].active )
+ {
+ inR = clamp( inputs[ INPUT_R ].value / AUDIO_MAX, -1.0, 1.0 );
+ inR = Filter( R, inR );
+ inOrigR = inR;
+ bMono = false;
+ }
+ else
+ bMono = true;
+
+ // left channel
+ if( inputs[ INPUT_L ].active )
+ {
+ inL = clamp( inputs[ INPUT_L ].value / AUDIO_MAX, -1.0, 1.0 );
+ inL = Filter( L, inL );
+ inOrigL = inL;
+
+ if( bMono )
+ {
+ inOrigR = inL;
+ inR = inL;
+ }
+ }
+
+ m_DelayBuffer[ L ][ m_DelayIn ] = inL + ( m_LastOut[ L ] * params[ PARAM_LEVEL_FB_LL ].value ) + ( m_LastOut[ R ] * params[ PARAM_LEVEL_FB_RL ].value );
+ m_DelayBuffer[ R ][ m_DelayIn ] = inR + ( m_LastOut[ R ] * params[ PARAM_LEVEL_FB_RR ].value ) + ( m_LastOut[ L ] * params[ PARAM_LEVEL_FB_LR ].value );
+
+ m_DelayIn = ( ( m_DelayIn + 1 ) & 0x7FFFF );
+
+ outL = m_DelayBuffer[ L ][ m_DelayOut[ L ] ];
+ outR = m_DelayBuffer[ R ][ m_DelayOut[ R ] ];
+
+ if( m_bReverseState )
+ {
+ m_DelayOut[ L ] = ( ( m_DelayOut[ L ] - 1 ) & 0x7FFFF );
+ m_DelayOut[ R ] = ( ( m_DelayOut[ R ] - 1 ) & 0x7FFFF );
+ }
+ else
+ {
+ m_DelayOut[ L ] = ( ( m_DelayOut[ L ] + 1 ) & 0x7FFFF );
+ m_DelayOut[ R ] = ( ( m_DelayOut[ R ] + 1 ) & 0x7FFFF );
+ }
+
+ m_LastOut[ L ] = outL;
+ m_LastOut[ R ] = outR;
+
+ // output
+ outputs[ OUT_L ].value = clamp( ( inOrigL * ( 1.0 - params[ PARAM_MIX ].value ) ) + ( (outL * AUDIO_MAX) * params[ PARAM_MIX ].value ), -AUDIO_MAX, AUDIO_MAX );
+ outputs[ OUT_R ].value = clamp( ( inOrigR * ( 1.0 - params[ PARAM_MIX ].value ) ) + ( (outR * AUDIO_MAX) * params[ PARAM_MIX ].value ), -AUDIO_MAX, AUDIO_MAX );
+}
diff --git a/src/SEQ_6x32x16.cpp b/src/SEQ_6x32x16.cpp
new file mode 100644
index 0000000..7bd6b86
--- /dev/null
+++ b/src/SEQ_6x32x16.cpp
@@ -0,0 +1,855 @@
+#include "mscHack.hpp"
+#include "mscHack_Controls.hpp"
+#include "dsp/digital.hpp"
+#include "CLog.h"
+
+#define nCHANNELS 6
+#define nSTEPS 32
+#define nPROG 16
+
+typedef struct
+{
+ bool bPending;
+ int prog;
+}PHRASE_CHANGE_STRUCT;
+
+//-----------------------------------------------------
+// Module Definition
+//
+//-----------------------------------------------------
+struct SEQ_6x32x16 : Module
+{
+ enum ParamIds
+ {
+ PARAM_CPY_NEXT,
+ PARAM_RAND = PARAM_CPY_NEXT + nCHANNELS,
+ PARAM_PAUSE = PARAM_RAND + nCHANNELS,
+ PARAM_BILEVEL = PARAM_PAUSE + nCHANNELS,
+ PARAM_LO_KNOB = PARAM_BILEVEL + nCHANNELS,
+ PARAM_MD_KNOB = PARAM_LO_KNOB + nCHANNELS,
+ PARAM_HI_KNOB = PARAM_MD_KNOB + nCHANNELS,
+ PARAM_SWING_KNOB = PARAM_HI_KNOB + nCHANNELS,
+ PARAM_SWING_KNOB2 = PARAM_SWING_KNOB + nCHANNELS,
+
+ nPARAMS = PARAM_SWING_KNOB2 + nCHANNELS
+ };
+
+ enum InputIds
+ {
+ IN_GLOBAL_CLK_RESET,
+ IN_GLOBAL_PAT_CHANGE,
+ IN_CLK,
+ IN_PAT_TRIG = IN_CLK + nCHANNELS,
+ nINPUTS = IN_PAT_TRIG + nCHANNELS
+ };
+
+ enum OutputIds
+ {
+ OUT_TRIG,
+ OUT_LEVEL = OUT_TRIG + nCHANNELS,
+ OUT_BEAT1 = OUT_LEVEL + nCHANNELS,
+ nOUTPUTS = OUT_BEAT1 + nCHANNELS
+ };
+
+ bool m_bInitialized = false;
+ CLog lg;
+
+ bool m_bPauseState[ nCHANNELS ] = {};
+
+ bool m_bBiLevelState[ nCHANNELS ] = {};
+
+ int m_RandCount[ nCHANNELS ] = {};
+ int m_CpyNextCount[ nCHANNELS ] = {};
+
+ SinglePatternClocked32 *m_pPatternDisplay[ nCHANNELS ] = {};
+ int m_Pattern[ nCHANNELS ][ nPROG ][ nSTEPS ];
+ int m_MaxPat[ nCHANNELS ][ nPROG ] = {};
+
+ PatternSelectStrip *m_pProgramDisplay[ nCHANNELS ] = {};
+ int m_CurrentProg[ nCHANNELS ] = {};
+ int m_MaxProg[ nCHANNELS ] = {};
+ PHRASE_CHANGE_STRUCT m_ProgPending[ nCHANNELS ] = {};
+
+ SchmittTrigger m_SchTrigClock[ nCHANNELS ];
+ SchmittTrigger m_SchTrigProg[ nCHANNELS ];
+ SchmittTrigger m_SchTrigGlobalClkReset;
+ SchmittTrigger m_SchTrigGlobalProg;
+
+ bool m_bGlobalClocked[ nCHANNELS ] = {};
+
+ bool m_bTrig[ nCHANNELS ] = {};
+ PulseGenerator m_gatePulse[ nCHANNELS ];
+ PulseGenerator m_gateBeatPulse[ nCHANNELS ];
+
+ // swing
+ int m_SwingLen[ nCHANNELS ] = {0};
+ int m_SwingCount[ nCHANNELS ] = {0};
+ int m_ClockTick[ nCHANNELS ] = {0};
+
+ // buttons
+ MyLEDButton *m_pButtonPause[ nCHANNELS ] = {};
+ MyLEDButton *m_pButtonCopy[ nCHANNELS ] = {};
+ MyLEDButton *m_pButtonBiLevel[ nCHANNELS ] = {};
+ MyLEDButton *m_pButtonRand[ nCHANNELS ] = {};
+ MyLEDButton *m_pButtonAutoPat[ nCHANNELS ] = {};
+ MyLEDButton *m_pButtonHoldCV[ nCHANNELS ] = {};
+
+ bool m_bAutoPatChange[ nCHANNELS ] = {};
+ bool m_bHoldCVState[ nCHANNELS ] = {};
+
+ // Contructor
+ SEQ_6x32x16() : Module(nPARAMS, nINPUTS, nOUTPUTS, 0){}
+
+ // Overrides
+ void step() override;
+ json_t* toJson() override;
+ void fromJson(json_t *rootJ) override;
+ void randomize() override;
+ void reset() override;
+
+ void CpyNext( int ch );
+ void Rand( int ch );
+ void ChangeProg( int ch, int prog, bool force );
+ void SetPendingProg( int ch, int prog );
+
+ //void reset() override;
+};
+
+//-----------------------------------------------------
+// MyLEDButton_AutoPat
+//-----------------------------------------------------
+void MyLEDButton_AutoPat( void *pClass, int id, bool bOn )
+{
+ SEQ_6x32x16 *mymodule;
+ mymodule = (SEQ_6x32x16*)pClass;
+ mymodule->m_bAutoPatChange[ id ] = bOn;
+}
+
+//-----------------------------------------------------
+// MyLEDButton_Pause
+//-----------------------------------------------------
+void MyLEDButton_Pause( void *pClass, int id, bool bOn )
+{
+ SEQ_6x32x16 *mymodule;
+ mymodule = (SEQ_6x32x16*)pClass;
+ mymodule->m_bPauseState[ id ] = bOn;
+}
+
+//-----------------------------------------------------
+// MyLEDButton_HoldCV
+//-----------------------------------------------------
+void MyLEDButton_HoldCV( void *pClass, int id, bool bOn )
+{
+ SEQ_6x32x16 *mymodule;
+ mymodule = (SEQ_6x32x16*)pClass;
+ mymodule->m_bHoldCVState[ id ] = bOn;
+}
+
+//-----------------------------------------------------
+// MyLEDButton_BiLevel
+//-----------------------------------------------------
+void MyLEDButton_BiLevel( void *pClass, int id, bool bOn )
+{
+ SEQ_6x32x16 *mymodule;
+ mymodule = (SEQ_6x32x16*)pClass;
+ mymodule->m_bBiLevelState[ id ] = bOn;
+}
+
+//-----------------------------------------------------
+// MyLEDButton_CpyNxt
+//-----------------------------------------------------
+void MyLEDButton_CpyNxt( void *pClass, int id, bool bOn )
+{
+ SEQ_6x32x16 *mymodule;
+ mymodule = (SEQ_6x32x16*)pClass;
+ mymodule->CpyNext( id );
+}
+
+//-----------------------------------------------------
+// MyLEDButton_Rand
+//-----------------------------------------------------
+void MyLEDButton_Rand( void *pClass, int id, bool bOn )
+{
+ SEQ_6x32x16 *mymodule;
+ mymodule = (SEQ_6x32x16*)pClass;
+ mymodule->Rand( id );
+}
+
+//-----------------------------------------------------
+// Procedure: PatternChangeCallback
+//
+//-----------------------------------------------------
+void SEQ_6x32x16_PatternChangeCallback ( void *pClass, int ch, int pat, int level, int maxpat )
+{
+ SEQ_6x32x16 *mymodule = (SEQ_6x32x16 *)pClass;
+
+ if( !mymodule || !mymodule->m_bInitialized )
+ return;
+
+ mymodule->m_MaxPat[ ch ][ mymodule->m_CurrentProg[ ch ] ] = maxpat;
+ mymodule->m_Pattern[ ch ][ mymodule->m_CurrentProg[ ch ] ] [ pat ] = level;
+}
+
+//-----------------------------------------------------
+// Procedure: ProgramChangeCallback
+//
+//-----------------------------------------------------
+void SEQ_6x32x16_ProgramChangeCallback ( void *pClass, int ch, int pat, int max )
+{
+ SEQ_6x32x16 *mymodule = (SEQ_6x32x16 *)pClass;
+
+ if( !mymodule || !mymodule->m_bInitialized )
+ return;
+
+ if( mymodule->m_CurrentProg[ ch ] != pat )
+ {
+ if( !mymodule->m_bPauseState[ ch ] && mymodule->inputs[ SEQ_6x32x16::IN_CLK + ch ].active )
+ mymodule->SetPendingProg( ch, pat );
+ else
+ mymodule->ChangeProg( ch, pat, false );
+ }
+ else if( mymodule->m_MaxProg[ ch ] != max )
+ {
+ mymodule->m_MaxProg[ ch ] = max;
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: Widget
+//
+//-----------------------------------------------------
+struct SEQ_6x32x16_Widget : ModuleWidget {
+ SEQ_6x32x16_Widget(SEQ_6x32x16 *module) : ModuleWidget(module)
+{
+ int x, y, x2, y2;
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/SEQ_6x32x16.svg")));
+
+ //module->lg.Open("SEQ_6x32x16.txt");
+
+ x = 7;
+ y = 22;
+
+ // global inputs
+ addChild(Port::create( Vec( 204, 357 ), Port::INPUT, module, SEQ_6x32x16::IN_GLOBAL_CLK_RESET ) );
+ addChild(Port::create( Vec( 90, 357 ), Port::INPUT, module, SEQ_6x32x16::IN_GLOBAL_PAT_CHANGE ) );
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ // inputs
+ addChild(Port::create( Vec( x + 6, y + 7 ), Port::INPUT, module, SEQ_6x32x16::IN_CLK + ch ) );
+ addChild(Port::create( Vec( x + 64, y + 31 ), Port::INPUT, module, SEQ_6x32x16::IN_PAT_TRIG + ch ) );
+
+ // pattern display
+ module->m_pPatternDisplay[ ch ] = new SinglePatternClocked32( x + 39, y + 2, 13, 13, 5, 2, 7, DWRGB( 255, 128, 64 ), DWRGB( 18, 18, 0 ), DWRGB( 180, 75, 180 ), DWRGB( 80, 45, 80 ), nSTEPS, ch, module, SEQ_6x32x16_PatternChangeCallback );
+ addChild( module->m_pPatternDisplay[ ch ] );
+
+ // program display
+ module->m_pProgramDisplay[ ch ] = new PatternSelectStrip( x + 106, y + 31, 9, 7, DWRGB( 180, 180, 0 ), DWRGB( 90, 90, 64 ), DWRGB( 0, 180, 200 ), DWRGB( 0, 90, 90 ), nPROG, ch, module, SEQ_6x32x16_ProgramChangeCallback );
+ addChild( module->m_pProgramDisplay[ ch ] );
+
+ // add knobs
+ y2 = y + 34;
+ addParam(ParamWidget::create( Vec( x + 374, y2 ), module, SEQ_6x32x16::PARAM_SWING_KNOB + ch, 0.0, 0.6, 0.0 ) );
+
+ x2 = x + 447;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, SEQ_6x32x16::PARAM_LO_KNOB + ch, 0.0, 1.0, 0.0 ) ); x2 += 24;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, SEQ_6x32x16::PARAM_MD_KNOB + ch, 0.0, 1.0, 0.0 ) ); x2 += 24;
+ addParam(ParamWidget::create( Vec( x2, y2 ), module, SEQ_6x32x16::PARAM_HI_KNOB + ch, 0.0, 1.0, 0.0 ) );
+
+ // add buttons
+ module->m_pButtonAutoPat[ ch ] = new MyLEDButton( x + 55, y + 35, 9, 9, 6.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 0 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_AutoPat );
+ addChild( module->m_pButtonAutoPat[ ch ] );
+
+ module->m_pButtonPause[ ch ] = new MyLEDButton( x + 26, y + 10, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_Pause );
+ addChild( module->m_pButtonPause[ ch ] );
+
+ y2 = y + 33;
+ module->m_pButtonCopy[ ch ] = new MyLEDButton( x + 290, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 244, 244 ), MyLEDButton::TYPE_MOMENTARY, ch, module, MyLEDButton_CpyNxt );
+ addChild( module->m_pButtonCopy[ ch ] );
+
+ module->m_pButtonRand[ ch ] = new MyLEDButton( x + 315, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 244, 244 ), MyLEDButton::TYPE_MOMENTARY, ch, module, MyLEDButton_Rand );
+ addChild( module->m_pButtonRand[ ch ] );
+
+ module->m_pButtonHoldCV[ ch ] = new MyLEDButton( x + 405, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 244, 244 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_HoldCV );
+ addChild( module->m_pButtonHoldCV[ ch ] );
+
+ module->m_pButtonBiLevel[ ch ] = new MyLEDButton( x + 425, y2, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 0, 244, 244 ), MyLEDButton::TYPE_SWITCH, ch, module, MyLEDButton_BiLevel );
+ addChild( module->m_pButtonBiLevel[ ch ] );
+
+ // add outputs
+ addChild(Port::create( Vec( x + 580, y + 7 ), Port::OUTPUT, module, SEQ_6x32x16::OUT_TRIG + ch ) );
+ addChild(Port::create( Vec( x + 544, y + 33 ), Port::OUTPUT, module, SEQ_6x32x16::OUT_LEVEL + ch ) );
+ addChild(Port::create( Vec( x + 37, y + 31 ), Port::OUTPUT, module, SEQ_6x32x16::OUT_BEAT1 + ch ) );
+
+ y += 56;
+ }
+
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
+
+ module->m_bInitialized = true;
+
+ reset();
+}
+};
+
+Model *modelSEQ_6x32x16_Widget = Model::create( "mscHack", "Seq_6ch_32step", "SEQ 6 x 32", SEQUENCER_TAG, MULTIPLE_TAG );
+
+
+//-----------------------------------------------------
+// Procedure:
+//
+//-----------------------------------------------------
+json_t *SEQ_6x32x16::toJson()
+{
+ bool *pbool;
+ int *pint;
+ json_t *rootJ = json_object();
+
+ // m_bPauseState
+ pbool = &m_bPauseState[ 0 ];
+
+ json_t *gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_boolean( (int) pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_bPauseState", gatesJ );
+
+ // m_bBiLevelState
+ pbool = &m_bBiLevelState[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_boolean( pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_bBiLevelState", gatesJ );
+
+ // m_Pattern
+ pint = &m_Pattern[ 0 ][ 0 ][ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS * nSTEPS * nPROG; i++)
+ {
+ json_t *gateJ = json_integer( pint[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_Pattern", gatesJ );
+
+ // m_MaxPat
+ pint = &m_MaxPat[ 0 ][ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS * nPROG; i++)
+ {
+ json_t *gateJ = json_integer( pint[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_MaxPat", gatesJ );
+
+ // m_CurrentProg
+ pint = &m_CurrentProg[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( pint[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_CurrentProg", gatesJ );
+
+ // m_MaxProg
+ pint = &m_MaxProg[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_integer( pint[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_MaxProg", gatesJ );
+
+ // m_bAutoPatChange
+ pbool = &m_bAutoPatChange[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_boolean( pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_bAutoPatChange", gatesJ );
+
+ // m_bHoldCVState
+ pbool = &m_bHoldCVState[ 0 ];
+
+ gatesJ = json_array();
+
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_boolean( pbool[ i ] );
+ json_array_append_new( gatesJ, gateJ );
+ }
+
+ json_object_set_new( rootJ, "m_bHoldCVState", gatesJ );
+
+ return rootJ;
+}
+
+//-----------------------------------------------------
+// Procedure: fromJson
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::fromJson(json_t *rootJ)
+{
+ bool *pbool;
+ int *pint;
+
+ // m_bPauseState
+ pbool = &m_bPauseState[ 0 ];
+
+ json_t *StepsJ = json_object_get(rootJ, "m_bPauseState");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_boolean_value( gateJ );
+ }
+ }
+
+ // m_bBiLevelState
+ pbool = &m_bBiLevelState[ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_bBiLevelState");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_boolean_value( gateJ );
+ }
+ }
+
+ // m_Pattern
+ pint = &m_Pattern[ 0 ][ 0 ][ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_Pattern");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS * nSTEPS * nPROG; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pint[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // m_MaxPat
+ pint = &m_MaxPat[ 0 ][ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_MaxPat");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS * nPROG; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pint[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // m_CurrentProg
+ pint = &m_CurrentProg[ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_CurrentProg");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pint[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // m_MaxProg
+ pint = &m_MaxProg[ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_MaxProg");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pint[ i ] = json_integer_value( gateJ );
+ }
+ }
+
+ // m_bAutoPatChange
+ pbool = &m_bAutoPatChange[ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_bAutoPatChange");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_boolean_value( gateJ );
+ }
+ }
+
+ // m_bHoldCVState
+ pbool = &m_bHoldCVState[ 0 ];
+
+ StepsJ = json_object_get(rootJ, "m_bHoldCVState");
+
+ if (StepsJ)
+ {
+ for (int i = 0; i < nCHANNELS; i++)
+ {
+ json_t *gateJ = json_array_get(StepsJ, i);
+
+ if (gateJ)
+ pbool[ i ] = json_boolean_value( gateJ );
+ }
+ }
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ m_pButtonAutoPat[ ch ]->Set( m_bAutoPatChange[ ch ] );
+ m_pButtonPause[ ch ]->Set( m_bPauseState[ ch ] );
+ m_pButtonBiLevel[ ch ]->Set( m_bBiLevelState[ ch ] );
+ m_pButtonHoldCV[ ch ]->Set( m_bHoldCVState[ ch ] );
+
+ m_pPatternDisplay[ ch ]->SetPatAll( m_Pattern[ ch ][ m_CurrentProg[ ch ] ] );
+ m_pPatternDisplay[ ch ]->SetMax( m_MaxPat[ ch ][ m_CurrentProg[ ch ] ] );
+
+ m_pProgramDisplay[ ch ]->SetPat( m_CurrentProg[ ch ], false );
+ m_pProgramDisplay[ ch ]->SetMax( m_MaxProg[ ch ] );
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: reset
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::reset()
+{
+ if( !m_bInitialized )
+ return;
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ m_pButtonPause[ ch ]->Set( false );
+ m_pButtonBiLevel[ ch ]->Set( false );
+ }
+
+ memset( m_bPauseState, 0, sizeof(m_bPauseState) );
+ memset( m_bBiLevelState, 0, sizeof(m_bBiLevelState) );
+ memset( m_Pattern, 0, sizeof(m_Pattern) );
+ memset( m_CurrentProg, 0, sizeof(m_CurrentProg) );
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ for( int prog = 0; prog < nCHANNELS; prog++ )
+ m_MaxPat[ ch ][ prog ] = nSTEPS - 1;
+
+ m_MaxProg[ ch ] = nPROG - 1;
+
+ m_pPatternDisplay[ ch ]->SetPatAll( m_Pattern[ ch ][ 0 ] );
+ m_pPatternDisplay[ ch ]->SetMax( m_MaxPat[ ch ][ 0 ] );
+
+ m_pProgramDisplay[ ch ]->SetPat( 0, false );
+ m_pProgramDisplay[ ch ]->SetMax( m_MaxProg[ ch ] );
+
+ m_pPatternDisplay[ ch ]->SetPatAll( m_Pattern[ ch ][ m_CurrentProg[ ch ] ] );
+
+ ChangeProg( ch, 0, true );
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: randomize
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::randomize()
+{
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ for( int p = 0; p < nPROG; p++ )
+ {
+ for( int i = 0; i < nSTEPS; i++ )
+ {
+ m_Pattern[ ch ][ p ][ i ] = (int)(randomUniform() * 3.4 );
+ }
+ }
+
+ m_pPatternDisplay[ ch ]->SetPatAll( m_Pattern[ ch ][ m_CurrentProg[ ch ] ] );
+ }
+}
+
+//-----------------------------------------------------
+// Procedure: Rand
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::Rand( int ch )
+{
+ for( int i = 0; i < nSTEPS; i++ )
+ {
+ m_Pattern[ ch ][ m_CurrentProg[ ch ] ][ i ] = (int)(randomUniform() * 3.4 );
+ }
+
+ m_pPatternDisplay[ ch ]->SetPatAll( m_Pattern[ ch ][ m_CurrentProg[ ch ] ] );
+}
+
+//-----------------------------------------------------
+// Procedure: CpyNext
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::CpyNext( int ch )
+{
+ int next;
+
+ next = m_CurrentProg[ ch ] + 1;
+
+ if( next >= nPROG )
+ return;
+
+ memcpy( m_Pattern[ ch ][ next ], m_Pattern[ ch ][ m_CurrentProg[ ch ] ], sizeof(int) * nSTEPS );
+
+ if(m_bPauseState[ ch ] || !inputs[ SEQ_6x32x16::IN_CLK + ch ].active )
+ ChangeProg( ch, next, false );
+}
+
+//-----------------------------------------------------
+// Procedure: ChangProg
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::ChangeProg( int ch, int prog, bool bforce )
+{
+ if( ch < 0 || ch >= nCHANNELS )
+ return;
+
+ if( !bforce && prog == m_CurrentProg[ ch ] )
+ return;
+
+ if( prog < 0 )
+ prog = nPROG - 1;
+ else if( prog >= nPROG )
+ prog = 0;
+
+ m_CurrentProg[ ch ] = prog;
+
+ m_pPatternDisplay[ ch ]->SetPatAll( m_Pattern[ ch ][ prog ] );
+ m_pPatternDisplay[ ch ]->SetMax( m_MaxPat[ ch ][ prog ] );
+ m_pProgramDisplay[ ch ]->SetPat( prog, false );
+}
+
+//-----------------------------------------------------
+// Procedure: SetPendingProg
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::SetPendingProg( int ch, int progIn )
+{
+ int prog;
+
+ if( progIn < 0 || progIn >= nPROG )
+ prog = ( m_CurrentProg[ ch ] + 1 ) & 0xF;
+ else
+ prog = progIn;
+
+ if( prog > m_MaxProg[ ch ] )
+ prog = 0;
+
+ m_ProgPending[ ch ].bPending = true;
+ m_ProgPending[ ch ].prog = prog;
+ m_pProgramDisplay[ ch ]->SetPat( m_CurrentProg[ ch ], false );
+ m_pProgramDisplay[ ch ]->SetPat( prog, true );
+}
+
+
+//-----------------------------------------------------
+// Procedure: step
+//
+//-----------------------------------------------------
+void SEQ_6x32x16::step()
+{
+ bool bClk, bClkTrig, bClockAtZero = false, bGlobalClk = false, bGlobalProg = false, bTrigOut;
+ float fout = 0.0;
+ int iclk = 0;
+
+ if( !m_bInitialized )
+ return;
+
+ if( inputs[ IN_GLOBAL_CLK_RESET ].active )
+ bGlobalClk = m_SchTrigGlobalClkReset.process( inputs[ IN_GLOBAL_CLK_RESET ].value );
+
+ if( inputs[ IN_GLOBAL_PAT_CHANGE ].active )
+ bGlobalProg= m_SchTrigGlobalProg.process( inputs[ IN_GLOBAL_PAT_CHANGE ].value );
+
+ for( int ch = 0; ch < nCHANNELS; ch++ )
+ {
+ if( bGlobalClk )
+ m_bGlobalClocked[ ch ] = true;
+
+ bClkTrig = false;
+ bClk = false;
+ bTrigOut = false;
+ bClockAtZero = true;
+
+ if( inputs[ IN_CLK + ch ].active )
+ {
+ bClockAtZero = false;
+ m_ClockTick[ ch ]++;
+
+ // time the clock tick
+ if( m_bGlobalClocked[ ch ] )
+ {
+ m_bGlobalClocked[ ch ] = false;
+ m_SwingCount[ ch ] = m_SwingLen[ ch ];
+ m_pPatternDisplay[ ch ]->ClockReset();
+ bClkTrig = true;
+ bClockAtZero = true;
+ bClk = true;
+ }
+
+ if( m_SchTrigClock[ ch ].process( inputs[ IN_CLK + ch ].value ) )
+ {
+ m_SwingLen[ ch ] = m_ClockTick[ ch ] + (int)( (float)m_ClockTick[ ch ] * params[ PARAM_SWING_KNOB + ch ].value );
+ m_ClockTick[ ch ] = 0;
+ bClkTrig = true;
+ }
+
+ if( params[ PARAM_SWING_KNOB + ch ].value < 0.05 )
+ {
+ bClk = bClkTrig;
+ }
+ else
+ {
+ // beat 1, wait for count to shorten every other note
+ if( !( m_pPatternDisplay[ ch ]->m_PatClk & 1 ) )
+ {
+ if( --m_SwingCount[ ch ] <= 0 )
+ bClk = true;
+ }
+ // beat 0, wait for actual clock
+ else if( bClkTrig )
+ {
+ m_SwingCount[ ch ] = m_SwingLen[ ch ];
+ bClk = true;
+ }
+ }
+
+ if( !m_bPauseState[ ch ] )
+ {
+ if( bGlobalProg || m_SchTrigProg[ ch ].process( inputs[ IN_PAT_TRIG + ch ].value ) )
+ SetPendingProg( ch, -1 );
+
+ // clock in
+ if( bClk )
+ {
+ if( !bClockAtZero )
+ bClockAtZero = m_pPatternDisplay[ ch ]->ClockInc();
+
+ bTrigOut = ( m_Pattern[ ch ][ m_CurrentProg[ ch ] ][ m_pPatternDisplay[ ch ]->m_PatClk ] );
+ }
+ }
+ }
+
+ iclk = m_pPatternDisplay[ ch ]->m_PatClk;
+
+ // auto inc pattern
+ if( m_bAutoPatChange[ ch ] && bClockAtZero )
+ {
+ SetPendingProg( ch, -1 );
+ m_ProgPending[ ch ].bPending = false;
+ ChangeProg( ch, m_ProgPending[ ch ].prog, true );
+ bTrigOut = ( m_Pattern[ ch ][ m_CurrentProg[ ch ] ][ m_pPatternDisplay[ ch ]->m_PatClk ] );
+ }
+ // resolve any left over phrase triggers
+ else if( m_ProgPending[ ch ].bPending && bClockAtZero )
+ {
+ m_ProgPending[ ch ].bPending = false;
+ ChangeProg( ch, m_ProgPending[ ch ].prog, false );
+ bTrigOut = ( m_Pattern[ ch ][ m_CurrentProg[ ch ] ][ m_pPatternDisplay[ ch ]->m_PatClk ] );
+ }
+
+ // trigger the beat 0 output
+ if( bClockAtZero )
+ m_gateBeatPulse[ ch ].trigger(1e-3);
+
+ outputs[ OUT_BEAT1 + ch ].value = m_gateBeatPulse[ ch ].process( 1.0 / engineGetSampleRate() ) ? CV_MAX : 0.0;
+
+ // trigger out
+ if( bTrigOut )
+ m_gatePulse[ ch ].trigger(1e-3);
+
+ outputs[ OUT_TRIG + ch ].value = m_gatePulse[ ch ].process( 1.0 / engineGetSampleRate() ) ? CV_MAX : 0.0;
+
+ int level = m_Pattern[ ch ][ m_CurrentProg[ ch ] ][ iclk ];
+
+ // get actual level from knobs
+ switch( level )
+ {
+ case 1:
+ fout = params[ PARAM_LO_KNOB + ch ].value;
+ break;
+ case 2:
+ fout = params[ PARAM_MD_KNOB + ch ].value;
+ break;
+ case 3:
+ fout = params[ PARAM_HI_KNOB + ch ].value;
+ break;
+ default:
+ if( m_bHoldCVState[ ch ] )
+ continue;
+ else
+ fout = 0.0f;
+ }
+
+ // bidirectional convert to -1.0 to 1.0
+ if( m_bBiLevelState[ ch ] )
+ fout = ( fout * 2 ) - 1.0;
+
+ outputs[ OUT_LEVEL + ch ].value = CV_MAX * fout;
+ }
+}
diff --git a/src/Seq_3x16x16.cpp b/src/Seq_3x16x16.cpp
index 32584db..d6e1eeb 100644
--- a/src/Seq_3x16x16.cpp
+++ b/src/Seq_3x16x16.cpp
@@ -13,9 +13,9 @@
// Module Definition
//
//-----------------------------------------------------
-struct Seq_3x16x16 : Module
+struct Seq_3x16x16 : Module
{
- enum ParamIds
+ enum ParamIds
{
PARAM_RAND,
PARAM_CPY_NEXT = PARAM_RAND + CHANNELS,
@@ -29,7 +29,7 @@ struct Seq_3x16x16 : Module
nPARAMS = PARAM_GLOBAL_PAT + ( PATTERNS )
};
- enum InputIds
+ enum InputIds
{
INPUT_EXT_CLOCK,
INPUT_RESET,
@@ -38,14 +38,27 @@ struct Seq_3x16x16 : Module
nINPUTS = INPUT_PAT_CHANGE + CHANNELS
};
- enum OutputIds
+ enum OutputIds
{
OUT_GATES,
OUT_CV = OUT_GATES + CHANNELS,
nOUTPUTS = OUT_CV + CHANNELS
};
- enum GateMode
+ enum LightIds
+ {
+ LIGHT_RUN,
+ LIGHT_RESET,
+ LIGHT_STEP_NUM,
+ LIGHT_STEP = LIGHT_STEP_NUM + ( STEPS ),
+ LIGHT_PAT = LIGHT_STEP + ( CHANNELS * STEPS ),
+ LIGHT_COPY = LIGHT_PAT + ( CHANNELS * PATTERNS ),
+ LIGHT_RAND = LIGHT_COPY + CHANNELS,
+ LIGHT_GLOBAL_PAT = LIGHT_RAND + CHANNELS,
+ nLIGHTS = LIGHT_GLOBAL_PAT + PATTERNS
+ };
+
+ enum GateMode
{
TRIGGER,
RETRIGGER,
@@ -64,22 +77,17 @@ struct Seq_3x16x16 : Module
SchmittTrigger m_SchTrigRun;
SchmittTrigger m_SchTrigReset;
SchmittTrigger m_SchTrigPatChange[ CHANNELS ];
- float m_fLightRun = 0.0;
- float m_fLightReset = 0.0;
// random pattern button
SchmittTrigger m_SchTrigRandPat;
- float m_fLightRandPat[ CHANNELS ] = {};
// copy next buttons
SchmittTrigger m_SchTrigCopyNext;
- float m_fLightCopyNext[ CHANNELS ] = {};
// number of steps
- int m_nSteps = STEPS;
+ int m_nSteps = STEPS;
SchmittTrigger m_SchTrigStepNumbers[ STEPS ];
- float m_fLightStepNumbers[ STEPS ] = {};
-
+
// Level settings
ParamWidget *m_pLevelToggleParam2[ CHANNELS ][ STEPS ] = {};
SchmittTrigger m_SchTrigLevels[ CHANNELS ][ STEPS ];
@@ -87,15 +95,13 @@ struct Seq_3x16x16 : Module
// Global Pattern Select
SchmittTrigger m_SchTrigGlobalPatternSelect;
- float m_fLightGlobalPatternSelects[ PATTERNS ] = {};
int m_GlobalSelect = 0;
bool m_bGlobalPatternChangePending = false;
int m_GlobalPendingLight = 0;
// Pattern Select
- int m_PatternSelect[ CHANNELS ] = {0};
+ int m_PatternSelect[ CHANNELS ] = {0};
SchmittTrigger m_SchTrigPatternSelects[ CHANNELS ][ PATTERNS ];
- float m_fLightPatternSelects[ CHANNELS ][ PATTERNS ] = {};
bool m_bPatternPending = false;
bool m_bPatternChangePending[ CHANNELS ] = {0};
int m_PendingLight[ CHANNELS ] = {0};
@@ -103,23 +109,18 @@ struct Seq_3x16x16 : Module
// Steps
int m_CurrentStep = 0;
SchmittTrigger m_SchTrigSteps[ CHANNELS ][ STEPS ];
- float m_fLightSteps[ CHANNELS ][ STEPS ] = {};
+ bool m_bStepStates[ PATTERNS ][ CHANNELS ][ STEPS ] = {};
float m_fLightStepLevels[ CHANNELS ][ STEPS ] = {};
- bool m_bStepStates[ PATTERNS ][ CHANNELS ][ STEPS ] = {};
// Contructor
- Seq_3x16x16() : Module(nPARAMS, nINPUTS, nOUTPUTS)
- {
- //m_fLightPatternSelects[ 0 ] = 1.0;
- }
+ Seq_3x16x16() : Module( nPARAMS, nINPUTS, nOUTPUTS, nLIGHTS ){}
- // Overrides
+ // Overrides
void step() override;
json_t* toJson() override;
void fromJson(json_t *rootJ) override;
void randomize() override;
- void initialize() override;
- //void reset() override;
+ void reset() override;
void Randomize_Channel( int ch );
void CopyNext( int ch );
@@ -137,7 +138,7 @@ struct MySquareButton_CopyNext : MySquareButton
Seq_3x16x16 *mymodule;
int param;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -147,7 +148,7 @@ struct MySquareButton_CopyNext : MySquareButton
mymodule->CopyNext( param );
}
- MomentarySwitch::onChange();
+ MomentarySwitch::onChange( e );
}
};
@@ -160,7 +161,7 @@ struct MySquareButton_Rand : MySquareButton
Seq_3x16x16 *mymodule;
int param;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -170,7 +171,7 @@ struct MySquareButton_Rand : MySquareButton
mymodule->Randomize_Channel( param );
}
- MomentarySwitch::onChange();
+ MomentarySwitch::onChange( e );
}
};
@@ -183,7 +184,7 @@ struct MySquareButton_GlobalPattern : MySquareButton //SVGSwitch, MomentarySwitc
Seq_3x16x16 *mymodule;
int param;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -205,7 +206,7 @@ struct MySquareButton_GlobalPattern : MySquareButton //SVGSwitch, MomentarySwitc
}
}
- MomentarySwitch::onChange();
+ MomentarySwitch::onChange( e );
}
};
@@ -218,7 +219,7 @@ struct MySquareButton_Pattern : MySquareButton //SVGSwitch, MomentarySwitch
Seq_3x16x16 *mymodule;
int ch, stp, param;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -245,7 +246,7 @@ struct MySquareButton_Pattern : MySquareButton //SVGSwitch, MomentarySwitch
}
}
- MomentarySwitch::onChange();
+ MomentarySwitch::onChange( e );
}
};
@@ -253,12 +254,12 @@ struct MySquareButton_Pattern : MySquareButton //SVGSwitch, MomentarySwitch
// Procedure: MySquareButton_Step
//
//-----------------------------------------------------
-struct MySquareButton_Step : MySquareButton //SVGSwitch, MomentarySwitch
+struct MySquareButton_Step : MySquareButton //SVGSwitch, MomentarySwitch
{
Seq_3x16x16 *mymodule;
int ch, stp, param;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -270,10 +271,10 @@ struct MySquareButton_Step : MySquareButton //SVGSwitch, MomentarySwitch
//lg.f( (char*)"step = %d, ch = %d, stp = %d\n", param, ch, stp );
mymodule->m_bStepStates[ mymodule->m_PatternSelect[ ch ] ][ ch ][ stp ] = !mymodule->m_bStepStates[ mymodule->m_PatternSelect[ ch ] ][ ch ][ stp ];
- mymodule->m_fLightSteps[ ch ][ stp ] = mymodule->m_bStepStates[ mymodule->m_PatternSelect[ ch ] ][ ch ][ stp ] ? 1.0 : 0.0;
+ mymodule->lights[ Seq_3x16x16::LIGHT_STEP + ( ch * STEPS ) + stp ].value = mymodule->m_bStepStates[ mymodule->m_PatternSelect[ ch ] ][ ch ][ stp ] ? 1.0f : 0.0f;
}
- MomentarySwitch::onChange();
+ MomentarySwitch::onChange( e );
}
};
@@ -281,12 +282,12 @@ struct MySquareButton_Step : MySquareButton //SVGSwitch, MomentarySwitch
// Procedure: MySquareButton_Step
//
//-----------------------------------------------------
-struct MySquareButton_StepNum : MySquareButton
+struct MySquareButton_StepNum : MySquareButton
{
Seq_3x16x16 *mymodule;
int stp, i;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -296,7 +297,7 @@ struct MySquareButton_StepNum : MySquareButton
mymodule->SetSteps( stp + 1 );
}
- MomentarySwitch::onChange();
+ MomentarySwitch::onChange( e );
}
};
@@ -309,7 +310,7 @@ struct MySlider_Levels : MySlider_01
Seq_3x16x16 *mymodule;
int ch, stp, param;
- void onChange() override
+ void onChange( EventChange &e ) override
{
mymodule = (Seq_3x16x16*)module;
@@ -324,7 +325,7 @@ struct MySlider_Levels : MySlider_01
mymodule->m_fLevels[ mymodule->m_PatternSelect[ ch ] ][ ch ][ stp ] = value;
}
- SVGSlider::onChange();
+ SVGFader::onChange( e );
}
};
@@ -332,79 +333,72 @@ struct MySlider_Levels : MySlider_01
// Procedure: Widget
//
//-----------------------------------------------------
-Seq_3x16x16_Widget::Seq_3x16x16_Widget()
+struct Seq_3x16x16_Widget : ModuleWidget {
+ Seq_3x16x16_Widget(Seq_3x16x16 *module) : ModuleWidget(module)
{
int i, ch, stp, x, y;
- Seq_3x16x16 *module = new Seq_3x16x16();
- setModule(module);
- box.size = Vec(15*23, 380);
-
- {
- SVGPanel *panel = new SVGPanel();
- panel->box.size = box.size;
- panel->setBackground(SVG::load(assetPlugin(plugin, "res/Seq_3x16x16.svg")));
- addChild(panel);
- }
+
+ setPanel(SVG::load(assetPlugin(plugin, "res/Seq_3x16x16.svg")));
//module->lg.Open("Seq_3x16x16.txt");
- addChild(createScrew(Vec(15, 0)));
- addChild(createScrew(Vec(box.size.x-30, 0)));
- addChild(createScrew(Vec(15, 365)));
- addChild(createScrew(Vec(box.size.x-30, 365)));
+ addChild(Widget::create(Vec(15, 0)));
+ addChild(Widget::create(Vec(box.size.x-30, 0)));
+ addChild(Widget::create(Vec(15, 365)));
+ addChild(Widget::create(Vec(box.size.x-30, 365)));
// clk/run button
- addParam(createParam(Vec( 78, 17 ), module, Seq_3x16x16::PARAM_RUN, 0.0, 1.0, 0.0 ) );
- addChild(createValueLight>( Vec( 78 + 5, 17 + 5 ), &module->m_fLightRun ) );
- addInput(createInput