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) + +![screenshot](modules.PNG) + +# 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + + D1 + D2 + D3 + D4 + C1 + C2 + C3 + C4 + B1 + B2 + B3 + B4 + A1 + A2 + A3 + A4 + M + M + M + M + M + M + M + M + S + S + S + S + A + B + C + D + S + S + S + S + M + M + M + M + S + S + S + S + M + M + M + M + S + S + S + S + M + M + M + M + S + S + S + S + L / Mono + R + LEVEL + PAN + L + L + L + L + L + R + R + R + R + R + OUT + + + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + sdffds sdfds + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + sdffds sdfds + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + sdffds sdfds + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + sdffds sdfds + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + sdffds sdfds + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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( Vec( 47, 17 ), module, Seq_3x16x16::INPUT_EXT_CLOCK ) ); + addParam(ParamWidget::create(Vec( 78, 17 ), module, Seq_3x16x16::PARAM_RUN, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( 78 + 5, 17 + 5 ), module, Seq_3x16x16::LIGHT_RUN ) ); + addChild(Port::create( Vec( 47, 17 ), Port::INPUT, module, Seq_3x16x16::INPUT_EXT_CLOCK ) ); // reset button - addParam(createParam( Vec( 143, 17 ), module, Seq_3x16x16::PARAM_RESET, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>(Vec( 143 + 5, 17 + 5 ), &module->m_fLightReset ) ); - addInput(createInput( Vec( 117, 17 ), module, Seq_3x16x16::INPUT_RESET ) ); + addParam(ParamWidget::create( Vec( 143, 17 ), module, Seq_3x16x16::PARAM_RESET, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( 143 + 5, 17 + 5 ), module, Seq_3x16x16::LIGHT_RESET ) ); + addChild(Port::create( Vec( 117, 17 ), Port::INPUT, module, Seq_3x16x16::INPUT_RESET ) ); //---------------------------------------------------- - // Step Select buttons + // Step Select buttons y = 41; x = 50; - for ( stp = 0; stp < STEPS; stp++ ) + for ( stp = 0; stp < STEPS; stp++ ) { // step button - addParam(createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_STEP_NUM + stp, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>( Vec( x + 1, y + 2 ), &module->m_fLightStepNumbers[ stp ] ) ); + addParam(ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_STEP_NUM + stp, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 3 ), module, Seq_3x16x16::LIGHT_STEP_NUM + stp ) ); if( ( stp & 0x3 ) == 0x3 ) x += 20; else x += 15; - module->m_fLightStepNumbers[ stp ] = 1.0; + module->lights[ Seq_3x16x16::LIGHT_STEP_NUM + stp ].value = 1.0; } //---------------------------------------------------- - // All the Channel buttons + // All the Channel buttons // channel - for ( ch = 0; ch < CHANNELS; ch++ ) + for ( ch = 0; ch < CHANNELS; ch++ ) { x = 50; // step - for ( stp = 0; stp < STEPS; stp++ ) + for ( stp = 0; stp < STEPS; stp++ ) { y = 64 + (ch * CHANNEL_OFF_H); // level button - module->m_pLevelToggleParam2[ ch ][ stp ] = createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_SLIDERS + ( ch * STEPS ) + stp , 0.0, 6.0, 0.0); + module->m_pLevelToggleParam2[ ch ][ stp ] = ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_SLIDERS + ( ch * STEPS ) + stp , 0.0, 6.0, 0.0); addParam( module->m_pLevelToggleParam2[ ch ][ stp ] ); y += 46; // step button - addParam(createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_STEPS + ( ch * STEPS ) + stp, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>( Vec( x + 1, y + 2 ), &module->m_fLightSteps[ ch ][ stp ] ) ); + addParam(ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_STEPS + ( ch * STEPS ) + stp, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 3 ), module, Seq_3x16x16::LIGHT_STEP + ( ch * STEPS ) + stp ) ); if( ( stp & 0x3 ) == 0x3 ) x += 20; @@ -415,11 +409,11 @@ Seq_3x16x16_Widget::Seq_3x16x16_Widget() x = 45; y = 130 + (ch * CHANNEL_OFF_H); - for ( i = 0; i < PATTERNS; i++ ) + for ( i = 0; i < PATTERNS; i++ ) { // pattern button - addParam(createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_PATTERNS + ( ch * PATTERNS ) + i, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>( Vec( x + 1, y + 2 ), &module->m_fLightPatternSelects[ ch ][ i ] ) ); + addParam(ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_PATTERNS + ( ch * PATTERNS ) + i, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 3 ), module, Seq_3x16x16::LIGHT_PAT + ( ch * PATTERNS ) + i ) ); x += 13; } @@ -428,57 +422,60 @@ Seq_3x16x16_Widget::Seq_3x16x16_Widget() y = 126 + (ch * CHANNEL_OFF_H); // copy next button - addParam(createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_CPY_NEXT + ch, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>( Vec( x + 1, y + 2 ), &module->m_fLightCopyNext[ ch ] ) ); + addParam(ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_CPY_NEXT + ch, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 3 ), module, Seq_3x16x16::LIGHT_COPY + ch ) ); x = 290; // random button - addParam(createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_RAND + ch, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>( Vec( x + 1, y + 2 ), &module->m_fLightRandPat[ ch ] ) ); + addParam(ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_RAND + ch, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 3 ), module, Seq_3x16x16::LIGHT_RAND + ch ) ); // pattern change input trigger - addInput(createInput( Vec( 17, 127 + (ch * CHANNEL_OFF_H) ), module, Seq_3x16x16::INPUT_PAT_CHANGE + ch ) ); + addChild(Port::create( Vec( 17, 127 + (ch * CHANNEL_OFF_H) ), Port::INPUT, module, Seq_3x16x16::INPUT_PAT_CHANGE + ch ) ); x = 310; y = 65 + (ch * CHANNEL_OFF_H); // outputs - addOutput(createOutput( Vec( x, y ), module, Seq_3x16x16::OUT_CV + ch ) ); - addOutput(createOutput( Vec( x, y + 42 ), module, Seq_3x16x16::OUT_GATES + ch ) ); + addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, Seq_3x16x16::OUT_CV + ch ) ); + addChild(Port::create( Vec( x, y + 42 ), Port::OUTPUT, module, Seq_3x16x16::OUT_GATES + ch ) ); } //---------------------------------------------------- - // Global patter select buttons + // Global patter select buttons x = 45; y = 340; - for ( stp = 0; stp < STEPS; stp++ ) + for ( stp = 0; stp < PATTERNS; stp++ ) { // pattern button - addParam(createParam( Vec( x, y ), module, Seq_3x16x16::PARAM_GLOBAL_PAT + stp, 0.0, 1.0, 0.0 ) ); - addChild(createValueLight>( Vec( x + 1, y + 2 ), &module->m_fLightGlobalPatternSelects[ stp ] ) ); + addParam(ParamWidget::create( Vec( x, y ), module, Seq_3x16x16::PARAM_GLOBAL_PAT + stp, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 3 ), module, Seq_3x16x16::LIGHT_GLOBAL_PAT + stp ) ); x += 13; } // pattern change input trigger - addInput(createInput( Vec( 17, 338 ), module, Seq_3x16x16::INPUT_GLOBAL_PAT_CHANGE ) ); + addChild(Port::create( Vec( 17, 338 ), Port::INPUT, module, Seq_3x16x16::INPUT_GLOBAL_PAT_CHANGE ) ); - //lg.f("sample rate = %.3f", gSampleRate ); - module->m_fLightGlobalPatternSelects[ 0 ] = 1.0; + module->lights[ Seq_3x16x16::LIGHT_GLOBAL_PAT ].value = 1.0; module->m_GlobalSelect = 0; module->ChangePattern( 0, PATTERNS, false ); module->ChangePattern( 1, PATTERNS, false ); module->ChangePattern( 2, PATTERNS, false ); } +}; + +Model *modelSeq_3x16x16_Widget = Model::create( "mscHack", "Seq_3ch_16step", "SEQ 3 x 16", SEQUENCER_TAG, MULTIPLE_TAG ); + //----------------------------------------------------- -// Procedure: +// Procedure: // //----------------------------------------------------- -json_t *Seq_3x16x16::toJson() +json_t *Seq_3x16x16::toJson() { bool *pgateState; float *pfloat; @@ -505,7 +502,7 @@ json_t *Seq_3x16x16::toJson() json_t *gatesJ_3 = json_array(); - for (int i = 0; i < ( PATTERNS * CHANNELS * STEPS ); i++) + for (int i = 0; i < ( PATTERNS * CHANNELS * STEPS ); i++) { json_t *gateJ = json_real( pfloat[ i ] ); json_array_append_new(gatesJ_3, gateJ); @@ -528,7 +525,7 @@ json_t *Seq_3x16x16::toJson() // Procedure: fromJson // //----------------------------------------------------- -void Seq_3x16x16::fromJson(json_t *rootJ) +void Seq_3x16x16::fromJson(json_t *rootJ) { bool *pgateState; float *pfloat; @@ -544,7 +541,7 @@ void Seq_3x16x16::fromJson(json_t *rootJ) json_t *StepsJ = json_object_get(rootJ, "steps"); - if (StepsJ) + if (StepsJ) { for (int i = 0; i < ( PATTERNS * CHANNELS * STEPS ); i++) { @@ -560,9 +557,9 @@ void Seq_3x16x16::fromJson(json_t *rootJ) json_t *LvlSettingsJ = json_object_get(rootJ, "levelsettings"); - if (LvlSettingsJ) + if (LvlSettingsJ) { - for (int i = 0; i < ( PATTERNS * CHANNELS * STEPS ); i++) + for (int i = 0; i < ( PATTERNS * CHANNELS * STEPS ); i++) { json_t *gateJ = json_array_get(LvlSettingsJ, i); @@ -590,16 +587,23 @@ void Seq_3x16x16::fromJson(json_t *rootJ) } //----------------------------------------------------- -// Procedure: initialize +// Procedure: reset // //----------------------------------------------------- -void Seq_3x16x16::initialize() +void Seq_3x16x16::reset() { //lg.f("init\n"); memset( m_fLevels, 0, sizeof(m_fLevels) ); memset( m_bStepStates, 0, sizeof(m_bStepStates) ); - memset( m_fLightSteps, 0, sizeof(m_fLightSteps) ); - memset( m_fLightStepLevels, 0, sizeof(m_fLightStepLevels) ); + + for( int ch = 0; ch < CHANNELS; ch ++ ) + { + for( int i = 0; i < STEPS; i ++ ) + { + lights[ LIGHT_STEP + ( ch * STEPS ) + i ].value = 0.0; + m_fLightStepLevels[ ch ][ i ] = 0.0; + } + } ChangePattern( 0, PATTERNS, false ); ChangePattern( 1, PATTERNS, false ); @@ -608,22 +612,11 @@ void Seq_3x16x16::initialize() SetSteps( 16 ); } -//----------------------------------------------------- -// Procedure: reset -// -//----------------------------------------------------- -/*void reset() -{ - for (int i = 0; i < 8; i++) { - gateState[i] = false; - } -}*/ - //----------------------------------------------------- // Procedure: randomize // //----------------------------------------------------- -float stepchance[ 16 ] = { 0.9, 0.5, 0.8, 0.4, 0.9, 0.5, 0.8, 0.4, 0.9, 0.5, 0.8, 0.4, 0.9, 0.5, 0.8, 0.4 }; +float stepchance[ 16 ] = { 0.9, 0.5, 0.8, 0.4, 0.9, 0.5, 0.8, 0.4, 0.9, 0.5, 0.8, 0.4, 0.9, 0.5, 0.8, 0.4 }; void Seq_3x16x16::Randomize_Channel( int ch ) { int stp, pat; @@ -631,12 +624,12 @@ void Seq_3x16x16::Randomize_Channel( int ch ) pat = m_PatternSelect[ ch ]; - octave = (int)( randomf() * 4.0 ); + octave = (int)( randomUniform() * 4.0 ); for( stp = 0; stp < STEPS; stp++ ) { - m_fLevels[ pat ][ ch ][ stp ] = (float)octave + ( randomf() * 2.0 ); - m_bStepStates[ pat ][ ch ][ stp ] = ( randomf() < stepchance[ stp ] ) ? true : false; + m_fLevels[ pat ][ ch ][ stp ] = (float)octave + ( randomUniform() * 2.0 ); + m_bStepStates[ pat ][ ch ][ stp ] = ( randomUniform() < stepchance[ stp ] ) ? true : false; } ChangePattern( ch, pat, true ); @@ -646,7 +639,7 @@ void Seq_3x16x16::Randomize_Channel( int ch ) // Procedure: randomize // //----------------------------------------------------- -void Seq_3x16x16::randomize() +void Seq_3x16x16::randomize() { int ch, stp, pat; int octave; @@ -655,12 +648,12 @@ void Seq_3x16x16::randomize() { for( pat = 0; pat < PATTERNS; pat++ ) { - octave = (int)( randomf() * 4.0 ); + octave = (int)( randomUniform() * 4.0 ); for( stp = 0; stp < STEPS; stp++ ) { - m_fLevels[ pat ][ ch ][ stp ] = (float)octave + ( randomf() * 2.0 ); - m_bStepStates[ pat ][ ch ][ stp ] = ( randomf() < stepchance[ stp ] ) ? true : false; + m_fLevels[ pat ][ ch ][ stp ] = (float)octave + ( randomUniform() * 2.0 ); + m_bStepStates[ pat ][ ch ][ stp ] = ( randomUniform() < stepchance[ stp ] ) ? true : false; } } } @@ -712,21 +705,19 @@ void Seq_3x16x16::ChangePattern( int ch, int index, bool bForceChange ) m_PatternSelect[ ch ] = index; // update lights - for( stp = 0; stp < STEPS; stp++ ) - { - m_fLightSteps[ ch ][ stp ] = m_bStepStates[ m_PatternSelect[ ch ] ][ ch ][ stp ] ? 1.0 : 0.0; - } + for( stp = 0; stp < STEPS; stp++ ) + lights[ LIGHT_STEP + ( ch * STEPS ) + stp ].value = m_bStepStates[ m_PatternSelect[ ch ] ][ ch ][ stp ] ? 1.0 : 0.0; for( i = 0; i < PATTERNS; i++ ) { if( m_PatternSelect[ ch ] == i ) - m_fLightPatternSelects[ ch ][ i ] = 1.0; + lights[ LIGHT_PAT + ( ch * PATTERNS ) + i ].value = 1.0f; else - m_fLightPatternSelects[ ch ][ i ] = 0.0; + lights[ LIGHT_PAT + ( ch * PATTERNS ) + i ].value = 0.0f; } // step - for( stp = 0; stp < STEPS; stp++ ) + for( stp = 0; stp < STEPS; stp++ ) { // level button m_pLevelToggleParam2[ ch ][ stp ]->setValue( m_fLevels[ m_PatternSelect[ ch ] ][ ch ][ stp ] ); @@ -746,13 +737,13 @@ void Seq_3x16x16::SetSteps( int nSteps ) m_nSteps = nSteps; - for ( i = 0; i < STEPS; i++ ) + for ( i = 0; i < STEPS; i++ ) { // level button if( i < nSteps ) - m_fLightStepNumbers[ i ] = 1.0f; + lights[ LIGHT_STEP_NUM + i ].value = 1.0f; else - m_fLightStepNumbers[ i ] = 0.0f; + lights[ LIGHT_STEP_NUM + i ].value = 0.0f; } } @@ -762,8 +753,10 @@ void Seq_3x16x16::SetSteps( int nSteps ) //----------------------------------------------------- void Seq_3x16x16::SetGlobalPattern( int step ) { - memset( m_fLightGlobalPatternSelects, 0, sizeof(m_fLightGlobalPatternSelects) ); - m_fLightGlobalPatternSelects[ step ] = 1.0; + for ( int i = 0; i < PATTERNS; i++ ) + lights[ LIGHT_GLOBAL_PAT + i ].value = 0.0f; + + lights[ LIGHT_GLOBAL_PAT + step ].value = 1.0; m_GlobalSelect = step; @@ -776,7 +769,7 @@ void Seq_3x16x16::SetGlobalPattern( int step ) // Procedure: step // //----------------------------------------------------- -void Seq_3x16x16::step() +void Seq_3x16x16::step() { bool bNextStep = false, bPulse, bGatesOn, bChTrig; int ch, stp; @@ -788,7 +781,7 @@ void Seq_3x16x16::step() // pending light value if( bUp ) { - fpendinglight += ( 1.0 / gSampleRate ) * 1.5 ; + fpendinglight += ( 1.0 / engineGetSampleRate() ) * 1.5 ; if( fpendinglight >= 0.5f ) { @@ -798,7 +791,7 @@ void Seq_3x16x16::step() } else { - fpendinglight -= ( 1.0 / gSampleRate ) * 2; + fpendinglight -= ( 1.0 / engineGetSampleRate() ) * 2; if( fpendinglight <= 0.0f ) { @@ -811,30 +804,30 @@ void Seq_3x16x16::step() if( m_SchTrigRun.process( params[ PARAM_RUN ].value ) ) m_bRunning = !m_bRunning; - m_fLightRun = m_bRunning ? 1.0 : 0.0; + lights[ LIGHT_RUN ].value = m_bRunning ? 1.0f : 0.0f; // Random and copy buttons - for( ch = 0; ch < CHANNELS; ch++ ) + for( ch = 0; ch < CHANNELS; ch++ ) { - if( m_SchTrigRandPat.process( params[ PARAM_RAND + ch ].value ) ) - m_fLightRandPat[ ch ] = 1.0; + if( m_SchTrigRandPat.process( params[ PARAM_RAND + ch ].value ) ) + lights[ LIGHT_RAND + ch ].value = 1.0f; - if( m_SchTrigCopyNext.process( params[ PARAM_CPY_NEXT + ch ].value ) ) - m_fLightCopyNext[ ch ] = 1.0; + if( m_SchTrigCopyNext.process( params[ PARAM_CPY_NEXT + ch ].value ) ) + lights[ LIGHT_COPY + ch ].value = 1.0f; - m_fLightRandPat[ ch ] -= m_fLightRandPat[ ch ] / LIGHT_LAMBDA / gSampleRate; + lights[ LIGHT_RAND + ch ].value -= lights[ LIGHT_RAND + ch ].value / LIGHT_LAMBDA / engineGetSampleRate(); - m_fLightCopyNext[ ch ] -= m_fLightCopyNext[ ch ] / LIGHT_LAMBDA / gSampleRate; + lights[ LIGHT_COPY + ch ].value -= lights[ LIGHT_COPY + ch ].value / LIGHT_LAMBDA / engineGetSampleRate(); } - if( m_bRunning ) + if( m_bRunning ) { - if( inputs[ INPUT_EXT_CLOCK ].active ) + if( inputs[ INPUT_EXT_CLOCK ].active ) { // External clock - if( m_SchTrigClock.process( inputs[ INPUT_EXT_CLOCK ].value ) ) + if( m_SchTrigClock.process( inputs[ INPUT_EXT_CLOCK ].value ) ) { - m_fPhase = 0.0; + m_fPhase = 0.0f; bNextStep = true; } } @@ -862,23 +855,23 @@ void Seq_3x16x16::step() } // Reset - if( m_SchTrigReset.process( params[ PARAM_RESET ].value + inputs[ INPUT_RESET ].value ) ) + if( m_SchTrigReset.process( params[ PARAM_RESET ].value + inputs[ INPUT_RESET ].value ) ) { - m_fPhase = 0.0; + m_fPhase = 0.0f; m_CurrentStep = m_nSteps; bNextStep = true; - m_fLightReset = 1.0; + lights[ LIGHT_RESET ].value = 1.0f; } - if( bNextStep ) + if( bNextStep ) { m_CurrentStep += 1; - if( m_CurrentStep >= m_nSteps ) + if( m_CurrentStep >= m_nSteps ) m_CurrentStep = 0; for( ch = 0; ch < CHANNELS; ch++ ) - m_fLightStepLevels[ ch ][ m_CurrentStep ] = 1.0; + m_fLightStepLevels[ ch ][ m_CurrentStep ] = 1.0f; m_PulseStep.trigger( 1e-3 ); @@ -913,7 +906,7 @@ void Seq_3x16x16::step() // global pattern pending change light blinky if( m_bGlobalPatternChangePending ) { - m_fLightGlobalPatternSelects[ m_GlobalPendingLight ] = fpendinglight; + lights[ LIGHT_GLOBAL_PAT + m_GlobalPendingLight ].value = fpendinglight; } // channel pattern pending change light blinky @@ -927,21 +920,21 @@ void Seq_3x16x16::step() if( m_bPatternChangePending[ ch ] ) { m_bPatternPending = true; - m_fLightPatternSelects[ ch ][ m_PendingLight[ ch ] ] = fpendinglight; + lights[ LIGHT_PAT + (ch * PATTERNS) + m_PendingLight[ ch ] ].value = fpendinglight; } } } - m_fLightReset -= m_fLightReset / LIGHT_LAMBDA / gSampleRate; + lights[ LIGHT_RESET ].value -= lights[ LIGHT_RESET ].value / LIGHT_LAMBDA / engineGetSampleRate(); - bPulse = m_PulseStep.process( 1.0 / gSampleRate ); + bPulse = m_PulseStep.process( 1.0 / engineGetSampleRate() ); // Step buttons - for( ch = 0; ch < CHANNELS; ch++ ) + for( ch = 0; ch < CHANNELS; ch++ ) { bChTrig = false; - for( stp = 0; stp < STEPS; stp++ ) + for( stp = 0; stp < STEPS; stp++ ) { bGatesOn = ( m_bRunning && stp == m_CurrentStep && m_bStepStates[ m_PatternSelect[ ch ] ][ ch ][ stp ] ); @@ -952,17 +945,18 @@ void Seq_3x16x16::step() if( bGatesOn ) bChTrig = true; - - m_fLightStepLevels[ ch ][ stp ] -= m_fLightStepLevels[ ch ][ stp ] / LIGHT_LAMBDA / gSampleRate; - m_fLightSteps[ ch ][ stp ] = m_bStepStates[ m_PatternSelect[ ch ] ][ ch ][ stp ] ? 1.0 - m_fLightStepLevels[ ch ][ stp ] : m_fLightStepLevels[ ch ][ stp ]; + + m_fLightStepLevels[ ch ][ stp ] -= m_fLightStepLevels[ ch ][ stp ] / LIGHT_LAMBDA / engineGetSampleRate(); + + lights[ LIGHT_STEP + (ch * STEPS) + stp ].value = m_bStepStates[ m_PatternSelect[ ch ] ][ ch ][ stp ] ? 1.0 - m_fLightStepLevels[ ch ][ stp ] : m_fLightStepLevels[ ch ][ stp ]; } // trigger - outputs[ OUT_GATES + ch ].value = bChTrig ? 10.0 : 0.0; + outputs[ OUT_GATES + ch ].value = bChTrig ? CV_MAX : 0.0; } // outputs - for( ch = 0; ch < CHANNELS; ch++ ) + for( ch = 0; ch < CHANNELS; ch++ ) { // set CV if( m_bRunning && m_bStepStates[ m_PatternSelect[ ch ] ][ ch ][ m_CurrentStep ] ) @@ -981,7 +975,7 @@ void Seq_3x16x16::step() for( ch = 0; ch < CHANNELS; ch++ ) { // pat change trigger - ignore if already pending - if( m_SchTrigPatChange[ ch ].process( inputs[ INPUT_PAT_CHANGE + ch ].value ) && !m_bPatternChangePending[ ch ] ) + if( m_SchTrigPatChange[ ch ].process( inputs[ INPUT_PAT_CHANGE + ch ].value ) && !m_bPatternChangePending[ ch ] ) { m_bPatternPending = true; m_bPatternChangePending[ ch ] = true; @@ -990,7 +984,7 @@ void Seq_3x16x16::step() } // global pat change trigger - ignore if already pending - if( m_SchTrigGlobalPatternSelect.process( inputs[ INPUT_GLOBAL_PAT_CHANGE ].value ) && !m_bGlobalPatternChangePending ) + if( m_SchTrigGlobalPatternSelect.process( inputs[ INPUT_GLOBAL_PAT_CHANGE ].value ) && !m_bGlobalPatternChangePending ) { m_bGlobalPatternChangePending = true; m_GlobalPendingLight = ( m_GlobalSelect + 1 ) & 0xF; diff --git a/src/Seq_Triad.cpp b/src/Seq_Triad.cpp new file mode 100644 index 0000000..60537f2 --- /dev/null +++ b/src/Seq_Triad.cpp @@ -0,0 +1,1062 @@ +#include "mscHack.hpp" +#include "mscHack_Controls.hpp" +#include "dsp/digital.hpp" +#include "CLog.h" + +#define nKEYBOARDS 3 +#define nKEYS 37 +#define nOCTAVESEL 4 +#define nPATTERNS 16 +#define nPHRASE_SAVES 4 + +#define CHANNEL_X 15 +#define CHANNEL_Y 78 +#define CHANNEL_OFF_Y 100 + +#define SEMI ( 1.0f / 12.0f ) + +typedef struct +{ + float fsemi; + +}KEYBOARD_KEY_STRUCT; + +typedef struct +{ + int note; + int oct; + bool bTrigOff; + int pad[ 5 ]; + +}PATTERN_STRUCT; + +typedef struct +{ + bool bPending; + int phrase; +}PHRASE_CHANGE_STRUCT; + +//----------------------------------------------------- +// Module Definition +// +//----------------------------------------------------- +struct Seq_Triad : Module +{ + enum ParamIds + { + PARAM_COPY_NEXT, + PARAM_PAUSE, + PARAM_STEP_NUM, + PARAM_OCTAVES = PARAM_STEP_NUM + (nPATTERNS), + PARAM_PATTERNS = PARAM_OCTAVES + (nOCTAVESEL * nKEYBOARDS), + PARAM_GLIDE = PARAM_PATTERNS + (nPATTERNS ), + PARAM_TRIGOFF = PARAM_GLIDE + ( nKEYBOARDS ), + PARAM_PHRASE_SAVES = PARAM_TRIGOFF + ( nKEYBOARDS ), + PARAM_PHRASE_USED = PARAM_PHRASE_SAVES + ( nKEYBOARDS * nPHRASE_SAVES ), + nPARAMS = PARAM_PHRASE_USED + ( nKEYBOARDS * nPHRASE_SAVES ) + }; + + enum InputIds + { + IN_PATTERN_TRIG, + IN_VOCT_OFF, + IN_PROG_CHANGE, + nINPUTS = IN_PROG_CHANGE + ( nKEYBOARDS ), + }; + + enum OutputIds + { + OUT_TRIG, + OUT_VOCTS = OUT_TRIG + nKEYBOARDS, + nOUTPUTS = OUT_VOCTS + nKEYBOARDS + }; + + enum LightIds + { + LIGHT_COPY_NEXT, + LIGHT_STEP_NUM, + LIGHT_PAT = LIGHT_STEP_NUM + ( nPATTERNS ), + LIGHT_TRIG = LIGHT_PAT + nPATTERNS, + LIGHT_OCT = LIGHT_TRIG + nKEYBOARDS, + LIGHT_PHRASE = LIGHT_OCT + ( nKEYBOARDS * nOCTAVESEL ), + LIGHT_PHRASE_USED = LIGHT_PHRASE + ( nKEYBOARDS * nPHRASE_SAVES ), + LIGHT_PAUSE = LIGHT_PHRASE_USED + ( nKEYBOARDS * nPHRASE_SAVES ), + nLIGHTS = LIGHT_PAUSE + nKEYBOARDS + }; + + CLog lg; + bool m_bJsonInit = false; + + bool m_bChangeAllPatternOctaves = true; + bool m_bChangeAllPatternNotes = false; + + // octaves + float m_fCvStartOut[ nKEYBOARDS ] = {}; + float m_fCvEndOut[ nKEYBOARDS ] = {}; + + // patterns + int m_CurrentPattern = 0; + PATTERN_STRUCT m_PatternNotes[ nKEYBOARDS ][ nPHRASE_SAVES ][ nPATTERNS ] = {}; + SchmittTrigger m_SchTrigPatternSelect[ nPATTERNS ] = {}; + SchmittTrigger m_SchTrigPatternSelectInput; + + // phrase save + int m_CurrentPhrase[ nKEYBOARDS ] = {}; + PHRASE_CHANGE_STRUCT m_PhrasePending[ nKEYBOARDS ] = {}; + SchmittTrigger m_SchTrigPhraseSelect[ nKEYBOARDS ] = {}; + int m_PhrasesUsed[ nKEYBOARDS ] = {0}; + + // number of steps + int m_nSteps = nPATTERNS; + SchmittTrigger m_SchTrigStepNumbers[ nPATTERNS ]; + + // pause button + bool m_bPause = false; + + // triggers + bool m_bTrig[ nKEYBOARDS ] = {}; + PulseGenerator m_gatePulse[ nKEYBOARDS ]; + + // glide + float m_fglideInc[ nKEYBOARDS ] = {}; + int m_glideCount[ nKEYBOARDS ] = {}; + float m_fglide[ nKEYBOARDS ] = {}; + float m_fLastNotePlayed[ nKEYBOARDS ]; + bool m_bWasLastNotePlayed[ nKEYBOARDS ] = {}; + + // copy next button + bool m_bCopy = false; + + Keyboard_3Oct_Widget *pKeyboardWidget[ nKEYBOARDS ]; + float m_fKeyNotes[ 37 ]; + + float m_VoctOffsetIn = 0.0; + + // Contructor + Seq_Triad() : Module(nPARAMS, nINPUTS, nOUTPUTS, nLIGHTS) + { + int i; + + for( i = 0; i < 37; i++ ) + m_fKeyNotes[ i ] = (float)i * SEMI; + + } + + // Overrides + void step() override; + json_t* toJson() override; + void fromJson(json_t *rootJ) override; + void randomize() override; + void reset() override; + + void SetPhraseSteps( int kb, int nSteps ); + void SetSteps( int steps ); + void SetKey( int kb ); + void SetOut( int kb ); + void ChangePattern( int index, bool bForce ); + void ChangePhrase( int kb, int index, bool bForce ); + void CopyNext( void ); + + //----------------------------------------------------- + // MySquareButton_Phrase + //----------------------------------------------------- + struct MySquareButton_Used : MySquareButtonSmall + { + Seq_Triad *mymodule; + int i, kb, param; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + param = paramId - Seq_Triad::PARAM_PHRASE_USED; + kb = param / nPHRASE_SAVES; + i = param - (kb * nPHRASE_SAVES); + + mymodule->SetPhraseSteps( kb, i ); + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MySquareButton_Phrase + //----------------------------------------------------- + struct MySquareButton_Phrase : MySquareButtonSmall + { + Seq_Triad *mymodule; + int i, kb, param; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + param = paramId - Seq_Triad::PARAM_PHRASE_SAVES; + kb = param / nPHRASE_SAVES; + i = param - (kb * nPHRASE_SAVES); + + // change immediately if clock paused or clock not used + if( mymodule->m_bPause || !mymodule->inputs[ IN_PATTERN_TRIG ].active ) + mymodule->ChangePhrase( kb, i, false ); + else + { + mymodule->lights[ LIGHT_PHRASE + (kb * nPHRASE_SAVES) + i ].value = 0.4f; + mymodule->m_PhrasePending[ kb ].bPending = true; + mymodule->m_PhrasePending[ kb ].phrase = i; + } + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MySquareButton_Trig + //----------------------------------------------------- + struct MySquareButton_Trig : MySquareButton + { + int kb; + + Seq_Triad *mymodule; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + kb = paramId - Seq_Triad::PARAM_TRIGOFF; + + mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ mymodule->m_CurrentPattern ].bTrigOff = !mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ mymodule->m_CurrentPattern ].bTrigOff; + mymodule->lights[ LIGHT_TRIG + kb ].value = mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ mymodule->m_CurrentPattern ].bTrigOff ? 1.0f : 0.0f; + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MySquareButton_CopyNext + //----------------------------------------------------- + struct MySquareButton_Cpy : MySquareButton + { + Seq_Triad *mymodule; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + mymodule->CopyNext(); + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MySquareButton_Step + //----------------------------------------------------- + struct MySquareButton_Pause : MySquareButton + { + Seq_Triad *mymodule; + int stp, i; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + mymodule->m_bPause = !mymodule->m_bPause; + mymodule->lights[ LIGHT_PAUSE ].value = mymodule->m_bPause ? 1.0 : 0.0; + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MySquareButton_Step + //----------------------------------------------------- + struct MySquareButton_Stp : MySquareButton + { + Seq_Triad *mymodule; + int stp, i; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + stp = paramId - Seq_Triad::PARAM_STEP_NUM; + mymodule->SetSteps( stp + 1 ); + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MySquareButton_Pat + //----------------------------------------------------- + struct MySquareButton_Pat : MySquareButton + { + Seq_Triad *mymodule; + int stp, i; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + stp = paramId - Seq_Triad::PARAM_PATTERNS; + mymodule->ChangePattern( stp, false ); + } + + MomentarySwitch::onChange( e ); + } + }; + + //----------------------------------------------------- + // MyOCTButton + //----------------------------------------------------- + struct MyOCTButton : MySquareButton + { + int kb, oct, i; + Seq_Triad *mymodule; + int param; + + void onChange( EventChange &e ) override + { + mymodule = (Seq_Triad*)module; + + if( mymodule && value == 1.0 ) + { + param = paramId - Seq_Triad::PARAM_OCTAVES; + kb = param / nOCTAVESEL; + oct = param - (kb * nOCTAVESEL); + + mymodule->lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + 0 ].value = 0.0; + mymodule->lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + 1 ].value = 0.0; + mymodule->lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + 2 ].value = 0.0; + mymodule->lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + 3 ].value = 0.0; + mymodule->lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + oct ].value = 1.0; + + if( mymodule->m_bChangeAllPatternOctaves ) + { + for( i = 0; i < nPATTERNS; i++ ) + mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ i ].oct = oct; + } + else + { + mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ mymodule->m_CurrentPattern ].oct = oct; + } + + mymodule->SetOut( kb ); + } + + MomentarySwitch::onChange( e ); + } + }; +}; + +//----------------------------------------------------- +// Procedure: Widget +// +//----------------------------------------------------- +void NoteChangeCallback ( void *pClass, int kb, int notepressed, int *pnotes, bool bOn ) +{ + int i; + Seq_Triad *mymodule = (Seq_Triad *)pClass; + + if( !pClass ) + return; + + //mymodule->lg.f( "kb = %d, note = %d\n", kb, note ); + + if( mymodule->m_bChangeAllPatternNotes ) + { + for( i = 0; i < nPATTERNS; i++ ) + { + mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ i ].note = notepressed; + } + } + else + { + mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ mymodule->m_CurrentPattern ].note = notepressed; + } + + mymodule->SetOut( kb ); +} + +//----------------------------------------------------- +// Procedure: Widget +// +//----------------------------------------------------- +struct Seq_Triad_Widget : ModuleWidget { + Seq_Triad_Widget(Seq_Triad *module) : ModuleWidget(module) +{ + int kb, oct, x, y, pat, stp; + + setPanel(SVG::load(assetPlugin(plugin, "res/TriadSequencer.svg"))); + + //module->lg.Open("StickyKeysLog.txt"); + + //---------------------------------------------------- + // Step Select buttons + + y = 10; + x = 45; + + for ( stp = 0; stp < nPATTERNS; stp++ ) + { + // step button + addParam(ParamWidget::create( Vec( x, y ), module, Seq_Triad::PARAM_STEP_NUM + stp, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 1, y + 2 ), module, Seq_Triad::LIGHT_STEP_NUM + stp ) ); + + x += 15; + module->lights[ Seq_Triad::LIGHT_STEP_NUM + stp ].value = 1.0f; + } + + // pause button + addParam(ParamWidget::create(Vec( 45, 48 ), module, Seq_Triad::PARAM_PAUSE, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( 45 + 1, 48 + 2 ), module, Seq_Triad::LIGHT_PAUSE ) ); + + //---------------------------------------------------- + // Pattern Select buttons + y = 32; + x = 45; + + addChild(Port::create( Vec( x - 30, y - 3 ), Port::INPUT, module, Seq_Triad::IN_PATTERN_TRIG ) ); + + for ( pat = 0; pat < nPATTERNS; pat++ ) + { + // pattern button + addParam(ParamWidget::create( Vec( x, y ), module, Seq_Triad::PARAM_PATTERNS + pat, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 1, y + 2 ), module, Seq_Triad::LIGHT_PAT + pat ) ); + + x += 15; + } + + // copy next button + //addParam(ParamWidget::create(Vec( 290, 32 ), module, Seq_Triad::PARAM_COPY_NEXT, 0.0, 1.0, 0.0 ) ); + //addChild(createLight>( Vec( 290 + 1, 32 + 2 ), module, Seq_Triad::LIGHT_COPY_NEXT ) ); + + + //---------------------------------------------------- + // Keyboard Keys + y = CHANNEL_Y; + + for( kb = 0; kb < nKEYBOARDS; kb++ ) + { + x = 45; + + // trig button + addParam(ParamWidget::create( Vec( x, y - 15 ), module, Seq_Triad::PARAM_TRIGOFF + kb, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 1, y - 13 ), module, Seq_Triad::LIGHT_TRIG + kb ) ); + + // glide knob + addParam(ParamWidget::create( Vec( 120, y - 17 ), module, Seq_Triad::PARAM_GLIDE + kb, 0.0, 1.0, 0.0 ) ); + + x = 230; + + for( oct = 0; oct < nOCTAVESEL; oct++ ) + { + addParam(ParamWidget::create( Vec( x, y - 17), module, Seq_Triad::PARAM_OCTAVES + ( kb * nOCTAVESEL) + oct, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 1, y -15 ), module, Seq_Triad::LIGHT_OCT + (kb * nOCTAVESEL ) + oct ) ); + x += 18; + } + + x = CHANNEL_X; + + // keyboard widget + module->pKeyboardWidget[ kb ] = new Keyboard_3Oct_Widget( x, y - 1, 1, kb, DWRGB( 255, 128, 64 ), module, NoteChangeCallback, &module->lg ); + addChild( module->pKeyboardWidget[ kb ] ); + + x = 55; + + for( pat = 0; pat < nPHRASE_SAVES; pat++ ) + { + addParam(ParamWidget::create( Vec( x, y + 69), module, Seq_Triad::PARAM_PHRASE_SAVES + ( kb * nPHRASE_SAVES) + pat, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 69 + 2 ), module, Seq_Triad::LIGHT_PHRASE + ( kb * nPHRASE_SAVES ) + pat ) ); + x += 12; + } + + x = 118; + + for( pat = 0; pat < nPHRASE_SAVES; pat++ ) + { + addParam(ParamWidget::create( Vec( x, y + 69), module, Seq_Triad::PARAM_PHRASE_USED + ( kb * nPHRASE_SAVES) + pat, 0.0, 1.0, 0.0 ) ); + addChild(ModuleLightWidget::create>( Vec( x + 2, y + 69 + 2 ), module, Seq_Triad::LIGHT_PHRASE_USED + ( kb * nPHRASE_SAVES ) + pat ) ); + x += 12; + } + + y += CHANNEL_OFF_Y; + } + + 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))); + + // prog change input triggers + x = 200; + y = 360; + addChild(Port::create( Vec( x, y ), Port::INPUT, module, Seq_Triad::IN_PROG_CHANGE ) ); x += 32; + addChild(Port::create( Vec( x, y ), Port::INPUT, module, Seq_Triad::IN_PROG_CHANGE + 1 ) ); x += 32; + addChild(Port::create( Vec( x, y ), Port::INPUT, module, Seq_Triad::IN_PROG_CHANGE + 2 ) ); + + // VOCT offset input + addChild(Port::create( Vec( 40, 360 ), Port::INPUT, module, Seq_Triad::IN_VOCT_OFF ) ); + + // outputs + x = 307; + y = CHANNEL_Y; + addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, Seq_Triad::OUT_VOCTS ) ); + addChild(Port::create( Vec( x, y + 50 ), Port::OUTPUT, module, Seq_Triad::OUT_TRIG ) ); + y += CHANNEL_OFF_Y; + addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, Seq_Triad::OUT_VOCTS + 1 ) ); + addChild(Port::create( Vec( x, y + 50 ), Port::OUTPUT, module, Seq_Triad::OUT_TRIG + 1 ) ); + y += CHANNEL_OFF_Y; + addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, Seq_Triad::OUT_VOCTS + 2 ) ); + addChild(Port::create( Vec( x, y + 50 ), Port::OUTPUT, module, Seq_Triad::OUT_TRIG + 2 ) ); + + module->SetPhraseSteps( 0, 3 ); + module->SetPhraseSteps( 1, 3 ); + module->SetPhraseSteps( 2, 3 ); + module->ChangePattern( 0, true ); + module->ChangePhrase( 0, 0, true ); + module->ChangePhrase( 1, 0, true ); + module->ChangePhrase( 2, 0, true ); +} +}; + +Model *modelSeq_Triad_Widget = Model::create( "mscHack", "TriadSeq", "SEQ Triad OBSOLETE!", SEQUENCER_TAG, MULTIPLE_TAG ); + + +//----------------------------------------------------- +// Procedure: reset +// +//----------------------------------------------------- +void Seq_Triad::reset() +{ + for( int kb = 0; kb < nKEYBOARDS; kb++ ) + { + for( int oct = 0; oct < nOCTAVESEL; oct++ ) + { + lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + oct ].value = 0.0f; + } + } + + memset( m_fCvStartOut, 0, sizeof(m_fCvStartOut) ); + memset( m_fCvEndOut, 0, sizeof(m_fCvEndOut) ); + memset( m_PatternNotes, 0, sizeof(m_PatternNotes) ); + + SetPhraseSteps( 0, 3 ); + SetPhraseSteps( 1, 3 ); + SetPhraseSteps( 2, 3 ); + ChangePattern( 0, true ); + ChangePhrase( 0, 0, true ); + ChangePhrase( 1, 0, true ); + ChangePhrase( 2, 0, true ); +} + +//----------------------------------------------------- +// Procedure: randomize +// +//----------------------------------------------------- +const int keyscalenotes[ 7 ] = { 0, 2, 4, 5, 7, 9, 11}; +const int keyscalenotes_minor[ 7 ] = { 0, 2, 3, 5, 7, 9, 11}; +void Seq_Triad::randomize() +{ + int kb, pat, phrase, basekey, note, oct; + + for( kb = 0; kb < nKEYBOARDS; kb++ ) + { + for( oct = 0; oct < nOCTAVESEL; oct++ ) + { + lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + oct ].value = 0.0f; + } + } + + memset( m_fCvStartOut, 0, sizeof(m_fCvStartOut) ); + memset( m_fCvEndOut, 0, sizeof(m_fCvEndOut) ); + memset( m_PatternNotes, 0, sizeof(m_PatternNotes) ); + + basekey = (int)(randomUniform() * 24.4); + oct = (int)( randomUniform() * 3.4 ); + + for( kb = 0; kb < nKEYBOARDS; kb++ ) + { + for( pat = 0; pat < nPATTERNS; pat++ ) + { + for( phrase = 0; phrase < nPHRASE_SAVES; phrase++ ) + { + if( randomUniform() > 0.7 ) + note = keyscalenotes_minor[ (int)(randomUniform() * 7.4 ) ]; + else + note = keyscalenotes[ (int)(randomUniform() * 7.4 ) ]; + + m_PatternNotes[ kb ][ phrase ][ pat ].bTrigOff = ( randomUniform() < 0.10 ); + m_PatternNotes[ kb ][ phrase ][ pat ].note = basekey + note; + m_PatternNotes[ kb ][ phrase ][ pat ].oct = oct; + } + } + } + + ChangePattern( 0, true ); +} + +//----------------------------------------------------- +// Procedure: CopyNext +// +//----------------------------------------------------- +void Seq_Triad::CopyNext( void ) +{ + if( m_CurrentPattern < ( nPATTERNS - 1 ) ) + { + memcpy( &m_PatternNotes[ 0 ][ m_CurrentPattern + 1 ], &m_PatternNotes[ 0 ][ m_CurrentPattern ], sizeof(PATTERN_STRUCT ) ); + memcpy( &m_PatternNotes[ 1 ][ m_CurrentPattern + 1 ], &m_PatternNotes[ 1 ][ m_CurrentPattern ], sizeof(PATTERN_STRUCT ) ); + memcpy( &m_PatternNotes[ 2 ][ m_CurrentPattern + 1 ], &m_PatternNotes[ 2 ][ m_CurrentPattern ], sizeof(PATTERN_STRUCT ) ); + + ChangePattern( m_CurrentPattern + 1, true ); + + //m_fLightCopyNext = 1.0; + m_bCopy = true; + } +} + +//----------------------------------------------------- +// Procedure: SetPhraseSteps +// +//----------------------------------------------------- +void Seq_Triad::SetPhraseSteps( int kb, int nSteps ) +{ + int i; + + if( nSteps < 0 || nSteps >= nPHRASE_SAVES ) + nSteps = 0; + + m_PhrasesUsed[ kb ] = nSteps; + + for( i = 0; i < nPHRASE_SAVES; i++ ) + { + if( i < (nSteps+1) ) + lights[ LIGHT_PHRASE_USED + ( kb * nPHRASE_SAVES ) + i ].value= 1.0f; + else + lights[ LIGHT_PHRASE_USED + ( kb * nPHRASE_SAVES ) + i ].value = 0.0f; + } +} + +//----------------------------------------------------- +// Procedure: SetSteps +// +//----------------------------------------------------- +void Seq_Triad::SetSteps( int nSteps ) +{ + int i; + + if( nSteps < 1 || nSteps > nPATTERNS ) + nSteps = nPATTERNS; + + m_nSteps = nSteps; + + for ( i = 0; i < nPATTERNS; i++ ) + { + // level button + if( i < nSteps ) + lights[ LIGHT_STEP_NUM + i ].value = 1.0f; + else + lights[ LIGHT_STEP_NUM + i ].value = 0.0f; + } +} + +//----------------------------------------------------- +// Procedure: SetOut +// +//----------------------------------------------------- +void Seq_Triad::SetOut( int kb ) +{ + int note; + float foct; + + // end glide note (current pattern note) + foct = (float)m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].oct; + note = m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].note; + m_fCvEndOut[ kb ] = foct + m_fKeyNotes[ note ] + m_VoctOffsetIn; + + // start glide note (last pattern note) + if( m_bWasLastNotePlayed[ kb ] ) + { + m_fCvStartOut[ kb ] = m_fLastNotePlayed[ kb ] + m_VoctOffsetIn; + } + else + { + m_bWasLastNotePlayed[ kb ] = true; + m_fCvStartOut[ kb ] = m_fCvEndOut[ kb ] + m_VoctOffsetIn; + } + + m_fLastNotePlayed[ kb ] = m_fCvEndOut[ kb ] + m_VoctOffsetIn; + + // glide time ( max glide = 0.5 seconds ) + m_glideCount[ kb ] = 1 + (int)( ( params[ PARAM_GLIDE + kb ].value * 0.5 ) * engineGetSampleRate()); + m_fglideInc[ kb ] = 1.0 / (float)m_glideCount[ kb ]; + m_fglide[ kb ] = 1.0; + + if( !m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].bTrigOff ) + m_bTrig[ kb ] = true; +} + +//----------------------------------------------------- +// Procedure: SetKey +// +//----------------------------------------------------- +void Seq_Triad::SetKey( int kb ) +{ + pKeyboardWidget[ kb ]->setkey( &m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].note ); +} + +//----------------------------------------------------- +// Procedure: ChangePhrase +// +//----------------------------------------------------- +void Seq_Triad::ChangePhrase( int kb, int index, bool bForce ) +{ + int i; + + if( !bForce && index == m_CurrentPhrase[ kb ] ) + return; + + if( index < 0 ) + index = m_PhrasesUsed[ kb ]; + else if( index > m_PhrasesUsed[ kb ] ) + index = 0; + + m_CurrentPhrase[ kb ] = index; + + // change phrase save light + for( i = 0; i < nPHRASE_SAVES; i++ ) + lights[ LIGHT_PHRASE + ( kb * nPHRASE_SAVES ) + i ].value = 0.0f; + + lights[ LIGHT_PHRASE + ( kb * nPHRASE_SAVES ) + index ].value = 1.0f; + + // set keyboard key + SetKey( kb ); + + lights[ LIGHT_TRIG + kb ].value = m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].bTrigOff ? 1.0 : 0.0; + + // change octave light + for( i = 0; i < nOCTAVESEL; i++ ) + lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + i ].value = 0.0f; + + lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].oct ].value = 1.0f; + + // set outputted note + SetOut( kb ); +} + +//----------------------------------------------------- +// Procedure: ChangePattern +// +//----------------------------------------------------- +void Seq_Triad::ChangePattern( int index, bool bForce ) +{ + int kb, i; + + if( !bForce && index == m_CurrentPattern ) + return; + + if( index < 0 ) + index = nPATTERNS - 1; + else if( index >= nPATTERNS ) + index = 0; + + // update octave offset immediately when not running + m_VoctOffsetIn = inputs[ IN_VOCT_OFF ].normalize( 0.0 ); + + m_CurrentPattern = index; + + // change pattern light + for( i = 0; i < nPATTERNS; i++ ) + lights[ LIGHT_PAT + i ].value = 0.0f; + + lights[ LIGHT_PAT + index ].value = 1.0; + + // change key select + for( kb = 0; kb < nKEYBOARDS; kb ++ ) + { + for( i = 0; i < nOCTAVESEL; i++ ) + lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + i ].value = 0.0f; + + // set keyboard key + SetKey( kb ); + + lights[ LIGHT_TRIG + kb ].value = m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].bTrigOff ? 1.0 : 0.0; + + // change octave light + lights[ LIGHT_OCT + ( kb * nOCTAVESEL ) + m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern ].oct ].value = 1.0f; + + // set outputted note + SetOut( kb ); + } +} + +//----------------------------------------------------- +// Procedure: +// +//----------------------------------------------------- +json_t *Seq_Triad::toJson() +{ + int *pint; + json_t *gatesJ; + json_t *rootJ = json_object(); + + + json_object_set_new(rootJ, "m_bPause", json_boolean (m_bPause)); + + json_object_set_new(rootJ, "m_bChangeAllPatternOctaves", json_boolean (m_bChangeAllPatternOctaves)); + + json_object_set_new(rootJ, "m_bChangeAllPatternNotes", json_boolean (m_bChangeAllPatternNotes)); + + // number of steps + gatesJ = json_integer( m_nSteps ); + json_object_set_new(rootJ, "numberofsteps", gatesJ ); + + // phrase select + pint = (int*)&m_CurrentPhrase[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_CurrentPhrase", gatesJ ); + + // patterns + pint = (int*)&m_PatternNotes[ 0 ][ 0 ][ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < (nPHRASE_SAVES * nPATTERNS * nKEYBOARDS * 8); i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_PatternNotes", gatesJ ); + + + // phrase used + pint = (int*)&m_PhrasesUsed[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_PhrasesUsed", gatesJ ); + + return rootJ; +} + +//----------------------------------------------------- +// Procedure: fromJson +// +//----------------------------------------------------- +void Seq_Triad::fromJson(json_t *rootJ) +{ + int *pint; + int i; + json_t *StepsJ; + + json_t *revJ = json_object_get(rootJ, "m_bPause"); + + if (revJ) + m_bPause = json_is_true( revJ ); + + revJ = json_object_get(rootJ, "m_bChangeAllPatternOctaves"); + + if (revJ) + m_bChangeAllPatternOctaves = json_is_true( revJ ); + + revJ = json_object_get(rootJ, "m_bChangeAllPatternNotes"); + + if (revJ) + m_bChangeAllPatternNotes = json_is_true( revJ ); + + // phrase select + pint = (int*)&m_CurrentPhrase[ 0 ]; + StepsJ = json_object_get( rootJ, "m_CurrentPhrase" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // number of steps + StepsJ = json_object_get(rootJ, "numberofsteps"); + if (StepsJ) + m_nSteps = json_integer_value( StepsJ ); + + // all patterns and phrases + pint = (int*)&m_PatternNotes[ 0 ][ 0 ][ 0 ]; + + StepsJ = json_object_get( rootJ, "m_PatternNotes" ); + + if (StepsJ) + { + for ( i = 0; i < (nPHRASE_SAVES * nPATTERNS * nKEYBOARDS * 8); i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // phrase steps + pint = (int*)&m_PhrasesUsed[ 0 ]; + + StepsJ = json_object_get( rootJ, "m_PhrasesUsed" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + lights[ LIGHT_PAUSE ].value = m_bPause ? 1.0 : 0.0; + + SetSteps( m_nSteps ); + SetPhraseSteps( 0, m_PhrasesUsed[ 0 ] ); + SetPhraseSteps( 1, m_PhrasesUsed[ 1 ] ); + SetPhraseSteps( 2, m_PhrasesUsed[ 2 ] ); + ChangePhrase( 0, 0, true ); + ChangePhrase( 1, 0, true ); + ChangePhrase( 2, 0, true ); + ChangePattern( 0, true ); + + m_bJsonInit = true; +} + +//----------------------------------------------------- +// Procedure: step +// +//----------------------------------------------------- +#define LIGHT_LAMBDA ( 0.065f ) +void Seq_Triad::step() +{ + int i, phrase; + + if( m_bCopy ) + { + lights[ LIGHT_COPY_NEXT ].value -= lights[ LIGHT_COPY_NEXT ].value / LIGHT_LAMBDA / engineGetSampleRate(); + + if( lights[ LIGHT_COPY_NEXT ].value < 0.001 ) + { + m_bCopy = false; + lights[ LIGHT_COPY_NEXT ].value = 0.0; + } + } + + // process triggered phrase changes + for( i = 0; i < nKEYBOARDS; i++ ) + { + if( inputs[ IN_PROG_CHANGE + i ].active && !m_bPause && inputs[ IN_PATTERN_TRIG ].active ) + { + if( m_SchTrigPhraseSelect[ i ].process( inputs[ IN_PROG_CHANGE + i ].value ) ) + { + phrase = ( m_CurrentPhrase[ i ] + 1 ) & 0x3; + + lights[ LIGHT_PHRASE + ( i * nPHRASE_SAVES ) + phrase ].value = 0.4f; + + m_PhrasePending[ i ].bPending = true; + m_PhrasePending[ i ].phrase = phrase; + } + } + } + + // pat change trigger - ignore if already pending + if( inputs[ IN_PATTERN_TRIG ].active && !m_bPause ) + { + if( m_CurrentPattern >= (m_nSteps-1 ) ) + { + m_CurrentPattern = (nPATTERNS - 1); + } + + if( m_SchTrigPatternSelectInput.process( inputs[ IN_PATTERN_TRIG ].value ) ) + { + ChangePattern( m_CurrentPattern + 1, true ); + + if( m_CurrentPattern == 0 ) + { + // process pending phrase changes + for( i = 0; i < nKEYBOARDS; i++ ) + { + if( m_PhrasePending[ i ].bPending ) + { + m_PhrasePending[ i ].bPending = false; + ChangePhrase( i, m_PhrasePending[ i ].phrase, false ); + } + } + } + } + } + else + { + // resolve any left over phrase triggers + for( i = 0; i < nKEYBOARDS; i++ ) + { + if( m_PhrasePending[ i ].bPending ) + { + m_PhrasePending[ i ].bPending = false; + ChangePhrase( i, m_PhrasePending[ i ].phrase, false ); + } + } + } + + // process triggers + for( i = 0; i < nKEYBOARDS; i++ ) + { + if( m_bTrig[ i ] ) + { + m_bTrig[ i ] = false; + m_gatePulse[ i ].trigger(1e-3); + } + + outputs[ OUT_TRIG + i ].value = m_gatePulse[ i ].process( 1.0 / engineGetSampleRate() ) ? CV_MAX : 0.0; + + if( --m_glideCount[ i ] > 0 ) + m_fglide[ i ] -= m_fglideInc[ i ]; + else + m_fglide[ i ] = 0.0; + + outputs[ OUT_VOCTS + i ].value = ( m_fCvStartOut[ i ] * m_fglide[ i ] ) + ( m_fCvEndOut[ i ] * ( 1.0 - m_fglide[ i ] ) ); + } +} diff --git a/src/Seq_Triad2.cpp b/src/Seq_Triad2.cpp new file mode 100644 index 0000000..fdadab2 --- /dev/null +++ b/src/Seq_Triad2.cpp @@ -0,0 +1,897 @@ +#include "mscHack.hpp" +#include "mscHack_Controls.hpp" +#include "dsp/digital.hpp" +#include "CLog.h" + +#define nKEYBOARDS 3 +#define nKEYS 37 +#define nOCTAVESEL 4 +#define nPATTERNS 16 +#define nPHRASE_SAVES 8 +#define SEMI ( 1.0f / 12.0f ) + +typedef struct +{ + float fsemi; + +}KEYBOARD_KEY_STRUCT; + +typedef struct +{ + int note; + bool bTrigOff; + int pad[ 6 ]; + +}PATTERN_STRUCT; + +typedef struct +{ + bool bPending; + int phrase; +}PHRASE_CHANGE_STRUCT; + +//----------------------------------------------------- +// Module Definition +// +//----------------------------------------------------- +struct Seq_Triad2 : Module +{ + + enum ParamIds + { + PARAM_PAUSE, + PARAM_OCTAVES = PARAM_PAUSE + ( nKEYBOARDS ), + PARAM_GLIDE = PARAM_OCTAVES + (nOCTAVESEL * nKEYBOARDS), + PARAM_TRIGOFF = PARAM_GLIDE + ( nKEYBOARDS ), + nPARAMS = PARAM_TRIGOFF + ( nKEYBOARDS ) + }; + + enum InputIds + { + IN_PATTERN_TRIG, + IN_VOCT_OFF = IN_PATTERN_TRIG + ( nKEYBOARDS ), + IN_PROG_CHANGE = IN_VOCT_OFF + ( nKEYBOARDS ), + IN_CLOCK_RESET = IN_PROG_CHANGE + ( nKEYBOARDS ), + IN_GLOBAL_PAT_CLK, + nINPUTS + }; + + enum OutputIds + { + OUT_TRIG, + OUT_VOCTS = OUT_TRIG + nKEYBOARDS, + nOUTPUTS = OUT_VOCTS + nKEYBOARDS + }; + + CLog lg; + bool m_bInitialized = false; + + // octaves + int m_Octave[ nKEYBOARDS ] = {}; + float m_fCvStartOut[ nKEYBOARDS ] = {}; + float m_fCvEndOut[ nKEYBOARDS ] = {}; + + // patterns + int m_CurrentPattern[ nKEYBOARDS ] = {}; + PATTERN_STRUCT m_PatternNotes[ nKEYBOARDS ][ nPHRASE_SAVES ][ nPATTERNS ] = {}; + SchmittTrigger m_SchTrigPatternSelectInput[ nKEYBOARDS ]; + PatternSelectStrip *m_pPatternSelect[ nKEYBOARDS ] = {}; + + // phrase save + int m_CurrentPhrase[ nKEYBOARDS ] = {}; + PHRASE_CHANGE_STRUCT m_PhrasePending[ nKEYBOARDS ] = {}; + SchmittTrigger m_SchTrigPhraseSelect[ nKEYBOARDS ] = {}; + int m_PhrasesUsed[ nKEYBOARDS ] = {0}; + PatternSelectStrip *m_pPhraseSelect[ nKEYBOARDS ] = {}; + + // number of steps + int m_nSteps[ nKEYBOARDS ][ nPHRASE_SAVES ] = {}; + + // pause button + bool m_bPause[ nKEYBOARDS ] = {}; + + // triggers + bool m_bTrig[ nKEYBOARDS ] = {}; + PulseGenerator m_gatePulse[ nKEYBOARDS ]; + + // global triggers + SchmittTrigger m_SchTrigGlobalClkReset; + SchmittTrigger m_SchTrigGlobalPatChange; + bool m_GlobalClkResetPending = false; + + // glide + float m_fglideInc[ nKEYBOARDS ] = {}; + int m_glideCount[ nKEYBOARDS ] = {}; + float m_fglide[ nKEYBOARDS ] = {}; + float m_fLastNotePlayed[ nKEYBOARDS ]; + bool m_bWasLastNotePlayed[ nKEYBOARDS ] = {}; + + Keyboard_3Oct_Widget *pKeyboardWidget[ nKEYBOARDS ]; + float m_fKeyNotes[ 37 ]; + + float m_VoctOffsetIn[ nKEYBOARDS ] = {}; + + // buttons + MyLEDButtonStrip *m_pButtonOctaveSelect[ nKEYBOARDS ] = {}; + MyLEDButton *m_pButtonPause[ nKEYBOARDS ] = {}; + MyLEDButton *m_pButtonTrig[ nKEYBOARDS ] = {}; + + // Contructor + Seq_Triad2() : Module( nPARAMS, nINPUTS, nOUTPUTS, 0 ) + { + int i; + + for( i = 0; i < 37; i++ ) + m_fKeyNotes[ i ] = (float)i * SEMI; + + } + + // Overrides + void step() override; + json_t* toJson() override; + void fromJson(json_t *rootJ) override; + void randomize() override; + void reset() override; + + void SetPhraseSteps( int kb, int nSteps ); + void SetSteps( int kb, int steps ); + void SetKey( int kb ); + void SetOut( int kb ); + void ChangePattern( int kb, int index, bool bForce ); + void ChangePhrase( int kb, int index, bool bForce ); + void SetPendingPhrase( int kb, int phrase ); + void CopyNext( void ); +}; + +//----------------------------------------------------- +// Seq_Triad2_OctSelect +//----------------------------------------------------- +void Seq_Triad2_OctSelect( void *pClass, int id, int nbutton, bool bOn ) +{ + Seq_Triad2 *mymodule; + mymodule = (Seq_Triad2*)pClass; + + mymodule->m_Octave[ id ] = nbutton; + mymodule->SetOut( id ); +} + +//----------------------------------------------------- +// Seq_Triad2_Pause +//----------------------------------------------------- +void Seq_Triad2_Pause( void *pClass, int id, bool bOn ) +{ + Seq_Triad2 *mymodule; + mymodule = (Seq_Triad2*)pClass; + mymodule->m_bPause[ id ] = !mymodule->m_bPause[ id ]; +} + +//----------------------------------------------------- +// Seq_Triad2_Trig +//----------------------------------------------------- +void Seq_Triad2_Trig( void *pClass, int id, bool bOn ) +{ + Seq_Triad2 *mymodule; + mymodule = (Seq_Triad2*)pClass; + mymodule->m_PatternNotes[ id ][ mymodule->m_CurrentPhrase[ id ] ][ mymodule->m_CurrentPattern[ id ] ].bTrigOff = !mymodule->m_PatternNotes[ id ][ mymodule->m_CurrentPhrase[ id ] ][ mymodule->m_CurrentPattern[ id ] ].bTrigOff; +} + +//----------------------------------------------------- +// Procedure: Widget +// +//----------------------------------------------------- +void Seq_Triad2_Widget_NoteChangeCallback ( void *pClass, int kb, int notepressed, int *pnotes, bool bOn ) +{ + Seq_Triad2 *mymodule = (Seq_Triad2 *)pClass; + + if( !pClass ) + return; + + mymodule->m_PatternNotes[ kb ][ mymodule->m_CurrentPhrase[ kb ] ][ mymodule->m_CurrentPattern[ kb ] ].note = notepressed; + mymodule->SetOut( kb ); +} + +//----------------------------------------------------- +// Procedure: PatternChangeCallback +// +//----------------------------------------------------- +void Seq_Triad2_Widget_PatternChangeCallback ( void *pClass, int kb, int pat, int max ) +{ + Seq_Triad2 *mymodule = (Seq_Triad2 *)pClass; + + if( !mymodule || !mymodule->m_bInitialized ) + return; + + if( mymodule->m_CurrentPattern[ kb ] != pat ) + mymodule->ChangePattern( kb, pat, false ); + else if( mymodule->m_nSteps[ kb ][ mymodule->m_CurrentPhrase[ kb ] ] != max ) + mymodule->SetSteps( kb, max ); +} + +//----------------------------------------------------- +// Procedure: PhraseChangeCallback +// +//----------------------------------------------------- +void Seq_Triad2_Widget_PhraseChangeCallback ( void *pClass, int kb, int pat, int max ) +{ + Seq_Triad2 *mymodule = (Seq_Triad2 *)pClass; + + if( !mymodule || !mymodule->m_bInitialized ) + return; + + if( mymodule->m_CurrentPhrase[ kb ] != pat ) + { + if( !mymodule->m_bPause[ kb ] && mymodule->inputs[ Seq_Triad2::IN_PATTERN_TRIG + kb ].active ) + mymodule->SetPendingPhrase( kb, pat ); + else + mymodule->ChangePhrase( kb, pat, false ); + + } + else if( mymodule->m_PhrasesUsed[ kb ] != max ) + mymodule->SetPhraseSteps( kb, max ); +} + +//----------------------------------------------------- +// Procedure: Widget +// +//----------------------------------------------------- +struct Seq_Triad2_Widget : ModuleWidget { + Seq_Triad2_Widget(Seq_Triad2 *module) : ModuleWidget(module) +{ + int kb, x, x2, y, y2; + + setPanel(SVG::load(assetPlugin(plugin, "res/TriadSequencer2.svg"))); + + module->lg.Open("TriadSequencer2.txt"); + + //---------------------------------------------------- + // Keyboard Keys + y = 21; + x = 11; + + for( kb = 0; kb < nKEYBOARDS; kb++ ) + { + // pause button + module->m_pButtonPause[ kb ] = new MyLEDButton( x + 60, y + 4, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, kb, module, Seq_Triad2_Pause ); + addChild( module->m_pButtonPause[ kb ] ); + + // trig button + module->m_pButtonTrig[ kb ] = new MyLEDButton( x + 314, y + 5, 11, 11, 8.0, DWRGB( 180, 180, 180 ), DWRGB( 255, 0, 0 ), MyLEDButton::TYPE_SWITCH, kb, module, Seq_Triad2_Trig ); + addChild( module->m_pButtonTrig[ kb ] ); + + // glide knob + addParam(ParamWidget::create( Vec( x + 220, y + 86 ), module, Seq_Triad2::PARAM_GLIDE + kb, 0.0, 1.0, 0.0 ) ); + + x2 = x + 274; + + // octave select + module->m_pButtonOctaveSelect[ kb ] = new MyLEDButtonStrip( x2, y + 90, 11, 11, 3, 8.0, 4, false, DWRGB( 180, 180, 180 ), DWRGB( 0, 255, 255 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, kb, module, Seq_Triad2_OctSelect ); + addChild( module->m_pButtonOctaveSelect[ kb ] ); + + // keyboard widget + module->pKeyboardWidget[ kb ] = new Keyboard_3Oct_Widget( x + 39, y + 19, 1, kb, DWRGB( 255, 128, 64 ), module, Seq_Triad2_Widget_NoteChangeCallback, &module->lg ); + addChild( module->pKeyboardWidget[ kb ] ); + + // pattern selects + module->m_pPatternSelect[ kb ] = new PatternSelectStrip( x + 79, y + 1, 9, 7, DWRGB( 255, 128, 64 ), DWRGB( 128, 64, 0 ), DWRGB( 255, 0, 128 ), DWRGB( 128, 0, 64 ), nPATTERNS, kb, module, Seq_Triad2_Widget_PatternChangeCallback ); + addChild( module->m_pPatternSelect[ kb ] ); + + // phrase selects + module->m_pPhraseSelect[ kb ] = new PatternSelectStrip( x + 79, y + 86, 9, 7, DWRGB( 255, 255, 0 ), DWRGB( 128, 128, 64 ), DWRGB( 0, 255, 255 ), DWRGB( 0, 128, 128 ), nPHRASE_SAVES, kb, module, Seq_Triad2_Widget_PhraseChangeCallback ); + addChild( module->m_pPhraseSelect[ kb ] ); + + x2 = x + 9; + y2 = y + 4; + // prog change trigger + addChild(Port::create( Vec( x2, y2 ), Port::INPUT, module, Seq_Triad2::IN_PATTERN_TRIG + kb ) ); y2 += 40; + + // VOCT offset input + addChild(Port::create( Vec( x2, y2 ), Port::INPUT, module, Seq_Triad2::IN_VOCT_OFF + kb ) ); y2 += 40; + + // prog change trigger + addChild(Port::create( Vec( x2, y2 ), Port::INPUT, module, Seq_Triad2::IN_PROG_CHANGE + kb ) ); + + // outputs + x2 = x + 330; + addChild(Port::create( Vec( x2, y + 27 ), Port::OUTPUT, module, Seq_Triad2::OUT_VOCTS + kb ) ); + addChild(Port::create( Vec( x2, y + 68 ), Port::OUTPUT, module, Seq_Triad2::OUT_TRIG + kb ) ); + + y += 111; + } + + // reset inputs + y2 = 357; + addChild(Port::create( Vec( x + 89, y2 ), Port::INPUT, module, Seq_Triad2::IN_CLOCK_RESET ) ); + addChild(Port::create( Vec( x + 166, y2 ), Port::INPUT, module, Seq_Triad2::IN_GLOBAL_PAT_CLK ) ); + + 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_Triad2_Widget = Model::create( "mscHack", "TriadSeq2", "SEQ Triad 2", SEQUENCER_TAG, MULTIPLE_TAG ); + + +//----------------------------------------------------- +// Procedure: reset +// +//----------------------------------------------------- +void Seq_Triad2::reset() +{ + if( !m_bInitialized ) + return; + + for( int kb = 0; kb < nKEYBOARDS; kb++ ) + m_pButtonOctaveSelect[ kb ]->Set( 0, true ); + + memset( m_fCvStartOut, 0, sizeof(m_fCvStartOut) ); + memset( m_fCvEndOut, 0, sizeof(m_fCvEndOut) ); + memset( m_PatternNotes, 0, sizeof(m_PatternNotes) ); + + for( int kb = 0; kb < nKEYBOARDS; kb++ ) + { + for( int pat = 0; pat < nPHRASE_SAVES; pat++ ) + m_nSteps[ kb ][ pat ] = 3; + + SetPhraseSteps( kb, 3 ); + ChangePattern( kb, 0, true ); + ChangePhrase( kb, 0, true ); + } +} + +//----------------------------------------------------- +// Procedure: randomize +// +//----------------------------------------------------- +const int keyscalenotes[ 7 ] = { 0, 2, 4, 5, 7, 9, 11}; +const int keyscalenotes_minor[ 7 ] = { 0, 2, 3, 5, 7, 9, 11}; +void Seq_Triad2::randomize() +{ + int kb, pat, phrase, basekey, note; + + for( int kb = 0; kb < nKEYBOARDS; kb++ ) + m_pButtonOctaveSelect[ kb ]->Set( 0, true ); + + memset( m_fCvStartOut, 0, sizeof(m_fCvStartOut) ); + memset( m_fCvEndOut, 0, sizeof(m_fCvEndOut) ); + memset( m_PatternNotes, 0, sizeof(m_PatternNotes) ); + + basekey = (int)(randomUniform() * 24.4); + + for( kb = 0; kb < nKEYBOARDS; kb++ ) + { + m_Octave[ kb ] = (int)( randomUniform() * 3.4 ); + + for( pat = 0; pat < nPATTERNS; pat++ ) + { + for( phrase = 0; phrase < nPHRASE_SAVES; phrase++ ) + { + if( randomUniform() > 0.7 ) + note = keyscalenotes_minor[ (int)(randomUniform() * 7.4 ) ]; + else + note = keyscalenotes[ (int)(randomUniform() * 7.4 ) ]; + + m_PatternNotes[ kb ][ phrase ][ pat ].bTrigOff = ( randomUniform() < 0.10 ); + m_PatternNotes[ kb ][ phrase ][ pat ].note = basekey + note; + } + } + + ChangePattern( kb, 0, true ); + } +} + +//----------------------------------------------------- +// Procedure: SetPhraseSteps +// +//----------------------------------------------------- +void Seq_Triad2::SetPhraseSteps( int kb, int nSteps ) +{ + if( nSteps < 0 || nSteps >= nPHRASE_SAVES ) + nSteps = 0; + + m_PhrasesUsed[ kb ] = nSteps; +} + +//----------------------------------------------------- +// Procedure: SetSteps +// +//----------------------------------------------------- +void Seq_Triad2::SetSteps( int kb, int nSteps ) +{ + if( nSteps < 0 || nSteps >= nPATTERNS ) + nSteps = 0; + + m_nSteps[ kb ][ m_CurrentPhrase[ kb ] ] = nSteps; +} + +//----------------------------------------------------- +// Procedure: SetOut +// +//----------------------------------------------------- +void Seq_Triad2::SetOut( int kb ) +{ + int note; + float foct; + + if( kb < 0 || kb >= nKEYBOARDS ) + return; + + // end glide note (current pattern note) + foct = (float)m_Octave[ kb ]; + + note = m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern[ kb ] ].note; + + if( note > 36 || note < 0 ) + note = 0; + + m_fCvEndOut[ kb ] = foct + m_fKeyNotes[ note ] + m_VoctOffsetIn[ kb ]; + + // start glide note (last pattern note) + if( m_bWasLastNotePlayed[ kb ] ) + { + m_fCvStartOut[ kb ] = m_fLastNotePlayed[ kb ] + m_VoctOffsetIn[ kb ]; + } + else + { + m_bWasLastNotePlayed[ kb ] = true; + m_fCvStartOut[ kb ] = m_fCvEndOut[ kb ] + m_VoctOffsetIn[ kb ]; + } + + m_fLastNotePlayed[ kb ] = m_fCvEndOut[ kb ] + m_VoctOffsetIn[ kb ]; + + // glide time ( max glide = 0.5 seconds ) + m_glideCount[ kb ] = 1 + (int)( ( params[ PARAM_GLIDE + kb ].value * 0.5 ) * engineGetSampleRate()); + + m_fglideInc[ kb ] = 1.0 / (float)m_glideCount[ kb ]; + + m_fglide[ kb ] = 1.0; + + if( !m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern[ kb ] ].bTrigOff ) + m_bTrig[ kb ] = true; +} + +//----------------------------------------------------- +// Procedure: SetKey +// +//----------------------------------------------------- +void Seq_Triad2::SetKey( int kb ) +{ + pKeyboardWidget[ kb ]->setkey( &m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern[ kb ] ].note ); +} + +//----------------------------------------------------- +// Procedure: SetPendingPhrase +// +//----------------------------------------------------- +void Seq_Triad2::SetPendingPhrase( int kb, int phraseIn ) +{ + int phrase; + + if( phraseIn < 0 || phraseIn >= nPHRASE_SAVES ) + phrase = ( m_CurrentPhrase[ kb ] + 1 ) & 0x7; + else + phrase = phraseIn; + + if( phrase > m_PhrasesUsed[ kb ] ) + phrase = 0; + + m_PhrasePending[ kb ].bPending = true; + m_PhrasePending[ kb ].phrase = phrase; + m_pPhraseSelect[ kb ]->SetPat( m_CurrentPhrase[ kb ], false ); + m_pPhraseSelect[ kb ]->SetPat( phrase, true ); +} + +//----------------------------------------------------- +// Procedure: ChangePhrase +// +//----------------------------------------------------- +void Seq_Triad2::ChangePhrase( int kb, int index, bool bForce ) +{ + if( kb < 0 || kb >= nKEYBOARDS ) + return; + + if( !bForce && index == m_CurrentPhrase[ kb ] ) + return; + + if( index < 0 ) + index = nPHRASE_SAVES - 1; + else if( index >= nPHRASE_SAVES ) + index = 0; + + m_CurrentPhrase[ kb ] = index; + + m_pPhraseSelect[ kb ]->SetPat( index, false ); + m_pPhraseSelect[ kb ]->SetMax( m_PhrasesUsed[ kb ] ); + m_pPatternSelect[ kb ]->SetMax( m_nSteps[ kb ][ index ] ); + + // set keyboard key + SetKey( kb ); + + m_pButtonTrig[ kb ]->Set( m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern[ kb ] ].bTrigOff ); + + // change octave light + m_pButtonOctaveSelect[ kb ]->Set( m_Octave[ kb ], true ); + + // set output note + SetOut( kb ); +} + +//----------------------------------------------------- +// Procedure: ChangePattern +// +//----------------------------------------------------- +void Seq_Triad2::ChangePattern( int kb, int index, bool bForce ) +{ + if( kb < 0 || kb >= nKEYBOARDS ) + return; + + if( !bForce && index == m_CurrentPattern[ kb ] ) + return; + + if( index < 0 ) + index = nPATTERNS - 1; + else if( index >= nPATTERNS ) + index = 0; + + // update octave offset immediately when not running + m_VoctOffsetIn[ kb ] = inputs[ IN_VOCT_OFF + kb ].normalize( 0.0 ); + + m_CurrentPattern[ kb ] = index; + + m_pPatternSelect[ kb ]->SetPat( index, false ); + + // set keyboard key + SetKey( kb ); + + m_pButtonTrig[ kb ]->Set( m_PatternNotes[ kb ][ m_CurrentPhrase[ kb ] ][ m_CurrentPattern[ kb ] ].bTrigOff ); + + // change octave light + m_pButtonOctaveSelect[ kb ]->Set( m_Octave[ kb ], true ); + + // set outputted note + SetOut( kb ); +} + +//----------------------------------------------------- +// Procedure: +// +//----------------------------------------------------- +json_t *Seq_Triad2::toJson() +{ + int *pint; + bool *pbool; + json_t *gatesJ; + json_t *rootJ = json_object(); + + // pauses + pbool = &m_bPause[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( (int)pbool[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_bPause", gatesJ ); + + // number of steps + pint = (int*)&m_nSteps[ 0 ][ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS * nPHRASE_SAVES; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_nSteps", gatesJ ); + + // octaves + pint = (int*)&m_Octave[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_Octave", gatesJ ); + + // phrase select + pint = (int*)&m_CurrentPhrase[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_CurrentPhrase", gatesJ ); + + // patterns + pint = (int*)&m_PatternNotes[ 0 ][ 0 ][ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < (nPHRASE_SAVES * nPATTERNS * nKEYBOARDS * 8); i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_PatternNotes", gatesJ ); + + + // phrase used + pint = (int*)&m_PhrasesUsed[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_PhrasesUsed", gatesJ ); + + // current pattern + pint = (int*)&m_CurrentPattern[ 0 ]; + + gatesJ = json_array(); + + for (int i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_integer( pint[ i ] ); + json_array_append_new( gatesJ, gateJ ); + } + + json_object_set_new( rootJ, "m_CurrentPattern", gatesJ ); + + return rootJ; +} + +//----------------------------------------------------- +// Procedure: fromJson +// +//----------------------------------------------------- +void Seq_Triad2::fromJson(json_t *rootJ) +{ + bool *pbool; + int *pint; + int i; + json_t *StepsJ; + + // pauses + pbool = &m_bPause[ 0 ]; + StepsJ = json_object_get( rootJ, "m_bPause" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pbool[ i ] = json_integer_value( gateJ ); + } + } + + // number of steps + pint = (int*)&m_nSteps[ 0 ][ 0 ]; + StepsJ = json_object_get( rootJ, "m_nSteps" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS * nPHRASE_SAVES; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // octaves + pint = (int*)&m_Octave[ 0 ]; + StepsJ = json_object_get( rootJ, "m_Octave" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // phrase select + pint = (int*)&m_CurrentPhrase[ 0 ]; + StepsJ = json_object_get( rootJ, "m_CurrentPhrase" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // all patterns and phrases + pint = (int*)&m_PatternNotes[ 0 ][ 0 ][ 0 ]; + + StepsJ = json_object_get( rootJ, "m_PatternNotes" ); + + if (StepsJ) + { + for ( i = 0; i < (nPHRASE_SAVES * nPATTERNS * nKEYBOARDS * 8); i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // phrase steps + pint = (int*)&m_PhrasesUsed[ 0 ]; + + StepsJ = json_object_get( rootJ, "m_PhrasesUsed" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + // current pattern + pint = (int*)&m_CurrentPattern[ 0 ]; + + StepsJ = json_object_get( rootJ, "m_CurrentPattern" ); + + if (StepsJ) + { + for ( i = 0; i < nKEYBOARDS; i++) + { + json_t *gateJ = json_array_get(StepsJ, i); + + if (gateJ) + pint[ i ] = json_integer_value( gateJ ); + } + } + + for( i = 0; i < nKEYBOARDS; i++ ) + { + m_pPhraseSelect[ i ]->SetMax( m_PhrasesUsed[ i ] ); + m_pPhraseSelect[ i ]->SetPat( m_CurrentPhrase[ i ], false ); + + m_pPatternSelect[ i ]->SetMax( m_nSteps[ i ][ m_CurrentPhrase[ i ] ] ); + m_pPatternSelect[ i ]->SetPat( m_CurrentPattern[ i ], false ); + + m_pButtonPause[ i ]->Set( m_bPause[ i ] ); + + SetPhraseSteps( i, m_PhrasesUsed[ i ] ); + ChangePhrase( i, m_CurrentPhrase[ i ], true ); + ChangePattern( i, m_CurrentPattern[ i ], true ); + } +} + +//----------------------------------------------------- +// Procedure: step +// +//----------------------------------------------------- +#define LIGHT_LAMBDA ( 0.065f ) +void Seq_Triad2::step() +{ + int kb; + bool bGlobalPatChange = false, bGlobalClkTriggered = false; + + if( !m_bInitialized ) + return; + + // global phrase change trigger + if( inputs[ IN_GLOBAL_PAT_CLK ].active ) + { + if( m_SchTrigGlobalPatChange.process( inputs[ IN_GLOBAL_PAT_CLK ].value ) ) + bGlobalPatChange = true; + } + + // global clock reset + if( inputs[ IN_CLOCK_RESET ].active ) + { + if( m_SchTrigGlobalClkReset.process( inputs[ IN_CLOCK_RESET ].value ) ) + m_GlobalClkResetPending = true; + } + + // process triggered phrase changes + for( kb = 0; kb < nKEYBOARDS; kb++ ) + { + // phrase change trigger + if( bGlobalPatChange && !m_bPause[ kb ] && inputs[ IN_PATTERN_TRIG + kb ].active ) + { + SetPendingPhrase( kb, -1 ); + } + else if( inputs[ IN_PROG_CHANGE + kb ].active && !m_bPause[ kb ] && inputs[ IN_PATTERN_TRIG + kb ].active ) + { + if( m_SchTrigPhraseSelect[ kb ].process( inputs[ IN_PROG_CHANGE + kb ].value ) ) + SetPendingPhrase( kb, -1 ); + } + + // pat change trigger - ignore if already pending + if( inputs[ IN_PATTERN_TRIG + kb ].active && !m_bPause[ kb ] ) + { + if( m_SchTrigPatternSelectInput[ kb ].process( inputs[ IN_PATTERN_TRIG + kb ].value ) ) + { + if( m_GlobalClkResetPending ) + { + bGlobalClkTriggered = true; + m_CurrentPattern[ kb ] = -1; + } + else if( m_CurrentPattern[ kb ] >= ( m_nSteps[ kb ][ m_CurrentPhrase[ kb ] ] ) ) + { + m_CurrentPattern[ kb ] = (nPATTERNS - 1); + } + + ChangePattern( kb, m_CurrentPattern[ kb ] + 1, true ); + + if( m_CurrentPattern[ kb ] == 0 ) + { + if( m_PhrasePending[ kb ].bPending ) + { + m_PhrasePending[ kb ].bPending = false; + ChangePhrase( kb, m_PhrasePending[ kb ].phrase, false ); + } + } + } + } + else + { + // resolve any left over phrase triggers + if( m_PhrasePending[ kb ].bPending ) + { + m_PhrasePending[ kb ].bPending = false; + ChangePhrase( kb, m_PhrasePending[ kb ].phrase, false ); + } + } + + if( m_bTrig[ kb ] ) + { + m_bTrig[ kb ] = false; + m_gatePulse[ kb ].trigger(1e-3); + } + + outputs[ OUT_TRIG + kb ].value = m_gatePulse[ kb ].process( 1.0 / engineGetSampleRate() ) ? CV_MAX : 0.0; + + if( --m_glideCount[ kb ] > 0 ) + m_fglide[ kb ] -= m_fglideInc[ kb ]; + else + m_fglide[ kb ] = 0.0; + + outputs[ OUT_VOCTS + kb ].value = ( m_fCvStartOut[ kb ] * m_fglide[ kb ] ) + ( m_fCvEndOut[ kb ] * ( 1.0 - m_fglide[ kb ] ) ); + } + + if( bGlobalClkTriggered ) + m_GlobalClkResetPending = false; +} diff --git a/src/SynthDrums.cpp b/src/SynthDrums.cpp new file mode 100644 index 0000000..8ff362f --- /dev/null +++ b/src/SynthDrums.cpp @@ -0,0 +1,650 @@ +#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 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, s, r; + int acount, scount, rcount, fadecount; + float fainc, fsinc, frinc, fadeinc; + float out; + bool bTrig; +}ASR_STRUCT; + +typedef struct +{ + int wavetype; + + + // wave + float phase; + + //filter + float q, f; + bool bHighpass; + + float lp1, bp1; + float hpIn; + float lpIn; + float mpIn; + + // ads + ASR_STRUCT adsr_wave; + ASR_STRUCT adsr_freq; + +}OSC_PARAM_STRUCT; + +//----------------------------------------------------- +// Module Definition +// +//----------------------------------------------------- +struct SynthDrums : Module +{ + enum WaveTypes + { + WAVE_SIN, + WAVE_TRI, + WAVE_SQR, + WAVE_SAW, + WAVE_NOISE, + nWAVEFORMS + }; + + enum ParamIds + { + PARAM_FREQ, + PARAM_ATT = PARAM_FREQ + nCHANNELS, + PARAM_REL = PARAM_ATT + nCHANNELS, + PARAM_REZ = PARAM_REL + nCHANNELS, + PARAM_WAVES = PARAM_REZ + nCHANNELS, + nPARAMS = PARAM_WAVES + (nWAVEFORMS * nCHANNELS) + }; + + enum InputIds + { + IN_LEVEL, + IN_TRIG = IN_LEVEL + nCHANNELS, + IN_FREQ_MOD = IN_TRIG + nCHANNELS, + nINPUTS = IN_FREQ_MOD + nCHANNELS + }; + + enum OutputIds + { + OUTPUT_AUDIO, + nOUTPUTS = OUTPUT_AUDIO + nCHANNELS + }; + + enum ADSRSTATES + { + ADSR_OFF, + ADSR_FADE, + ADSR_ON, + ADSR_ATTACK, + ADSR_SUSTAIN, + ADSR_RELEASE + }; + + CLog lg; + + SchmittTrigger m_SchTrig[ nCHANNELS ]; + + OSC_PARAM_STRUCT m_Wave[ nCHANNELS ] = {}; + + MyLEDButtonStrip *m_pButtonWaveSelect[ nCHANNELS ] = {}; + + // waveforms + float m_BufferWave[ nWAVEFORMS ][ WAVE_BUFFER_LEN ] = {}; + + // Contructor + SynthDrums() : Module( nPARAMS, nINPUTS, nOUTPUTS, 0 ){} + + //----------------------------------------------------- + // MyParamKnob + //----------------------------------------------------- + struct MyParamFreq : Yellow2_Small + { + SynthDrums *mymodule; + int param; + + void onChange( EventChange &e ) override + { + mymodule = (SynthDrums*)module; + + if( mymodule ) + { + param = paramId - SynthDrums::PARAM_FREQ; + + if( mymodule->m_Wave[ param ].wavetype == WAVE_NOISE ) + mymodule->ChangeFilterCutoff( param, value ); + else + mymodule->ChangeFilterCutoff( param, 0.6 ); + } + + RoundKnob::onChange( e ); + } + }; + + // Overrides + void step() override; + json_t* toJson() override; + void fromJson(json_t *rootJ) override; + void randomize() override; + void reset() override; + + void SetWaveLights( void ); + void BuildWaves( void ); + void ChangeFilterCutoff( int ch, float cutfreq ); + float Filter( int ch, float in, bool bHighPass ); + float GetWave( int type, float phase ); + float ProcessADS( int ch, bool bWave ); + float GetAudio( int ch ); +}; + +//----------------------------------------------------- +// SynthDrums_WaveSelect +//----------------------------------------------------- +void SynthDrums_WaveSelect( void *pClass, int id, int nbutton, bool bOn ) +{ + SynthDrums *mymodule; + mymodule = (SynthDrums*)pClass; + mymodule->m_Wave[ id ].wavetype = nbutton; +} + +//----------------------------------------------------- +// Procedure: +// +//----------------------------------------------------- +json_t *SynthDrums::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 SynthDrums::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 ); + } + } + + SetWaveLights(); +} + +//----------------------------------------------------- +// Procedure: Widget +// +//----------------------------------------------------- +struct SynthDrums_Widget : ModuleWidget { + SynthDrums_Widget(SynthDrums *module) : ModuleWidget(module) +{ + int ch, x, y; + + setPanel(SVG::load(assetPlugin(plugin, "res/SynthDrums.svg"))); + + //module->lg.Open("SynthDrums.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; + + // IN_FREQ_MOD + addChild(Port::create( Vec( x + 48, y ), Port::INPUT, module, SynthDrums::IN_FREQ_MOD + ch ) ); + + // inputs + addChild(Port::create( Vec( x, y ), Port::INPUT, module, SynthDrums::IN_LEVEL + ch ) ); + + y += 43; + + addChild(Port::create( Vec( x, y ), Port::INPUT, module, SynthDrums::IN_TRIG + ch ) ); + + x += 32; + y += 7; + + // wave select buttons + module->m_pButtonWaveSelect[ ch ] = new MyLEDButtonStrip( x, y, 11, 11, 5, 8.0, 5, false, DWRGB( 180, 180, 180 ), DWRGB( 255, 255, 0 ), MyLEDButtonStrip::TYPE_EXCLUSIVE, ch, module, SynthDrums_WaveSelect ); + addChild( module->m_pButtonWaveSelect[ ch ] ); + + x = CHANNEL_X + 25; + y -= 34; + + // params + addParam(ParamWidget::create( Vec( x, y ), module, SynthDrums::PARAM_FREQ + ch, 0.0, 1.0, 0.0 ) ); + + x += 34; + + addParam(ParamWidget::create( Vec( x, y ), module, SynthDrums::PARAM_ATT + ch, 0.0, 0.1, 0.0 ) ); + + x += 34; + + addParam(ParamWidget::create( Vec( x, y ), module, SynthDrums::PARAM_REL + ch, 0.0, 1.0, 0.0 ) ); + + y -= 15; + x += 34; + + // outputs + addChild(Port::create( Vec( x, y ), Port::OUTPUT, module, SynthDrums::OUTPUT_AUDIO + ch ) ); + + y += CHANNEL_H; + } + + module->BuildWaves(); + + module->ChangeFilterCutoff( 0, 0.6 ); + module->ChangeFilterCutoff( 1, 0.6 ); + module->ChangeFilterCutoff( 2, 0.6 ); + + module->SetWaveLights(); +} +}; + +Model *modelSynthDrums_Widget = Model::create( "mscHack", "SynthDrums", "SYNTH Drums", DRUM_TAG, MULTIPLE_TAG ); + + +//----------------------------------------------------- +// Procedure: reset +// +//----------------------------------------------------- +void SynthDrums::reset() +{ +} + +//----------------------------------------------------- +// Procedure: randomize +// +//----------------------------------------------------- +void SynthDrums::randomize() +{ + int ch; + + for( ch = 0; ch < nCHANNELS; ch++ ) + { + m_Wave[ ch ].wavetype = (int)( randomUniform() * (nWAVEFORMS-1) ); + } + + SetWaveLights(); +} + +//----------------------------------------------------- +// Procedure: SetWaveLights +// +//----------------------------------------------------- +void SynthDrums::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 SynthDrums::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 SynthDrums::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 SynthDrums::ProcessADS( int ch, bool bWave ) +{ + ASR_STRUCT *pasr; + + if( bWave ) + pasr = &m_Wave[ ch ].adsr_wave; + else + pasr = &m_Wave[ ch ].adsr_freq; + + // rettrig the adsr + if( pasr->bTrig ) + { + pasr->state = ADSR_FADE; + + pasr->fadecount = 900; + pasr->fadeinc = pasr->out / (float)pasr->fadecount; + + pasr->acount = 20; + pasr->fainc = 1.0 / pasr->acount; + + pasr->scount = 0;//(int)(.1 * engineGetSampleRate()); + pasr->fsinc = 0;//1.0 / pasr->scount; + + if( bWave ) + { + // for the wave asr the release is the waveform release + pasr->rcount = (int)( params[ PARAM_REL + ch ].value * ADS_MAX_TIME_SECONDS * engineGetSampleRate() ); + } + else + { + // for the wave asr the release is the hit + pasr->rcount = (int)( params[ PARAM_ATT + ch ].value * ADS_MAX_TIME_SECONDS * engineGetSampleRate() ); + } + + if( pasr->rcount ) + pasr->frinc = 1.0 / pasr->rcount; + + pasr->bTrig = false; + } + + // process + switch( pasr->state ) + { + case ADSR_FADE: + if( --pasr->fadecount <= 0 ) + { + pasr->state = ADSR_ATTACK; + pasr->out = 0.0f; + m_Wave[ ch ].phase = 0.0; + } + else + { + pasr->out -= pasr->fadeinc; + } + + break; + + case ADSR_OFF: + pasr->out = 0.0; + break; + + case ADSR_ATTACK: + if( --pasr->acount <= 0 ) + { + pasr->state = ADSR_SUSTAIN; + } + else + { + pasr->out += pasr->fainc; + } + break; + + case ADSR_SUSTAIN: + pasr->out = 1.0; + if( --pasr->scount <= 0 ) + { + pasr->state = ADSR_RELEASE; + } + + break; + + case ADSR_RELEASE: + + if( --pasr->rcount <= 0 ) + { + pasr->out = 0.0f; + pasr->state = ADSR_OFF; + } + else + { + pasr->out -= pasr->frinc; + } + break; + } + + return clamp( pasr->out, 0.0f, 1.0f ); +} + +//----------------------------------------------------- +// Procedure: ChangeFilterCutoff +// +//----------------------------------------------------- +void SynthDrums::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) +float SynthDrums::Filter( int ch, float in, bool bHighPass ) +{ + OSC_PARAM_STRUCT *p; + float rez, hp1; + float lowpass, highpass; + + p = &m_Wave[ ch ]; + + rez = 1.00;//1.00 - p->q; + + in = in + 0.000000001; + + p->lp1 = p->lp1 + p->f * p->bp1; + hp1 = in - p->lp1 - rez * p->bp1; + p->bp1 = p->f * hp1 + p->bp1; + lowpass = p->lp1; + highpass = hp1; + //bandpass = p->bp1; + + p->lp1 = p->lp1 + p->f * p->bp1; + hp1 = in - p->lp1 - rez * p->bp1; + p->bp1 = p->f * hp1 + p->bp1; + lowpass = lowpass + p->lp1; + highpass = highpass + hp1; + //bandpass = bandpass + p->bp1; + + in = in - 0.000000001; + + p->lp1 = p->lp1 + p->f * p->bp1; + hp1 = in - p->lp1 - rez * p->bp1; + p->bp1 = p->f * hp1 + p->bp1; + + lowpass = (lowpass + p->lp1) * MULTI; + highpass = (highpass + hp1) * MULTI; + //bandpass = (bandpass + p->bp1) * MULTI; + + if( bHighPass ) + return highpass; + + return lowpass; +} + +//----------------------------------------------------- +// Procedure: GetAudio +// +//----------------------------------------------------- +float SynthDrums::GetAudio( int ch ) +{ + float fout = 0, fenv = 0.0, freq, freqmod; + + if( outputs[ OUTPUT_AUDIO + ch ].active ) + { + freqmod = clamp( inputs[ IN_FREQ_MOD + ch ].value / CV_MAX, 0.0f, 1.0f ); + + // process our second envelope for hit + fenv = ProcessADS( ch, false ); + + // if noise then frequency affects the filter cutoff and not the wave frequency + if( m_Wave[ ch ].wavetype == WAVE_NOISE ) + { + freq = clamp( ( ( freqmod + params[ PARAM_FREQ + ch ].value ) + (fenv*2) ), 0.0f, 1.0f ); + + ChangeFilterCutoff( ch, freq ); + } + // other signals the second ADS affects the frequency for the hit + else + { + m_Wave[ ch ].phase += 35 + ( ( freqmod + params[ PARAM_FREQ + ch ].value ) * freqMAX ) + ( fenv * 400.0f ); + + if( m_Wave[ ch ].phase >= engineGetSampleRate() ) + m_Wave[ ch ].phase = m_Wave[ ch ].phase - engineGetSampleRate(); + } + + fout = ProcessADS( ch, true ) * GetWave( m_Wave[ ch ].wavetype, m_Wave[ ch ].phase ); + fout = Filter( ch, fout, ( m_Wave[ ch ].wavetype == WAVE_NOISE ) ); + } + + return fout; +} + +//----------------------------------------------------- +// Procedure: step +// +//----------------------------------------------------- +void SynthDrums::step() +{ + int ch; + + // check for triggers + for( ch = 0; ch < nCHANNELS; ch++ ) + { + if( inputs[ IN_TRIG + ch ].active ) + { + if( m_SchTrig[ ch ].process( inputs[ IN_TRIG + ch ].value ) ) + { + m_Wave[ ch ].adsr_freq.bTrig = true; + m_Wave[ ch ].adsr_wave.bTrig = true; + } + } + } + + // process sounds + outputs[ OUTPUT_AUDIO + 0 ].value = GetAudio( 0 ) * AUDIO_MAX * clamp( (inputs[ IN_LEVEL + 0 ].value / CV_MAX), 0.0, 1.0 ); + outputs[ OUTPUT_AUDIO + 1 ].value = GetAudio( 1 ) * AUDIO_MAX * clamp( (inputs[ IN_LEVEL + 1 ].value / CV_MAX), 0.0, 1.0 ); + outputs[ OUTPUT_AUDIO + 2 ].value = GetAudio( 2 ) * AUDIO_MAX * clamp( (inputs[ IN_LEVEL + 2 ].value / CV_MAX), 0.0, 1.0 ); +} diff --git a/src/XFade.cpp b/src/XFade.cpp new file mode 100644 index 0000000..2eb455a --- /dev/null +++ b/src/XFade.cpp @@ -0,0 +1,149 @@ +#include "mscHack.hpp" +#include "mscHack_Controls.hpp" +#include "dsp/digital.hpp" +#include "CLog.h" + +#define CHANNELS 3 + +//----------------------------------------------------- +// Module Definition +// +//----------------------------------------------------- +struct XFade : Module +{ + enum ParamIds + { + PARAM_MIX, + PARAM_LEVEL, + nPARAMS + }; + + enum InputIds + { + IN_MIXCV, + IN_AL, + IN_AR = IN_AL + CHANNELS, + IN_BL = IN_AR + CHANNELS, + IN_BR = IN_BL + CHANNELS, + nINPUTS = IN_BR + CHANNELS + }; + + enum OutputIds + { + OUT_L, + OUT_R = OUT_L + CHANNELS, + nOUTPUTS = OUT_R + CHANNELS + }; + + CLog lg; + + // Contructor + XFade() : Module(nPARAMS, nINPUTS, nOUTPUTS){} + + // Overrides + void step() override; + //json_t* toJson() override; + //void fromJson(json_t *rootJ) override; + void randomize() override; + void reset() override; +}; + +//----------------------------------------------------- +// Procedure: Widget +// +//----------------------------------------------------- +struct XFade_Widget : ModuleWidget { + XFade_Widget(XFade *module) : ModuleWidget(module) +{ + int i, x, y; + + + setPanel(SVG::load(assetPlugin(plugin, "res/XFade.svg"))); + + //module->lg.Open("XFade.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))); + + x = 10; + y = 47; + + for( i = 0; i < CHANNELS; i++ ) + { + // audio A + addChild(Port::create( Vec( x, y ), Port::INPUT, module, XFade::IN_AL + i ) ); + addChild(Port::create( Vec( x + 28, y ), Port::INPUT, module, XFade::IN_AR + i ) ); + + // audio B + addChild(Port::create( Vec( x, y + 32 ), Port::INPUT, module, XFade::IN_BL + i ) ); + addChild(Port::create( Vec( x + 28, y + 32 ), Port::INPUT, module, XFade::IN_BR + i ) ); + + // audio outputs + addChild(Port::create( Vec( x + 83, y ), Port::OUTPUT, module, XFade::OUT_L + i ) ); + addChild(Port::create( Vec( x + 83, y + 32 ), Port::OUTPUT, module, XFade::OUT_R + i ) ); + + y += 67; + } + + // mix CV + addChild(Port::create( Vec( 4, 263 ), Port::INPUT, module, XFade::IN_MIXCV) ); + + // mix knobs + addParam(ParamWidget::create( Vec( 30, 243 ), module, XFade::PARAM_MIX, -1.0, 1.0, 0.0 ) ); + addParam(ParamWidget::create( Vec( 30, 313 ), module, XFade::PARAM_LEVEL, 0.0, 2.0, 1.0 ) ); +} +}; + +Model *modelXFade_Widget = Model::create( "mscHack", "XFade", "MIXER Cross Fader 3 Channel", MIXER_TAG, MULTIPLE_TAG); + + + +//----------------------------------------------------- +// Procedure: reset +// +//----------------------------------------------------- +void XFade::reset() +{ +} + +//----------------------------------------------------- +// Procedure: randomize +// +//----------------------------------------------------- +void XFade::randomize() +{ +} + +//----------------------------------------------------- +// Procedure: step +// +//----------------------------------------------------- +void XFade::step() +{ + int i; + float mix, mixa, mixb; + + if( inputs[ IN_MIXCV ].active ) + mix = inputs[ IN_MIXCV ].value / AUDIO_MAX; + else + mix = params[ PARAM_MIX ].value; + + if( mix <= 0.0 ) + { + mixa = 1.0; + mixb = 1.0 + mix; + } + else + { + mixb = 1.0; + mixa = 1.0 - mix; + } + + for( i = 0; i < CHANNELS; i++ ) + { + outputs[ OUT_L + i ].value = ( ( inputs[ IN_AL + i ].value * mixa ) + ( inputs[ IN_BL + i ].value * mixb ) ) * params[ PARAM_LEVEL ].value; + outputs[ OUT_R + i ].value = ( ( inputs[ IN_AR + i ].value * mixa ) + ( inputs[ IN_BR + i ].value * mixb ) ) * params[ PARAM_LEVEL ].value; + } +} diff --git a/src/mscHack.cpp b/src/mscHack.cpp index d175c41..62eb5ba 100644 --- a/src/mscHack.cpp +++ b/src/mscHack.cpp @@ -1,14 +1,109 @@ -#include "mscHack.hpp" - -Plugin *plugin; - -void init(rack::Plugin *p) -{ - plugin = p; - plugin->slug = "mscHack"; - plugin->name = "mscHack"; - plugin->homepageUrl = ""; - - createModel(plugin, "Seq_3ch_16step", "SEQ 3 x 16 Programmable"); - //createModel(plugin, "WaveShaper1", "Single Channel Wave Shaper"); -} +#include "mscHack.hpp" +#include "mscHack_Controls.hpp" + +Plugin *plugin; + +void init(rack::Plugin *p) +{ + plugin = p; + plugin->slug = "mscHack"; + plugin->website = "https://github.com/mschack/VCV-Rack-Plugins"; + + p->addModel(modelMasterClockx4_Widget); + p->addModel(modelSeq_3x16x16_Widget); + p->addModel(modelSEQ_6x32x16_Widget); + p->addModel(modelSeq_Triad_Widget); + p->addModel(modelSeq_Triad2_Widget); + p->addModel(modelARP700_Widget); + p->addModel(modelSynthDrums_Widget); + p->addModel(modelXFade_Widget); + p->addModel(modelMix_1x4_Stereo_Widget); + p->addModel(modelMix_2x4_Stereo_Widget); + p->addModel(modelMix_4x4_Stereo_Widget); + p->addModel(modelMix_4x4_Stereo_Widget_old); + p->addModel(modelPingPong_Widget); + p->addModel(modelOsc_3Ch_Widget); + p->addModel(modelCompressor_Widget); +} + +//----------------------------------------------------- +// Procedure: JsonDataInt +// +//----------------------------------------------------- +void JsonDataInt( bool bTo, std::string strName, json_t *root, int *pdata, int len ) +{ + int i; + json_t *jsarray, *js; + + if( !pdata || !root || len <= 0 ) + return; + + if( bTo ) + { + jsarray = json_array(); + + for ( i = 0; i < len; i++ ) + { + js = json_integer( pdata[ i ] ); + json_array_append_new( jsarray, js ); + } + + json_object_set_new( root, strName.c_str(), jsarray ); + } + else + { + jsarray = json_object_get( root, strName.c_str() ); + + if( jsarray ) + { + for ( i = 0; i < len; i++) + { + js = json_array_get( jsarray, i ); + + if( js ) + pdata[ i ] = json_integer_value( js ); + } + } + } +} + +//----------------------------------------------------- +// Procedure: JsonDataBool +// +//----------------------------------------------------- +void JsonDataBool( bool bTo, std::string strName, json_t *root, bool *pdata, int len ) +{ + int i; + json_t *jsarray, *js; + + if( !pdata || !root || len <= 0 ) + return; + + if( bTo ) + { + jsarray = json_array(); + + for ( i = 0; i < len; i++ ) + { + js = json_boolean( pdata[ i ] ); + json_array_append_new( jsarray, js ); + } + + json_object_set_new( root, strName.c_str(), jsarray ); + } + else + { + jsarray = json_object_get( root, strName.c_str() ); + + if( jsarray ) + { + for ( i = 0; i < len; i++) + { + js = json_array_get( jsarray, i ); + + if( js ) + pdata[ i ] = json_boolean_value( js ); + } + } + } +} diff --git a/src/mscHack.hpp b/src/mscHack.hpp index 6cbbc76..05284c8 100644 --- a/src/mscHack.hpp +++ b/src/mscHack.hpp @@ -1,19 +1,35 @@ -#include "rack.hpp" -#include "CLog.h" - -using namespace rack; - -extern Plugin *plugin; - -//////////////////// -// module widgets -//////////////////// - -struct Seq_3x16x16_Widget : ModuleWidget { - Seq_3x16x16_Widget(); -}; - -/*struct WaveShaper1_Widget : ModuleWidget { - WaveShaper1_Widget(); -};*/ - +#include "rack.hpp" +#include "CLog.h" + +using namespace rack; + +extern Plugin *plugin; + +#define CV_MAX (15.0f) +#define AUDIO_MAX (6.0f) +#define VOCT_MAX (6.0f) + +#define TOJSON true +#define FROMJSON false + +void JsonDataInt( bool bTo, std::string strName, json_t *root, int *pdata, int len ); +void JsonDataBool( bool bTo, std::string strName, json_t *root, bool *pdata, int len ); + + + +extern Model *modelMasterClockx4_Widget; +extern Model *modelSeq_3x16x16_Widget; +extern Model *modelSEQ_6x32x16_Widget; +extern Model *modelSeq_Triad_Widget; +extern Model *modelSeq_Triad2_Widget; +extern Model *modelARP700_Widget; +extern Model *modelSynthDrums_Widget; +extern Model *modelXFade_Widget; +extern Model *modelMix_1x4_Stereo_Widget; +extern Model *modelMix_2x4_Stereo_Widget; +extern Model *modelMix_4x4_Stereo_Widget; +extern Model *modelMix_4x4_Stereo_Widget_old; +extern Model *modelPingPong_Widget; +extern Model *modelOsc_3Ch_Widget; +extern Model *modelCompressor_Widget; + diff --git a/src/mscHack_Controls.hpp b/src/mscHack_Controls.hpp index 482fc13..3aa8d7e 100644 --- a/src/mscHack_Controls.hpp +++ b/src/mscHack_Controls.hpp @@ -1,60 +1,1893 @@ #include "rack.hpp" +#include "CLog.h" -struct CyanValueLight : ColorValueLight +#define RESOURCE_LOCATION "res/" +//#define RESOURCE_LOCATION "plugins/mschack/res/" + +struct CyanValueLight : ModuleLightWidget +{ + CyanValueLight() + { + addBaseColor( COLOR_CYAN ); + } +}; + +struct OrangeValueLight : ModuleLightWidget +{ + OrangeValueLight() + { + addBaseColor( nvgRGB( 242, 79, 0 ) ); + } +}; + +struct DarkRedValueLight : ModuleLightWidget +{ + DarkRedValueLight() + { + addBaseColor( nvgRGB(0x70, 0, 0x30) ); + } +}; + +struct DarkGreenValueLight : ModuleLightWidget +{ + DarkGreenValueLight() + { + addBaseColor( nvgRGB(0, 0x90, 0x40) );; + } +}; + +struct DarkGreen2ValueLight : ModuleLightWidget +{ + DarkGreen2ValueLight() + { + addBaseColor( nvgRGB(0, 0x40, 0) );; + } +}; + +struct DarkYellow2ValueLight : ModuleLightWidget +{ + DarkYellow2ValueLight() + { + addBaseColor( nvgRGB(0x40, 0x40, 0) );; + } +}; + +#define lvl_to_db( x ) ( 20.0 * log10( x ) ) +#define db_to_lvl( x ) ( 1.0 / pow( 10, x / 20.0 ) ) + + + +typedef struct +{ + union + { + unsigned int dwCol; + unsigned char Col[ 4 ]; + }; +}RGB_STRUCT; + +#define DWRGB( r, g, b ) (b | g<<8 | r<<16) + +typedef struct +{ + int x, y, x2, y2; +}RECT_STRUCT; + +typedef struct +{ + float fx, fy; +}POINT_STRUCT; + +typedef struct +{ + int nUsed; + POINT_STRUCT p[ 8 ]; +}DRAW_VECT_STRUCT; + +//----------------------------------------------------- +// MyLED7DigitDisplay +//----------------------------------------------------- +struct MyLED7DigitDisplay : TransparentWidget, FramebufferWidget +{ + bool m_bInitialized = false; + int m_Type; + RGB_STRUCT m_Colour; + RGB_STRUCT m_LEDColour; + int m_iVal; + float m_fVal; + float m_fScale; + float m_fSpace; + float m_MaxDigits; + + enum MyLEDDisplay_Types + { + TYPE_INT, + TYPE_FLOAT + }; + +DRAW_VECT_STRUCT DigitDrawVects[ 8 ] = +{ + { 8, { {58, 0}, {143, 0}, {148, 5}, {148, 9}, {130, 27}, {68, 27}, {51, 10}, {51, 7} } }, // top 0 + { 8, { {39, 17}, {32, 24}, {18, 124}, {24, 130}, {30, 130}, {48, 117}, {59, 33}, {43, 17} } }, // top left 1 + { 8, { {153, 18}, {135, 36}, {124, 118}, {137, 129}, {142, 129}, {151, 122}, {164, 23}, {159, 18} } }, // top right 2 + { 8, { {56, 123}, {35, 137}, {35, 140}, {50, 152}, {111, 152}, {128, 140}, {128, 136}, {114, 123} } }, // middle 3 + { 8, { {24, 145}, {14, 152}, {1, 251}, {7, 257}, {11, 257}, {31, 239}, {41, 156}, {27, 145} } }, // bottom left 4 + { 8, { {137, 145}, {117, 158}, {104, 243}, {119, 258}, {122, 258}, {131, 251}, {147, 152}, {140, 145} } }, // bottom right 5 + { 8, { {38, 247}, {16, 265}, {16, 269}, {22, 275}, {104, 275}, {112, 267}, {112, 264}, {98, 247} } }, // bottom 6 + { 4, { {4 + 154, 240}, {0 + 154, 275}, {32 + 154, 275}, {36 + 154, 240}, {0, 0}, {0, 0}, {0, 0}, {0, 0} } }, // dot +}; + +#define LED_DIGITAL_SCALE_W 160.0f +#define LED_DIGITAL_SCALE_H 275.0f +#define LED_SPACE 210.0f +#define LED_DISPLAY_DIGITS 5 + +int DigitToDisplay[ 10 ][ 8 ] = +{ + { 6, 0, 1, 2, 4, 5, 6, 0 }, // 0 + { 2, 2, 5, 0, 0, 0, 0, 0 }, // 1 + { 5, 0, 2, 3, 4, 6, 0, 0 }, // 2 + { 6, 0, 2, 3, 5, 6, 0, 0 }, // 3 + { 4, 1, 2, 3, 5, 0, 0, 0 }, // 4 + { 5, 0, 1, 3, 5, 6, 0, 0 }, // 5 + { 6, 0, 1, 3, 4, 5, 6, 0 }, // 6 + { 3, 0, 2, 5, 0, 0, 0, 0 }, // 7 + { 7, 0, 1, 2, 3, 4, 5, 6 }, // 8 + { 6, 0, 1, 2, 3, 5, 6, 0 }, // 9 +}; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + MyLED7DigitDisplay( int x, int y, float fscale, int colour, int LEDcolour, int type, int maxdigits ) + { + int i, j; + + m_Type = type; + m_Colour.dwCol = colour; + m_LEDColour.dwCol = LEDcolour; + m_fScale = fscale; + m_fSpace = fscale * LED_SPACE; + m_MaxDigits = maxdigits; + + box.pos = Vec( x, y ); + + box.size = Vec( ( ( 5.0f * LED_DIGITAL_SCALE_W ) + ( 4.0f * LED_SPACE ) ) * fscale, LED_DIGITAL_SCALE_H * fscale ); + + // rescale the LED vectors + for( i = 0; i < 8; i++ ) + { + for( j = 0; j < 8; j++ ) + { + DigitDrawVects[ i ].p[ j ].fx *= fscale; + DigitDrawVects[ i ].p[ j ].fy *= fscale; + } + } + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: SetInt + //----------------------------------------------------- + void SetInt( int ival ) + { + if( !m_bInitialized ) + return; + + if( ival == m_iVal ) + return; + + m_iVal = ival; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: SetFloat + //----------------------------------------------------- + void SetFloat( float fval ) + { + if( !m_bInitialized ) + return; + + if( fval == m_fVal ) + return; + + m_fVal = fval; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: Set + //----------------------------------------------------- + void SetLEDCol( int colour ) + { + m_LEDColour.dwCol = colour; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: Val2Digits + //----------------------------------------------------- + void Val2Digits( int *pdigits ) + { + int temp; + + if( m_Type == TYPE_FLOAT ) + temp = (int)( m_fVal * 100.0 ); + else + temp = m_iVal; + + pdigits[ 0 ] = temp / 10000; + temp -= (pdigits[ 0 ] * 10000 ); + pdigits[ 1 ] = temp / 1000; + temp -= (pdigits[ 1 ] * 1000 ); + pdigits[ 2 ] = temp / 100; + temp -= (pdigits[ 2 ] * 100 ); + pdigits[ 3 ] = temp / 10; + temp -= (pdigits[ 3 ] * 10 ); + pdigits[ 4 ] = temp; + } + + //----------------------------------------------------- + // Procedure: drawvect + //----------------------------------------------------- + void drawvect( NVGcontext *vg, float fx, float fy, DRAW_VECT_STRUCT *pvect, RGB_STRUCT *pRGB, bool bLeadingZero ) + { + int i; + + if( !m_bInitialized ) + return; + + if( bLeadingZero ) + nvgFillColor(vg, nvgRGBA( pRGB->Col[ 2 ], pRGB->Col[ 1 ], pRGB->Col[ 0 ], 0x40 ) ); + else + nvgFillColor(vg, nvgRGB( pRGB->Col[ 2 ], pRGB->Col[ 1 ], pRGB->Col[ 0 ] ) ); + + nvgBeginPath(vg); + + for( i = 0; i < pvect->nUsed; i++ ) + { + if( i == 0 ) + nvgMoveTo(vg, pvect->p[ i ].fx + fx, pvect->p[ i ].fy + fy ); + else + nvgLineTo(vg, pvect->p[ i ].fx + fx, pvect->p[ i ].fy + fy ); + } + + nvgClosePath(vg); + nvgFill(vg); + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw( NVGcontext *vg ) override + { + int digits[ LED_DISPLAY_DIGITS ] = {}; + float xi; + int i, j; + bool bLeadingZero = true, bLead; + + if( !m_bInitialized ) + return; + + xi = 0; + + // get digits from value + Val2Digits( digits ); + + // draw digits + for( i = (LED_DISPLAY_DIGITS - m_MaxDigits); i < LED_DISPLAY_DIGITS; i++ ) + { + bLead = false; + + if( ( m_Type == TYPE_FLOAT ) && ( i < 2 ) ) + bLead = ( bLeadingZero && digits[ i ] == 0 ); + else if( ( m_Type == TYPE_INT ) && ( i < 4 ) ) + bLead = ( bLeadingZero && digits[ i ] == 0 ); + + for( j = 0; j < DigitToDisplay[ digits[ i ] ][ 0 ]; j++ ) + drawvect( vg, xi, 0, &DigitDrawVects[ DigitToDisplay[ digits[ i ] ][ j + 1 ] ], &m_LEDColour, bLead ); + + if( digits[ i ] != 0 ) + bLeadingZero = false; + + // draw decimal + if( i == 2 && m_Type == TYPE_FLOAT ) + drawvect( vg, xi, 0, &DigitDrawVects[ 7 ], &m_LEDColour, false ); + + xi += m_fSpace; + } + } +}; + +//----------------------------------------------------- +// MyLEDButtonStrip +//----------------------------------------------------- +#define nMAX_STRIP_BUTTONS 32 +struct MyLEDButtonStrip : OpaqueWidget, FramebufferWidget +{ + typedef void MyLEDButtonStripCALLBACK ( void *pClass, int id, int nbutton, bool bOn ); + + bool m_bInitialized = false; + int m_Id; + int m_Type; + int m_nButtons; + bool m_bOn[ nMAX_STRIP_BUTTONS ] = {}; + int m_ExclusiveOn = 0; + int m_HiLightOn = -1; + RGB_STRUCT m_Colour; + RGB_STRUCT m_LEDColour[ nMAX_STRIP_BUTTONS ] = {}; + float m_fLEDsize; + float m_fLEDsize_d2; + + MyLEDButtonStripCALLBACK *m_pCallback; + void *m_pClass; + + RECT_STRUCT m_Rect[ nMAX_STRIP_BUTTONS ]; + + enum MyLEDButton_Types + { + TYPE_EXCLUSIVE, + TYPE_EXCLUSIVE_WOFF, + TYPE_INDEPENDANT + }; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + MyLEDButtonStrip( int x, int y, int w, int h, int space, float LEDsize, int nbuttons, bool bVert, int colour, int LEDcolour, int type, int id, void *pClass, MyLEDButtonStripCALLBACK *pCallback ) + { + int i; + + if( nbuttons < 0 || nbuttons > nMAX_STRIP_BUTTONS ) + return; + + m_Id = id; + m_pCallback = pCallback; + m_pClass = pClass; + m_Type = type; + m_Colour.dwCol = colour; + m_nButtons = nbuttons; + m_fLEDsize = LEDsize; + m_fLEDsize_d2 = LEDsize / 2.0f; + + box.pos = Vec( x, y ); + + if( bVert ) + box.size = Vec( w, h * ( nbuttons + space ) ); + else + box.size = Vec( w * ( nbuttons + space ), h ); + + x = 0; + y = 0; + + for( i = 0; i < m_nButtons; i ++) + { + m_LEDColour[ i ].dwCol = LEDcolour; + + m_Rect[ i ].x = x; + m_Rect[ i ].y = y; + m_Rect[ i ].x2 = x + w - 1; + m_Rect[ i ].y2 = y + h - 1; + + if( bVert ) + y += space + h; + else + x += space + w; + } + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: Set + //----------------------------------------------------- + void SetHiLightOn( int button ) + { + m_HiLightOn = button; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: Set + //----------------------------------------------------- + void Set( int button, bool bOn ) + { + if( !m_bInitialized || button < 0 ) + return; + + if( m_Type == TYPE_EXCLUSIVE_WOFF ) + { + if( button > m_nButtons ) + return; + + m_ExclusiveOn = button; + } + else + { + if( button >= m_nButtons ) + return; + + if( m_Type == TYPE_EXCLUSIVE ) + m_ExclusiveOn = button; + + m_bOn[ button ] = bOn; + } + + dirty = true; + } + + //----------------------------------------------------- + // Procedure: SetLEDCol + //----------------------------------------------------- + void SetLEDCol( int button, int colour ) + { + if( !m_bInitialized || button < 0 || button >= m_nButtons ) + return; + + m_LEDColour[ button ].dwCol = colour; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw(NVGcontext *vg) override + { + float xi, yi; + int i; + char alpha = 0xFF; + + if( !m_bInitialized ) + return; + + for( i = 0; i < m_nButtons; i ++) + { + if( m_HiLightOn == i ) + nvgFillColor( vg, nvgRGB( 255, 255, 255 ) ); + else + nvgFillColor( vg, nvgRGB( m_Colour.Col[ 2 ], m_Colour.Col[ 1 ], m_Colour.Col[ 0 ] ) ); + + // background + nvgBeginPath( vg ); + nvgMoveTo(vg, m_Rect[ i ].x, m_Rect[ i ].y ); + nvgLineTo(vg, m_Rect[ i ].x2, m_Rect[ i ].y ); + nvgLineTo(vg, m_Rect[ i ].x2, m_Rect[ i ].y2 ); + nvgLineTo(vg, m_Rect[ i ].x, m_Rect[ i ].y2 ); + nvgClosePath( vg ); + nvgFill( vg ); + + nvgFillColor( vg, nvgRGB(0x40, 0x40, 0x40) ); + + if( m_HiLightOn == i ) + alpha = 0x40; + + if( m_Type == TYPE_EXCLUSIVE_WOFF ) + { + if( i == ( m_ExclusiveOn - 1 ) ) + nvgFillColor( vg, nvgRGBA( m_LEDColour[ i ].Col[ 2 ], m_LEDColour[ i ].Col[ 1 ], m_LEDColour[ i ].Col[ 0 ], alpha ) ); + } + else if( m_Type == TYPE_EXCLUSIVE || m_Type == TYPE_EXCLUSIVE_WOFF ) + { + if( i == m_ExclusiveOn ) + nvgFillColor( vg, nvgRGBA( m_LEDColour[ i ].Col[ 2 ], m_LEDColour[ i ].Col[ 1 ], m_LEDColour[ i ].Col[ 0 ], alpha ) ); + } + else + { + if( m_bOn[ i ] ) + nvgFillColor( vg, nvgRGBA( m_LEDColour[ i ].Col[ 2 ], m_LEDColour[ i ].Col[ 1 ], m_LEDColour[ i ].Col[ 0 ], alpha ) ); + } + + xi = ( ( (float)m_Rect[ i ].x2 + (float)m_Rect[ i ].x ) / 2.0f ) - m_fLEDsize_d2; + yi = ( ( (float)m_Rect[ i ].y2 + (float)m_Rect[ i ].y ) / 2.0f ) - m_fLEDsize_d2; + + nvgBeginPath( vg ); + nvgMoveTo(vg, xi, yi ); + nvgLineTo(vg, xi + m_fLEDsize, yi ); + nvgLineTo(vg, xi + m_fLEDsize, yi + m_fLEDsize ); + nvgLineTo(vg, xi, yi + m_fLEDsize ); + nvgClosePath( vg ); + nvgFill( vg ); + } + } + + //----------------------------------------------------- + // Procedure: isPoint + //----------------------------------------------------- + bool isPoint( RECT_STRUCT *prect, int x, int y ) + { + if( x < prect->x || x > prect->x2 || y < prect->y || y > prect->y2 ) + return false; + + return true; + } + + //----------------------------------------------------- + // Procedure: onMouseDown + //----------------------------------------------------- + void onMouseDown( EventMouseDown &e ) override + { + int i; + e.consumed = false; + + if( !m_bInitialized || e.button != 0 ) + return; + + for( i = 0; i < m_nButtons; i++ ) + { + if( isPoint( &m_Rect[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + m_bOn[ i ] = !m_bOn[ i ]; + + if( m_Type == TYPE_EXCLUSIVE_WOFF ) + { + if( m_ExclusiveOn == ( i + 1 ) ) + m_ExclusiveOn = 0; + else + m_ExclusiveOn = i + 1; + + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, m_ExclusiveOn, false ); + } + else + { + Set( i, m_bOn[ i ] ); + + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, i, m_bOn[ i ] ); + } + + dirty = true; + e.consumed = true; + return; + } + } + + return; + } +}; + +//----------------------------------------------------- +// MyLEDButton +//----------------------------------------------------- +struct MyLEDButton : OpaqueWidget, FramebufferWidget +{ + typedef void MyLEDButtonCALLBACK ( void *pClass, int id, bool bOn ); + + bool m_bInitialized = false; + int m_Id; + int m_Type; + int m_StepCount = 0; + bool m_bOn = false; + RGB_STRUCT m_Colour; + RGB_STRUCT m_LEDColour; + float m_fLEDsize; + float m_fLEDsize_d2; + + MyLEDButtonCALLBACK *m_pCallback; + void *m_pClass; + + RECT_STRUCT m_Rect; + + enum MyLEDButton_Types + { + TYPE_SWITCH, + TYPE_MOMENTARY + }; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + MyLEDButton( int x, int y, int w, int h, float LEDsize, int colour, int LEDcolour, int type, int id, void *pClass, MyLEDButtonCALLBACK *pCallback ) + { + m_Id = id; + m_pCallback = pCallback; + m_pClass = pClass; + m_Type = type; + m_Colour.dwCol = colour; + m_LEDColour.dwCol = LEDcolour; + m_fLEDsize = LEDsize; + m_fLEDsize_d2 = LEDsize / 2.0f; + + box.pos = Vec( x, y ); + box.size = Vec( w, h ); + + m_Rect.x = 0; + m_Rect.y = 0; + m_Rect.x2 = w - 1; + m_Rect.y2 = h - 1; + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: Set + //----------------------------------------------------- + void Set( bool bOn ) + { + m_bOn = bOn; + dirty = true; + + if( m_Type == TYPE_MOMENTARY && bOn ) + m_StepCount = 8;//(int)( engineGetSampleRate() * 0.05 ); + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw(NVGcontext *vg) override + { + float xi, yi; + + if( !m_bInitialized ) + return; + + nvgFillColor( vg, nvgRGB( m_Colour.Col[ 2 ], m_Colour.Col[ 1 ], m_Colour.Col[ 0 ] ) ); + + nvgBeginPath( vg ); + nvgRect( vg, 0, 0, box.size.x - 1, box.size.y - 1 ); + nvgClosePath( vg ); + nvgFill( vg ); + + if( !m_bOn ) + nvgFillColor( vg, nvgRGB(0x40, 0x40, 0x40) ); + else + nvgFillColor( vg, nvgRGB( m_LEDColour.Col[ 2 ], m_LEDColour.Col[ 1 ], m_LEDColour.Col[ 0 ] ) ); + + xi = ( ( (float)m_Rect.x2 + (float)m_Rect.x ) / 2.0f ) - m_fLEDsize_d2 ; + yi = ( ( (float)m_Rect.y2 + (float)m_Rect.y ) / 2.0f ) - m_fLEDsize_d2 ; + + nvgBeginPath( vg ); + nvgRoundedRect( vg, xi, yi, m_fLEDsize, m_fLEDsize, 2.5 ); + nvgClosePath( vg ); + nvgFill( vg ); + } + + //----------------------------------------------------- + // Procedure: isPoint + //----------------------------------------------------- + bool isPoint( RECT_STRUCT *prect, int x, int y ) + { + if( x < prect->x || x > prect->x2 || y < prect->y || y > prect->y2 ) + return false; + + return true; + } + + //----------------------------------------------------- + // Procedure: onMouseDown + //----------------------------------------------------- + void onMouseDown( EventMouseDown &e ) override + { + e.consumed = false; + + if( !m_bInitialized || e.button != 0 ) + return; + + if( isPoint( &m_Rect, (int)e.pos.x, (int)e.pos.y ) ) + { + m_bOn = !m_bOn; + + if( m_Type == TYPE_MOMENTARY ) + { + if( m_pCallback ) + { + m_bOn = true; + m_StepCount = 8;//(int)( engineGetSampleRate() * 0.05 ); + m_pCallback( m_pClass, m_Id, true ); + } + } + else + { + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, m_bOn ); + } + + dirty = true; + e.consumed = true; + return; + } + + return; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void step() override + { + if( m_StepCount && ( m_Type == TYPE_MOMENTARY ) ) + { + if( --m_StepCount <= 0 ) + { + m_bOn = false; + m_StepCount = 0; + dirty = true; + } + } + + FramebufferWidget::step(); + } +}; + +//----------------------------------------------------- +// SinglePatternClocked32 +//----------------------------------------------------- +#define MAX_CLK_PAT 32 +struct SinglePatternClocked32 : OpaqueWidget, FramebufferWidget +{ + typedef void SINGLEPAT16CALLBACK ( void *pClass, int id, int pat, int level, int maxpat ); + + bool m_bInitialized = false; + int m_Id; + int m_nLEDs; + int m_MaxPat = 0; + int m_PatClk = 0; + int m_PatSelLevel[ MAX_CLK_PAT ] = {0}; + int m_StepCount; + + SINGLEPAT16CALLBACK *m_pCallback; + void *m_pClass; + + RECT_STRUCT m_RectsPatSel[ MAX_CLK_PAT ]; + RGB_STRUCT m_PatCol[ 2 ]; + RECT_STRUCT m_RectsMaxPat[ MAX_CLK_PAT ]; + RGB_STRUCT m_MaxCol[ 2 ]; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + SinglePatternClocked32( int x, int y, int w, int h, int mh, int space, int beatspace, int colourPaton, int colourPatoff, int colourMaxon, int colourMaxoff, int nleds, int id, void *pClass, SINGLEPAT16CALLBACK *pCallback ) + { + int i; + + if ( nleds < 2 || nleds > MAX_CLK_PAT ) + return; + + m_Id = id; + m_pCallback = pCallback; + m_pClass = pClass; + m_nLEDs = nleds; + + m_PatCol[ 0 ].dwCol = colourPatoff; + m_PatCol[ 1 ].dwCol = colourPaton; + + m_MaxCol[ 0 ].dwCol = colourMaxoff; + m_MaxCol[ 1 ].dwCol = colourMaxon; + + box.pos = Vec(x, y); + + x = 0; + + for( i = 0; i < m_nLEDs; i++ ) + { + m_RectsMaxPat[ i ].x = x; + m_RectsMaxPat[ i ].y = 0; + m_RectsMaxPat[ i ].x2 = x + w; + m_RectsMaxPat[ i ].y2 = mh; + + m_RectsPatSel[ i ].x = x; + m_RectsPatSel[ i ].y = mh + 2; + m_RectsPatSel[ i ].x2 = x + w; + m_RectsPatSel[ i ].y2 = ( h + mh ) + 2; + + if( ( i & 0x3 ) == 3 ) + x += ( w + beatspace ); + else + x += ( w + space ); + } + + box.size = Vec( x, ( h + mh ) + 2 ); + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: SetPatAll + //----------------------------------------------------- + void SetPatAll( int *pPat ) + { + if ( !pPat ) + return; + + for( int i = 0; i < m_nLEDs; i++ ) + m_PatSelLevel[ i ] = pPat[ i ] & 0x3; + + dirty = true; + } + + //----------------------------------------------------- + // Procedure: GetPatAll + //----------------------------------------------------- + void GetPatAll( int *pPat ) + { + if ( !pPat ) + return; + + for( int i = 0; i < m_nLEDs; i++ ) + pPat[ i ] = m_PatSelLevel[ i ]; + + dirty = true; + } + + //----------------------------------------------------- + // Procedure: SetPat + //----------------------------------------------------- + void SetPat( int pat ) + { + if ( pat < 0 || pat >= m_nLEDs ) + return; + + m_PatSelLevel[ pat ] = ( m_PatSelLevel[ pat ] + 1 ) & 0x3; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: SetPat + //----------------------------------------------------- + void ClrPat( int pat ) + { + if ( pat < 0 || pat >= m_nLEDs ) + return; + + m_PatSelLevel[ pat ] = 0; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: ClockInc + //----------------------------------------------------- + bool ClockInc( void ) + { + m_PatClk ++; + + if ( m_PatClk < 0 || m_PatClk > m_MaxPat || m_PatClk >= m_nLEDs ) + m_PatClk = 0; + + dirty = true; + + if( m_PatClk == 0 ) + return true; + + return false; + } + + //----------------------------------------------------- + // Procedure: ClockReset + //----------------------------------------------------- + void ClockReset( void ) + { + m_PatClk = 0; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: SetMax + //----------------------------------------------------- + void SetMax( int max ) + { + m_MaxPat = max; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw(NVGcontext *vg) override + { + float xi, yi; + + RGB_STRUCT rgb = {0}; + int i; + + if( !m_bInitialized ) + return; + + nvgFillColor(vg, nvgRGBA(0, 0, 0, 0xc0)); + nvgBeginPath(vg); + nvgMoveTo(vg, -1, -1 ); + nvgLineTo(vg, box.size.x + 1, -1 ); + nvgLineTo(vg, box.size.x + 1, box.size.y + 1 ); + nvgLineTo(vg, -1, box.size.y + 1 ); + nvgClosePath(vg); + nvgFill(vg); + + for( i = 0; i < m_nLEDs; i++ ) + { + // max pattern display + if( i <= m_MaxPat ) + nvgFillColor( vg, nvgRGB( m_MaxCol[ 1 ].Col[ 2 ], m_MaxCol[ 1 ].Col[ 1 ], m_MaxCol[ 1 ].Col[ 0 ] ) ); + else + nvgFillColor( vg, nvgRGB( m_MaxCol[ 0 ].Col[ 2 ], m_MaxCol[ 0 ].Col[ 1 ], m_MaxCol[ 0 ].Col[ 0 ] ) ); + + nvgBeginPath(vg); + xi = ( ( (float)m_RectsPatSel[ i ].x2 + (float)m_RectsPatSel[ i ].x ) / 2.0f ); + nvgMoveTo(vg, m_RectsMaxPat[ i ].x, m_RectsMaxPat[ i ].y ); + nvgLineTo(vg, m_RectsMaxPat[ i ].x2, m_RectsMaxPat[ i ].y ); + nvgLineTo(vg, xi, m_RectsMaxPat[ i ].y2 ); + //nvgLineTo(vg, m_RectsMaxPat[ i ].x, m_RectsMaxPat[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + + // pattern select + rgb.Col[ 0 ] = ( ( m_PatCol[ 1 ].Col[ 0 ] * m_PatSelLevel[ i ] ) + ( m_PatCol[ 0 ].Col[ 0 ] * ( 3 - m_PatSelLevel[ i ] ) ) ) / 3; + rgb.Col[ 1 ] = ( ( m_PatCol[ 1 ].Col[ 1 ] * m_PatSelLevel[ i ] ) + ( m_PatCol[ 0 ].Col[ 1 ] * ( 3 - m_PatSelLevel[ i ] ) ) ) / 3; + rgb.Col[ 2 ] = ( ( m_PatCol[ 1 ].Col[ 2 ] * m_PatSelLevel[ i ] ) + ( m_PatCol[ 0 ].Col[ 2 ] * ( 3 - m_PatSelLevel[ i ] ) ) ) / 3; + + nvgFillColor( vg, nvgRGB( rgb.Col[ 2 ], rgb.Col[ 1 ], rgb.Col[ 0 ] ) ); + + nvgBeginPath(vg); + nvgMoveTo(vg, m_RectsPatSel[ i ].x, m_RectsPatSel[ i ].y ); + nvgLineTo(vg, m_RectsPatSel[ i ].x2, m_RectsPatSel[ i ].y ); + nvgLineTo(vg, m_RectsPatSel[ i ].x2, m_RectsPatSel[ i ].y2 ); + nvgLineTo(vg, m_RectsPatSel[ i ].x, m_RectsPatSel[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + + xi = ( ( (float)m_RectsPatSel[ i ].x2 + (float)m_RectsPatSel[ i ].x ) / 2.0f ) - 2.0f ; + yi = ( ( (float)m_RectsPatSel[ i ].y2 + (float)m_RectsPatSel[ i ].y ) / 2.0f ) - 2.0f ; + + if( i == m_PatClk ) + nvgFillColor( vg, nvgRGBA( 0, 0xFF, 0, 0xFF ) ); + else + nvgFillColor( vg, nvgRGBA( 0, 0, 0, 0xFF ) ); + + nvgBeginPath(vg); + + nvgMoveTo(vg, xi, yi ); + nvgLineTo(vg, xi + 4.0f, yi ); + nvgLineTo(vg, xi + 4.0f, yi + 4.0f ); + nvgLineTo(vg, xi, yi + 4.0f ); + + nvgClosePath(vg); + nvgFill(vg); + } + } + + //----------------------------------------------------- + // Procedure: isPoint + //----------------------------------------------------- + bool isPoint( RECT_STRUCT *prect, int x, int y ) + { + if( x < prect->x || x > prect->x2 || y < prect->y || y > prect->y2 ) + return false; + + return true; + } + + //----------------------------------------------------- + // Procedure: onMouseDown + //----------------------------------------------------- + void onMouseDown( EventMouseDown &e ) override + { + int i; + + e.consumed = false; + + if( !m_bInitialized ) + return; + + for( i = 0; i < m_nLEDs; i++) + { + if( isPoint( &m_RectsPatSel[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + if( e.button == 0 ) + SetPat( i ); + else + ClrPat( i ); + + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, i, m_PatSelLevel[ i ], m_MaxPat ); + + dirty = true; + e.consumed = true; + return; + } + + else if( isPoint( &m_RectsMaxPat[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + m_MaxPat = i; + + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, i, m_PatSelLevel[ i ], m_MaxPat ); + + dirty = true; + e.consumed = true; + return; + } + } + + return; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void step() override + { + FramebufferWidget::step(); + } +}; + +//----------------------------------------------------- +// PatternSelectStrip +//----------------------------------------------------- +#define MAX_PAT 32 +struct PatternSelectStrip : OpaqueWidget, FramebufferWidget +{ + typedef void PATCHANGECALLBACK ( void *pClass, int id, int pat, int maxpat ); + + bool m_bInitialized = false; + int m_Id; + int m_nLEDs; + int m_MaxPat = 0; + int m_PatSel = 0; + int m_PatPending = -1; + int m_StepCount; + + PATCHANGECALLBACK *m_pCallback; + void *m_pClass; + + RECT_STRUCT m_RectsMaxPat[ MAX_PAT ]; + RECT_STRUCT m_RectsPatSel[ MAX_PAT ]; + RGB_STRUCT m_PatCol[ 2 ]; + RGB_STRUCT m_MaxCol[ 2 ]; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + PatternSelectStrip( int x, int y, int w, int h, int colourPaton, int colourPatoff, int colourMaxon, int colourMaxoff, int nleds, int id, void *pClass, PATCHANGECALLBACK *pCallback ) + { + int i; + + if ( nleds < 0 || nleds > MAX_PAT ) + return; + + m_Id = id; + m_pCallback = pCallback; + m_pClass = pClass; + m_nLEDs = nleds; + + m_PatCol[ 0 ].dwCol = colourPatoff; + m_PatCol[ 1 ].dwCol = colourPaton; + + m_MaxCol[ 0 ].dwCol = colourMaxoff; + m_MaxCol[ 1 ].dwCol = colourMaxon; + + box.pos = Vec(x, y); + box.size = Vec( w * m_nLEDs + ( m_nLEDs * 2 ) - 1, ( h * 2 ) + 2 ); + + x = 0; + + for( i = 0; i < m_nLEDs; i++ ) + { + m_RectsMaxPat[ i ].x = x; + m_RectsMaxPat[ i ].y = 0; + m_RectsMaxPat[ i ].x2 = x + w; + m_RectsMaxPat[ i ].y2 = h; + + m_RectsPatSel[ i ].x = x; + m_RectsPatSel[ i ].y = h + 2; + m_RectsPatSel[ i ].x2 = x + w; + m_RectsPatSel[ i ].y2 = ( h * 2 ) + 2; + + x += ( w + 2 ); + } + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: SetPat + //----------------------------------------------------- + void SetPat( int pat, bool bPending ) + { + if( bPending ) + m_PatPending = pat; + else + { + m_PatPending = -1; + m_PatSel = pat; + } + + dirty = true; + } + + //----------------------------------------------------- + // Procedure: SetMax + //----------------------------------------------------- + void SetMax( int max ) + { + m_MaxPat = max; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw(NVGcontext *vg) override + { + int i; + float xi; + + if( !m_bInitialized ) + return; + + nvgFillColor(vg, nvgRGBA(0, 0, 0, 0xc0)); + nvgBeginPath(vg); + nvgMoveTo(vg, -1, -1 ); + nvgLineTo(vg, box.size.x + 1, -1 ); + nvgLineTo(vg, box.size.x + 1, box.size.y + 1 ); + nvgLineTo(vg, -1, box.size.y + 1 ); + nvgClosePath(vg); + nvgFill(vg); + + for( i = 0; i < m_nLEDs; i++ ) + { + if( i <= m_MaxPat ) + nvgFillColor( vg, nvgRGB( m_MaxCol[ 1 ].Col[ 2 ], m_MaxCol[ 1 ].Col[ 1 ], m_MaxCol[ 1 ].Col[ 0 ] ) ); + else + nvgFillColor( vg, nvgRGB( m_MaxCol[ 0 ].Col[ 2 ], m_MaxCol[ 0 ].Col[ 1 ], m_MaxCol[ 0 ].Col[ 0 ] ) ); + + + + nvgBeginPath(vg); + xi = ( ( (float)m_RectsPatSel[ i ].x2 + (float)m_RectsPatSel[ i ].x ) / 2.0f ); + nvgMoveTo(vg, m_RectsMaxPat[ i ].x, m_RectsMaxPat[ i ].y ); + nvgLineTo(vg, m_RectsMaxPat[ i ].x2, m_RectsMaxPat[ i ].y ); + nvgLineTo(vg, xi, m_RectsMaxPat[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + + if( m_PatSel == i ) + nvgFillColor( vg, nvgRGB( m_PatCol[ 1 ].Col[ 2 ], m_PatCol[ 1 ].Col[ 1 ], m_PatCol[ 1 ].Col[ 0 ] ) ); + else + nvgFillColor( vg, nvgRGB( m_PatCol[ 0 ].Col[ 2 ], m_PatCol[ 0 ].Col[ 1 ], m_PatCol[ 0 ].Col[ 0 ] ) ); + + nvgBeginPath(vg); + nvgMoveTo(vg, m_RectsPatSel[ i ].x, m_RectsPatSel[ i ].y ); + nvgLineTo(vg, m_RectsPatSel[ i ].x2, m_RectsPatSel[ i ].y ); + nvgLineTo(vg, m_RectsPatSel[ i ].x2, m_RectsPatSel[ i ].y2 ); + nvgLineTo(vg, m_RectsPatSel[ i ].x, m_RectsPatSel[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + + if( m_PatPending == i ) + { + nvgFillColor( vg, nvgRGBA( m_PatCol[ 1 ].Col[ 2 ], m_PatCol[ 1 ].Col[ 1 ], m_PatCol[ 1 ].Col[ 0 ], 0x50 ) ); + + nvgBeginPath(vg); + nvgMoveTo(vg, m_RectsPatSel[ i ].x, m_RectsPatSel[ i ].y ); + nvgLineTo(vg, m_RectsPatSel[ i ].x2, m_RectsPatSel[ i ].y ); + nvgLineTo(vg, m_RectsPatSel[ i ].x2, m_RectsPatSel[ i ].y2 ); + nvgLineTo(vg, m_RectsPatSel[ i ].x, m_RectsPatSel[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + } + } + } + + //----------------------------------------------------- + // Procedure: isPoint + //----------------------------------------------------- + bool isPoint( RECT_STRUCT *prect, int x, int y ) + { + if( x < prect->x || x > prect->x2 || y < prect->y || y > prect->y2 ) + return false; + + return true; + } + + //----------------------------------------------------- + // Procedure: onMouseDown + //----------------------------------------------------- + void onMouseDown( EventMouseDown &e ) override + { + int i; + + e.consumed = false; + + if( !m_bInitialized ) + return; + + if( e.button != 0 ) + return; + + for( i = 0; i < m_nLEDs; i++) + { + if( isPoint( &m_RectsMaxPat[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + m_MaxPat = i; + + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, m_PatSel, m_MaxPat ); + + dirty = true; + e.consumed = true; + return; + } + + else if( isPoint( &m_RectsPatSel[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + m_PatSel = i; + + if( m_pCallback ) + m_pCallback( m_pClass, m_Id, m_PatSel, m_MaxPat ); + + e.consumed = true; + return; + } + } + + return; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void step() override + { + FramebufferWidget::step(); + } +}; + +//----------------------------------------------------- +// CompressorLEDMeterWidget +//----------------------------------------------------- +#define nDISPLAY_LEDS 10 +const float fleveldb[ nDISPLAY_LEDS ] = { 0, 3, 6, 10, 20, 30, 40, 50, 60, 80 }; +struct CompressorLEDMeterWidget : TransparentWidget +{ + bool m_bInitialized = false; + bool m_bOn[ nDISPLAY_LEDS ] = {}; + int m_StepCount = 0; + float m_fLargest = 0; + RECT_STRUCT m_Rects[ nDISPLAY_LEDS ]; + RGB_STRUCT m_ColourOn; + RGB_STRUCT m_ColourOff; + bool m_bInvert; + + float flevels[ nDISPLAY_LEDS ]; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + CompressorLEDMeterWidget( bool bInvert, int x, int y, int w, int h, int colouron, int colouroff ) + { + int i; + + m_bInvert = bInvert; + m_ColourOn.dwCol = colouron; + m_ColourOff.dwCol = colouroff; + + box.pos = Vec(x, y); + box.size = Vec( w, h * nDISPLAY_LEDS + ( nDISPLAY_LEDS * 2 ) ); + + y = 1; + + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + flevels[ i ] = db_to_lvl( fleveldb[ i ] ); + + m_Rects[ i ].x = 0; + m_Rects[ i ].y = y; + m_Rects[ i ].x2 = w; + m_Rects[ i ].y2 = y + h; + + y += ( h + 2 ); + } + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw(NVGcontext *vg) override + { + int i; + + if( !m_bInitialized ) + return; + + nvgFillColor(vg, nvgRGBA(0, 0, 0, 0xc0)); + nvgBeginPath(vg); + nvgMoveTo(vg, -1, -1 ); + nvgLineTo(vg, box.size.x + 1, -1 ); + nvgLineTo(vg, box.size.x + 1, box.size.y + 1 ); + nvgLineTo(vg, -1, box.size.y + 1 ); + nvgClosePath(vg); + nvgFill(vg); + + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + if( m_bOn[ i ] ) + nvgFillColor( vg, nvgRGB( m_ColourOn.Col[ 2 ], m_ColourOn.Col[ 1 ], m_ColourOn.Col[ 0 ] ) ); + else + nvgFillColor( vg, nvgRGB( m_ColourOff.Col[ 2 ], m_ColourOff.Col[ 1 ], m_ColourOff.Col[ 0 ] ) ); + + nvgBeginPath(vg); + nvgMoveTo(vg, m_Rects[ i ].x, m_Rects[ i ].y ); + nvgLineTo(vg, m_Rects[ i ].x2, m_Rects[ i ].y ); + nvgLineTo(vg, m_Rects[ i ].x2, m_Rects[ i ].y2 ); + nvgLineTo(vg, m_Rects[ i ].x, m_Rects[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + } + } + + //----------------------------------------------------- + // Procedure: Process + //----------------------------------------------------- + void Process( float level ) + { + int steptime = (int)( engineGetSampleRate() * 0.05f ); + int i; + + if( !m_bInitialized ) + return; + + if( fabs( level ) > m_fLargest ) + m_fLargest = fabs( level ); + + // only process every 1/10th of a second + if( ++m_StepCount >= steptime ) + { + m_StepCount = 0; + + if( m_bInvert ) + { + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + if( m_fLargest >= flevels[ i ] ) + m_bOn[ ( nDISPLAY_LEDS - 1 ) - i ] = true; + else + m_bOn[ ( nDISPLAY_LEDS - 1 ) - i ] = false; + } + } + else + { + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + if( m_fLargest >= flevels[ i ] ) + m_bOn[ i ] = true; + else + m_bOn[ i ] = false; + } + } + + m_fLargest = 0.0f; + } + } +}; + +//----------------------------------------------------- +// LEDMeterWidget +//----------------------------------------------------- +struct LEDMeterWidget : TransparentWidget +{ + bool m_bInitialized = false; + bool m_bOn[ nDISPLAY_LEDS ] = {}; + int m_space; + int m_StepCount = 0; + bool m_bVert; + float m_fLargest = 0.0; + RECT_STRUCT m_Rects[ nDISPLAY_LEDS ]; + RGB_STRUCT m_ColoursOn[ nDISPLAY_LEDS ]; + RGB_STRUCT m_ColoursOff[ nDISPLAY_LEDS ]; + float flevels[ nDISPLAY_LEDS ] = {}; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + LEDMeterWidget( int x, int y, int w, int h, int space, bool bVert ) + { + int i, xoff = 0, yoff = 0, xpos = 0, ypos = 0; + + m_space = space; + box.pos = Vec(x, y); + + if( bVert ) + { + box.size = Vec( w, h * nDISPLAY_LEDS + (m_space * nDISPLAY_LEDS) ); + yoff = h + m_space; + } + else + { + box.size = Vec( w * nDISPLAY_LEDS + (m_space * nDISPLAY_LEDS), h ); + xoff = w + m_space; + } + + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + flevels[ i ] = db_to_lvl( fleveldb[ i ] ); + + m_Rects[ i ].x = xpos; + m_Rects[ i ].y = ypos; + m_Rects[ i ].x2 = xpos + w; + m_Rects[ i ].y2 = ypos + h; + + // always red + if( i == 0 ) + { + m_ColoursOn[ i ].dwCol = DWRGB( 0xFF, 0, 0 ); + m_ColoursOff[ i ].dwCol= DWRGB( 0x80, 0, 0 ); + } + // yellow + else if( i < 3 ) + { + m_ColoursOn[ i ].dwCol = DWRGB( 0xFF, 0xFF, 0 ); + m_ColoursOff[ i ].dwCol= DWRGB( 0x80, 0x80, 0 ); + } + // green + else + { + m_ColoursOn[ i ].dwCol = DWRGB( 0, 0xFF, 0 ); + m_ColoursOff[ i ].dwCol= DWRGB( 0, 0x80, 0 ); + } + + xpos += xoff; + ypos += yoff; + } + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw(NVGcontext *vg) override + { + int i; + + if( !m_bInitialized ) + return; + + nvgFillColor(vg, nvgRGBA(0, 0, 0, 0xc0)); + nvgBeginPath(vg); + nvgMoveTo(vg, -1, -1 ); + nvgLineTo(vg, box.size.x + 1, -1 ); + nvgLineTo(vg, box.size.x + 1, box.size.y + 1 ); + nvgLineTo(vg, -1, box.size.y + 1 ); + nvgClosePath(vg); + nvgFill(vg); + + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + if( m_bOn[ i ] ) + nvgFillColor( vg, nvgRGB( m_ColoursOn[ i ].Col[ 2 ], m_ColoursOn[ i ].Col[ 1 ], m_ColoursOn[ i ].Col[ 0 ] ) ); + else + nvgFillColor( vg, nvgRGB( m_ColoursOff[ i ].Col[ 2 ], m_ColoursOff[ i ].Col[ 1 ], m_ColoursOff[ i ].Col[ 0 ] ) ); + + nvgBeginPath(vg); + nvgMoveTo(vg, m_Rects[ i ].x, m_Rects[ i ].y ); + nvgLineTo(vg, m_Rects[ i ].x2, m_Rects[ i ].y ); + nvgLineTo(vg, m_Rects[ i ].x2, m_Rects[ i ].y2 ); + nvgLineTo(vg, m_Rects[ i ].x, m_Rects[ i ].y2 ); + nvgClosePath(vg); + nvgFill(vg); + } + } + + //----------------------------------------------------- + // Procedure: Process + //----------------------------------------------------- + void Process( float level ) + { + int steptime = (int)( engineGetSampleRate() * 0.05 ); + int i; + + if( !m_bInitialized ) + return; + + if( fabs( level ) > m_fLargest ) + m_fLargest = fabs( level ); + + // only process every 1/10th of a second + if( ++m_StepCount >= steptime ) + { + m_StepCount = 0; + + for( i = 0; i < nDISPLAY_LEDS; i++ ) + { + if( m_fLargest >= flevels[ i ] ) + m_bOn[ i ] = true; + else + m_bOn[ i ] = false; + } + + m_fLargest = 0.0; + } + } +}; + +//----------------------------------------------------- +// Keyboard_3Oct_Widget +//----------------------------------------------------- +struct Keyboard_3Oct_Widget : OpaqueWidget, FramebufferWidget +{ + typedef void NOTECHANGECALLBACK ( void *pClass, int kb, int notepressed, int *pnotes, bool bOn ); + +#define nKEYS 37 +#define MAX_MULT_KEYS 16 +#define OCT_OFFSET_X 91 + + bool m_bInitialized = false; + RGB_STRUCT m_rgb_white, m_rgb_black, m_rgb_on; + CLog *lg; + int m_MaxMultKeys = 1; + int m_KeySave[ MAX_MULT_KEYS ] = {0}; + bool m_bKeyOnList[ nKEYS ] = {false}; + int m_nKeysOn = 0; + int m_KeyOn = 0; + RECT_STRUCT keyrects[ nKEYS ] ={}; + NOTECHANGECALLBACK *pNoteChangeCallback = NULL; + void *m_pClass = NULL; + int m_nKb; + +DRAW_VECT_STRUCT OctaveKeyDrawVects[ nKEYS ] = +{ + { 6, { {1, 1}, {1, 62}, {12, 62}, {12, 39}, {7, 39}, {7, 1}, {0, 0}, {0, 0} } }, + { 4, { {8, 1}, {8, 38}, {16, 38}, {16, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0} } }, + { 8, { {17, 1}, {17, 39}, {14, 39}, {14, 62}, {25, 62}, {25, 39}, {22, 39}, {22, 1} } }, + { 4, { {23, 1}, {23, 38}, {31, 38}, {31, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0} } }, + { 6, { {32, 1}, {32, 39}, {27, 39}, {27, 62}, {38, 62}, {38, 1}, {0, 0}, {0, 0} } }, + { 6, { {40, 1}, {40, 62}, {51, 62}, {51, 39}, {46, 39}, {46, 1}, {0, 0}, {0, 0} } }, + { 4, { {47, 1}, {47, 38}, {55, 38}, {55, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0} } }, + { 8, { {56, 1}, {56, 39}, {53, 39}, {53, 62}, {64, 62}, {64, 39}, {60, 39}, {60, 1} } }, + { 4, { {61, 1}, {61, 38}, {69, 38}, {69, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0} } }, + { 8, { {70, 1}, {70, 39}, {66, 39}, {66, 62}, {77, 62}, {77, 39}, {74, 39}, {74, 1} } }, + { 4, { {75, 1}, {75, 38}, {83, 38}, {83, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0} } }, + { 6, { {84, 1}, {84, 39}, {79, 39}, {79, 62}, {90, 62}, {90, 1}, {0, 0}, {0, 0} } }, +}; + +int keysquare_x[ nKEYS ] = { - CyanValueLight() + 1, 8, 14, 23, 27, 40, 47, 53, 61, 66, 75, 79 +}; + +DRAW_VECT_STRUCT OctaveKeyHighC [ 1 ] = +{ + { 5, { {1, 1}, {1, 62}, {12, 62}, {12, 44}, {12, 1}, {0, 0}, {0, 0}, {0, 0} } } +}; + + //----------------------------------------------------- + // Procedure: Constructor + //----------------------------------------------------- + Keyboard_3Oct_Widget( int x, int y, int maxkeysel, int nKb, unsigned int rgbon, void *pClass, NOTECHANGECALLBACK *pcallback, CLog *plog ) + { + int i, j, oct = 0; + + if( maxkeysel > MAX_MULT_KEYS ) + return; + + m_MaxMultKeys = maxkeysel; + pNoteChangeCallback = pcallback; + m_pClass = pClass; + m_nKb = nKb; + m_KeyOn = -1; + m_rgb_on.dwCol = rgbon; + + lg = plog; + + box.pos = Vec(x, y); + + // calc key rects and calculate the remainder of the key vec list + for( i = 0; i < nKEYS; i++ ) + { + if( i >= 12 ) + { + oct = i / 12; + + if( i == (nKEYS-1) ) + { + // populate the rest of the key vect table based on the first octave + memcpy( &OctaveKeyDrawVects[ i ], &OctaveKeyHighC[ 0 ], sizeof(DRAW_VECT_STRUCT) ); + + for( j = 0; j < 8; j++ ) + OctaveKeyDrawVects[ i ].p[ j ].fx = OctaveKeyHighC[ 0 ].p[ j ].fx + ( OCT_OFFSET_X * oct ); + + keysquare_x[ i ] = keysquare_x[ 0 ] + ( OCT_OFFSET_X * oct ); + } + else + { + // populate the rest of the key vect table based on the first octave + memcpy( &OctaveKeyDrawVects[ i ], &OctaveKeyDrawVects[ i - ( oct * 12 ) ], sizeof(DRAW_VECT_STRUCT) ); + + for( j = 0; j < 8; j++ ) + OctaveKeyDrawVects[ i ].p[ j ].fx = OctaveKeyDrawVects[ i - ( oct * 12 ) ].p[ j ].fx + ( OCT_OFFSET_X * oct ); + + keysquare_x[ i ] = keysquare_x[ i - ( oct * 12 ) ] + ( OCT_OFFSET_X * oct ); + } + } + + // build the key rects for key press detection + keyrects[ i ].x = keysquare_x[ i ]- 1; + keyrects[ i ].y = 1; + + if( OctaveKeyDrawVects[ i ].nUsed == 4 ) + { + // black key + keyrects[ i ].x2 = keyrects[ i ].x + 9; + keyrects[ i ].y2 = keyrects[ i ].y + 43; + } + else + { + // white key + keyrects[ i ].x2 = keyrects[ i ].x + 14; + keyrects[ i ].y2 = keyrects[ i ].y + 67; + } + } + + box.size = Vec( keyrects[ (nKEYS-1) ].x2, 62 ); + + m_rgb_white.dwCol = DWRGB( 215, 207, 198 ); + m_rgb_black.dwCol = 0; + memset( m_KeySave, 0xFF, sizeof(m_KeySave) ); + + m_bInitialized = true; + } + + //----------------------------------------------------- + // Procedure: addtokeylist + //----------------------------------------------------- + void addtokeylist( int key ) + { + int count = 0; + bool bOn = false; + + // single key + if( m_MaxMultKeys == 1 ) + { + m_KeySave[ 0 ] = key; + bOn = true; + } + else + { + // if key is off we are turning it on, check max keys + if( !m_bKeyOnList[ key ] ) + { + // ignore if we already have max keys + if( ( m_nKeysOn + 1 ) > m_MaxMultKeys ) + return; + + bOn = true; + m_nKeysOn++; + m_bKeyOnList[ key ] = true; + } + else + { + m_nKeysOn--; + m_bKeyOnList[ key ] = false; + } + + memset( m_KeySave, 0xFF, sizeof(m_KeySave) ); + + // build key list + for( int i = 0; i < nKEYS; i++ ) + { + if( m_bKeyOnList[ i ] ) + m_KeySave[ count++ ] = i; + } + } + + if( pNoteChangeCallback ) + pNoteChangeCallback( m_pClass, m_nKb, key, m_KeySave, bOn ); + } + + //----------------------------------------------------- + // Procedure: isPoint + //----------------------------------------------------- + bool isPoint( RECT_STRUCT *prect, int x, int y ) + { + if( x < prect->x || x > prect->x2 || y < prect->y || y > prect->y2 ) + return false; + + return true; + } + + //----------------------------------------------------- + // Procedure: onMouseDown + //----------------------------------------------------- + void onMouseDown( EventMouseDown &e ) override + { + int i; + + e.consumed = false; + + if( !m_bInitialized ) + return; + + // check black keys first they are on top + for( i = 0; i < nKEYS; i++) + { + if( OctaveKeyDrawVects[ i ].nUsed == 4 && isPoint( &keyrects[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + addtokeylist( i ); + dirty = true; + e.consumed = true; + return; + } + } + + // check white keys + for( i = 0; i < nKEYS; i++) + { + if( OctaveKeyDrawVects[ i ].nUsed != 4 && isPoint( &keyrects[ i ], (int)e.pos.x, (int)e.pos.y ) ) + { + addtokeylist( i ); + dirty = true; + e.consumed = true; + return; + } + } + + return; + } + + //----------------------------------------------------- + // Procedure: setkey + //----------------------------------------------------- + void setkey( int *pkey ) + { + memset( m_bKeyOnList, 0, sizeof( m_bKeyOnList ) ); + memset( m_KeySave, 0xFF, sizeof( m_KeySave ) ); + m_nKeysOn = 0; + + // build key list + for( int i = 0; i < m_MaxMultKeys; i++ ) + { + if( pkey[ i ] != -1 ) + { + m_nKeysOn++; + m_bKeyOnList[ pkey[ i ] ] = true; + m_KeySave[ i ] = pkey[ i ]; + } + } + + dirty = true; + } + + //----------------------------------------------------- + // Procedure: setkeyhighlight + //----------------------------------------------------- + void setkeyhighlight( int key ) + { + m_KeyOn = key; + dirty = true; + } + + //----------------------------------------------------- + // Procedure: drawkey + //----------------------------------------------------- + void drawkey( NVGcontext *vg, int key, bool bOn ) { - baseColor = COLOR_CYAN; + int i; + + if( !m_bInitialized ) + return; + + if( key < 0 || key >= nKEYS ) + return; + + if( bOn ) + { + // hilite on + if( key == m_KeyOn ) + nvgFillColor( vg, nvgRGBA( m_rgb_on.Col[ 2 ], m_rgb_on.Col[ 1 ], m_rgb_on.Col[ 0 ], 0x80 ) ); + // normal on + else + nvgFillColor( vg, nvgRGB( m_rgb_on.Col[ 2 ], m_rgb_on.Col[ 1 ], m_rgb_on.Col[ 0 ] ) ); + } + else + { + if( OctaveKeyDrawVects[ key ].nUsed == 4 ) + nvgFillColor( vg, nvgRGB( m_rgb_black.Col[ 2 ], m_rgb_black.Col[ 1 ], m_rgb_black.Col[ 0 ] ) ); + else + nvgFillColor( vg, nvgRGB( m_rgb_white.Col[ 2 ], m_rgb_white.Col[ 1 ], m_rgb_white.Col[ 0 ] ) ); + } + + // draw key + nvgBeginPath(vg); + + for( i = 0; i < OctaveKeyDrawVects[ key ].nUsed; i++ ) + { + if( i == 0 ) + nvgMoveTo(vg, (float)OctaveKeyDrawVects[ key ].p[ i ].fx, (float)OctaveKeyDrawVects[ key ].p[ i ].fy ); + else + nvgLineTo(vg, (float)OctaveKeyDrawVects[ key ].p[ i ].fx, (float)OctaveKeyDrawVects[ key ].p[ i ].fy ); + } + + nvgClosePath(vg); + nvgFill(vg); } + + //----------------------------------------------------- + // Procedure: draw + //----------------------------------------------------- + void draw( NVGcontext *vg ) override + { + int key; + + for( key = 0; key < nKEYS; key++ ) + drawkey( vg, key, false ); + + for( key = 0; key < m_MaxMultKeys; key++ ) + { + if( m_KeySave[ key ] != -1 ) + drawkey( vg, m_KeySave[ key ], true ); + } + } }; -struct OrangeValueLight : ColorValueLight +//----------------------------------------------------- +// Procedure: MySquareButton +// +//----------------------------------------------------- +struct MySquareButtonSmall : SVGSwitch, MomentarySwitch { - OrangeValueLight() + MySquareButtonSmall() { - baseColor = nvgRGB(242, 79, 0); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_square_button.svg"))); + sw->wrap(); + box.size = Vec(9, 9); } }; -struct DarkRedValueLight : ColorValueLight +//----------------------------------------------------- +// Procedure: MySquareButton +// +//----------------------------------------------------- +struct MySquareButton : SVGSwitch, MomentarySwitch { - DarkRedValueLight() + MySquareButton() { - baseColor = nvgRGB(0x70, 0, 0x30); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_square_button.svg"))); + sw->wrap(); + box.size = sw->box.size; } }; -struct DarkGreenValueLight : ColorValueLight +//----------------------------------------------------- +// Procedure: MySquareButton2 +// +//----------------------------------------------------- +struct MySquareButton2 : SVGSwitch, MomentarySwitch { - DarkGreenValueLight() + MySquareButton2() { - baseColor = nvgRGB(0, 0x90, 0x40); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_Square_Button2.svg"))); + sw->wrap(); + box.size = sw->box.size; } }; //----------------------------------------------------- -// Procedure: MyScrew +// Procedure: PianoWhiteKey // //----------------------------------------------------- -struct MyScrew : SVGScrew +struct PianoWhiteKey : SVGSwitch, ToggleSwitch { - MyScrew() + PianoWhiteKey() { - sw->svg = SVG::load(assetGlobal("plugins/mschack/res/mschack_screw.svg")); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_WhiteKeyOff.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_WhiteKeyOn.svg"))); sw->wrap(); box.size = sw->box.size; } }; //----------------------------------------------------- -// Procedure: MySquareButton +// Procedure: PianoBlackKey // //----------------------------------------------------- -struct MySquareButton : SVGSwitch, MomentarySwitch +struct PianoBlackKey : SVGSwitch, ToggleSwitch { - MySquareButton() + PianoBlackKey() { - addFrame(SVG::load(assetGlobal("plugins/mschack/res/mschack_square_button.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_BlackKeyOff.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_BlackKeyOn.svg"))); sw->wrap(); box.size = sw->box.size; } @@ -68,9 +1901,29 @@ struct MyToggle1 : SVGSwitch, ToggleSwitch { MyToggle1() { - addFrame(SVG::load(assetGlobal("plugins/mschack/res/mschack_3p_vert_simple_01.svg"))); - addFrame(SVG::load(assetGlobal("plugins/mschack/res/mschack_3p_vert_simple_02.svg"))); - addFrame(SVG::load(assetGlobal("plugins/mschack/res/mschack_3p_vert_simple_03.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_3p_vert_simple_01.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_3p_vert_simple_02.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_3p_vert_simple_03.svg"))); + + sw->wrap(); + box.size = sw->box.size; + } +}; + +//----------------------------------------------------- +// Procedure: FilterSelectToggle +// +//----------------------------------------------------- +struct FilterSelectToggle : SVGSwitch, ToggleSwitch +{ + FilterSelectToggle() + { + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_5p_filtersel_01.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_5p_filtersel_02.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_5p_filtersel_03.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_5p_filtersel_04.svg"))); + addFrame(SVG::load(assetPlugin(plugin,"res/mschack_5p_filtersel_05.svg"))); + sw->wrap(); box.size = sw->box.size; } @@ -80,18 +1933,21 @@ struct MyToggle1 : SVGSwitch, ToggleSwitch // Procedure: MySlider_01 // //----------------------------------------------------- -struct MySlider_01 : SVGSlider +struct MySlider_01 : SVGFader { MySlider_01() { + Vec margin = Vec(0, 0); - maxHandlePos = Vec(0, 0).plus(margin); + maxHandlePos = Vec(0, -4).plus(margin); minHandlePos = Vec(0, 33).plus(margin); - background->svg = SVG::load(assetGlobal("plugins/mschack/res/mschack_sliderBG_01.svg")); + + background->svg = SVG::load(assetPlugin(plugin,"res/mschack_sliderBG_01.svg")); background->wrap(); background->box.pos = margin; box.size = background->box.size.plus(margin.mult(2)); - handle->svg = SVG::load(assetGlobal("plugins/mschack/res/mschack_sliderKNOB_01.svg")); + + handle->svg = SVG::load(assetPlugin(plugin,"res/mschack_sliderKNOB_01.svg")); handle->wrap(); } }; @@ -104,7 +1960,7 @@ struct MyPortInSmall : SVGPort { MyPortInSmall() { - background->svg = SVG::load(assetGlobal("plugins/mschack/res/mschack_PortIn_small.svg")); + background->svg = SVG::load(assetPlugin(plugin, "res/mschack_PortIn_small.svg" ) ); background->wrap(); box.size = background->box.size; } @@ -118,8 +1974,260 @@ struct MyPortOutSmall : SVGPort { MyPortOutSmall() { - background->svg = SVG::load(assetGlobal("plugins/mschack/res/mschack_PortOut_small.svg")); + background->svg = SVG::load(assetPlugin(plugin, "res/mschack_PortOut_small.svg" ) ); background->wrap(); box.size = background->box.size; } +}; + +//----------------------------------------------------- +// Procedure: Red1_Med +// +//----------------------------------------------------- +struct Red1_Med : RoundKnob +{ + Red1_Med() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_KnobRed1.svg" ))); + box.size = Vec(20, 20); + } +}; + +//----------------------------------------------------- +// Procedure: Blue3_Med +// +//----------------------------------------------------- +struct Blue3_Med : RoundKnob +{ + Blue3_Med() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_KnobBlue3.svg" ))); + box.size = Vec(20, 20); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow3_Med +// +//----------------------------------------------------- +struct Yellow3_Med : RoundKnob +{ + Yellow3_Med() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_KnobYellow3.svg" ))); + box.size = Vec(20, 20); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow3_Med_Snap +// +//----------------------------------------------------- +struct Yellow3_Med_Snap : RoundKnob +{ + Yellow3_Med_Snap() + { + snap = true; + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_KnobYellow3.svg" ))); + box.size = Vec(20, 20); + } +}; + + +//----------------------------------------------------- +// Procedure: Purp1_Med +// +//----------------------------------------------------- +struct Purp1_Med : RoundKnob +{ + Purp1_Med() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_KnobPurp1.svg" ))); + box.size = Vec(20, 20); + } +}; + +//----------------------------------------------------- +// Procedure: Green1_Tiny +// +//----------------------------------------------------- +struct Green1_Tiny : RoundKnob +{ + Green1_Tiny() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Green1_small.svg" ))); + box.size = Vec(15, 15); + } +}; + +//----------------------------------------------------- +// Procedure: Green1_Big +// +//----------------------------------------------------- +struct Green1_Big : RoundKnob +{ + Green1_Big() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Green1_small.svg" ))); + box.size = Vec(40, 40); + } +}; + +//----------------------------------------------------- +// Procedure: Blue1_Small +// +//----------------------------------------------------- +struct Blue1_Small : RoundKnob +{ + Blue1_Small() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Blue1_small.svg" ))); + box.size = Vec(26, 26); + } +}; + +//----------------------------------------------------- +// Procedure: Blue2_Small +// +//----------------------------------------------------- +struct Blue2_Small : RoundKnob +{ + Blue2_Small() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Blue2_small.svg" ))); + box.size = Vec(26, 26); + } +}; + +//----------------------------------------------------- +// Procedure: Blue2_Tiny +// +//----------------------------------------------------- +struct Blue2_Tiny : RoundKnob +{ + Blue2_Tiny() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Blue2_small.svg" ))); + box.size = Vec(15, 15); + } +}; + +//----------------------------------------------------- +// Procedure: Blue2_Med +// +//----------------------------------------------------- +struct Blue2_Med : RoundKnob +{ + Blue2_Med() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Blue2_big.svg" ))); + box.size = Vec(40, 40); + } +}; + +//----------------------------------------------------- +// Procedure: Blue2_Big +// +//----------------------------------------------------- +struct Blue2_Big : RoundKnob +{ + Blue2_Big() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_Blue2_big.svg" ))); + box.size = Vec(56, 56); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow1_Small +// +//----------------------------------------------------- +struct Yellow1_Small : RoundKnob +{ + Yellow1_Small() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow1_small.svg" ))); + box.size = Vec(26, 26); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow1_Tiny +// +//----------------------------------------------------- +struct Yellow1_Tiny : RoundKnob +{ + Yellow1_Tiny() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow1_small.svg" ))); + box.size = Vec(15, 15); + } +}; + + +//----------------------------------------------------- +// Procedure: Yellow2_Small +// +//----------------------------------------------------- +struct Yellow2_Small : RoundKnob +{ + Yellow2_Small() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow2_small.svg" ))); + box.size = Vec(26, 26); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow2_Big +// +//----------------------------------------------------- +struct Yellow2_Big : RoundKnob +{ + Yellow2_Big() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow2_small.svg" ))); + box.size = Vec(40, 40); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow2_Huge +// +//----------------------------------------------------- +struct Yellow2_Huge : RoundKnob +{ + Yellow2_Huge() + { + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow2_small.svg" ))); + box.size = Vec(56, 56); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow2_Huge_Snap +// +//----------------------------------------------------- +struct Yellow2_Huge_Snap : RoundKnob +{ + Yellow2_Huge_Snap() + { + snap = true; + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow2_small.svg" ))); + box.size = Vec(56, 56); + } +}; + +//----------------------------------------------------- +// Procedure: Yellow2_Huge +// +//----------------------------------------------------- +struct Yellow2_Snap : RoundKnob +{ + Yellow2_Snap() + { + snap = true; + setSVG(SVG::load(assetPlugin(plugin, "res/mschack_yellow2_small.svg" ))); + box.size = Vec(26, 26); + } }; \ No newline at end of file