├── .gitignore ├── res ├── fonts │ ├── EnvyCodeR.ttf │ ├── EnvyCodeR-Bold.ttf │ └── EnvyCodeR-LICENSE.md ├── blank-1.svg ├── DaisyChannelVu.svg ├── DaisyChannelVu-dark.svg ├── DaisyBlank.svg ├── DaisyBlank-dark.svg ├── blank-3.svg ├── blank-5.svg ├── src │ ├── DaisyBlank.src.svg │ ├── DaisyBlank-dark.src.svg │ ├── DaisyChannelVu.src.svg │ ├── DaisyChannelVu-dark.src.svg │ ├── DaisyChannelSends2.src.svg │ ├── DaisyChannelSends2-dark.src.svg │ ├── DaisyChannel2.src.svg │ ├── DaisyChannel2-dark.src.svg │ ├── BufferedMult.src.svg │ ├── BufferedMult-dark.src.svg │ ├── DaisyChannel.src.svg │ ├── DaisyChannel-dark.src.svg │ ├── DaisyMaster2.src.svg │ ├── DaisyMaster2-dark.src.svg │ ├── DaisyMaster.src.svg │ ├── UnityMix.src.svg │ ├── UnityMix-dark.src.svg │ ├── DaisyMaster-dark.src.svg │ ├── MasterMixer.src.svg │ ├── MasterMixer-dark.src.svg │ ├── Horsehair.src.svg │ └── Horsehair-dark.src.svg ├── Makefile ├── BufferedMult.svg ├── BufferedMult-dark.svg ├── UnityMix.svg ├── UnityMix-dark.svg ├── DaisyChannel.svg ├── DaisyMaster2.svg ├── DaisyChannel-dark.svg ├── DaisyMaster2-dark.svg ├── DaisyChannel2.svg ├── DaisyChannel2-dark.svg ├── DaisyChannelSends2.svg ├── DaisyMaster.svg ├── DaisyChannelSends2-dark.svg └── DaisyMaster-dark.svg ├── doc └── img │ ├── quantal-audio-mods.png │ ├── daisy-master-context-menu.png │ ├── quantal-audio-daisy-mixer.png │ ├── daisy-channel-context-menu.png │ └── quantal-audio-daisy-mixer2.png ├── src ├── Blank1.cpp ├── Blank3.cpp ├── Blank5.cpp ├── QuantalAudio.hpp ├── QuantalAudio.cpp ├── Daisy.hpp ├── BufferedMult.cpp ├── DaisyMaster.cpp ├── DaisyChannel.cpp ├── UnityMix.cpp ├── DaisyBlank.cpp ├── MasterMixer.cpp ├── DaisyChannelVu.cpp └── DaisyChannelSends2.cpp ├── .astylerc ├── Makefile ├── changelog.md └── plugin.json /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /plugin.dylib 4 | /plugin.dll 5 | /plugin.so 6 | .DS_Store -------------------------------------------------------------------------------- /res/fonts/EnvyCodeR.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/res/fonts/EnvyCodeR.ttf -------------------------------------------------------------------------------- /res/fonts/EnvyCodeR-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/res/fonts/EnvyCodeR-Bold.ttf -------------------------------------------------------------------------------- /doc/img/quantal-audio-mods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/doc/img/quantal-audio-mods.png -------------------------------------------------------------------------------- /doc/img/daisy-master-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/doc/img/daisy-master-context-menu.png -------------------------------------------------------------------------------- /doc/img/quantal-audio-daisy-mixer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/doc/img/quantal-audio-daisy-mixer.png -------------------------------------------------------------------------------- /doc/img/daisy-channel-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/doc/img/daisy-channel-context-menu.png -------------------------------------------------------------------------------- /doc/img/quantal-audio-daisy-mixer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumpygump/quantal-audio/HEAD/doc/img/quantal-audio-daisy-mixer2.png -------------------------------------------------------------------------------- /res/blank-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/DaisyChannelVu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyChannelVu-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyBlank.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyBlank-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Blank1.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | 3 | struct Blank1Widget : ModuleWidget { 4 | explicit Blank1Widget(Module *module) { 5 | setModule(module); 6 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/blank-1.svg"))); 7 | } 8 | }; 9 | 10 | Model* modelBlank1 = createModel("Blank1"); 11 | -------------------------------------------------------------------------------- /res/blank-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /res/blank-5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /src/Blank3.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | 3 | struct Blank3Widget : ModuleWidget { 4 | explicit Blank3Widget(Module *module) { 5 | setModule(module); 6 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/blank-3.svg"))); 7 | 8 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 9 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 10 | } 11 | }; 12 | 13 | Model* modelBlank3 = createModel("Blank3"); 14 | -------------------------------------------------------------------------------- /res/src/DaisyBlank.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /res/src/DaisyBlank-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Blank5.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | 3 | struct Blank5Widget : ModuleWidget { 4 | explicit Blank5Widget(Module *module) { 5 | setModule(module); 6 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/blank-5.svg"))); 7 | 8 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 9 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 10 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 11 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 12 | } 13 | }; 14 | 15 | Model* modelBlank5 = createModel("Blank5"); 16 | -------------------------------------------------------------------------------- /src/QuantalAudio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "rack.hpp" 3 | 4 | using namespace rack; 5 | 6 | // Forward-declare the Plugin, defined in Template.cpp 7 | extern Plugin* pluginInstance; 8 | 9 | // Forward-declare each Model, defined in each module source file 10 | extern Model* modelMasterMixer; 11 | extern Model* modelBufferedMult; 12 | extern Model* modelUnityMix; 13 | extern Model* modelDaisyBlank; 14 | extern Model* modelDaisyChannel; 15 | extern Model* modelDaisyChannel2; 16 | extern Model* modelDaisyChannelSends2; 17 | extern Model* modelDaisyChannelVu; 18 | extern Model* modelDaisyMaster; 19 | extern Model* modelDaisyMaster2; 20 | extern Model* modelHorsehair; 21 | extern Model* modelBlank1; 22 | extern Model* modelBlank3; 23 | extern Model* modelBlank5; -------------------------------------------------------------------------------- /.astylerc: -------------------------------------------------------------------------------- 1 | # astyle settings file 2 | 3 | # Requires Artistic Style 3.1 4 | # http://astyle.sourceforge.net/ 5 | 6 | # Usage: 7 | # astyle --suffix=none --options=.astylerc filename.cpp 8 | # or recursively 9 | # astyle --suffix=none --options=.astylerc -r 'include/*' 'src/*' 10 | # or using find 11 | # find src include -type f | xargs astyle --suffix=none --options=.astylerc 12 | 13 | style=java 14 | 15 | add-braces 16 | align-pointer=type 17 | align-reference=type 18 | attach-closing-while 19 | attach-return-type 20 | attach-return-type-decl 21 | break-one-line-headers 22 | close-templates 23 | indent-col1-comments 24 | indent-preproc-block 25 | indent-preproc-define 26 | indent-switches 27 | indent=spaces=4 28 | keep-one-line-statements 29 | min-conditional-indent=0 30 | pad-comma 31 | pad-header 32 | pad-include 33 | pad-oper # Bug with this for nested templates https://gitlab.com/saalen/astyle/-/issues/69 34 | squeeze-lines=1 35 | squeeze-ws 36 | unpad-paren 37 | -------------------------------------------------------------------------------- /src/QuantalAudio.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | 3 | Plugin* pluginInstance; 4 | 5 | void init(Plugin *p) { 6 | pluginInstance = p; 7 | 8 | // Add all Models defined throughout the pluginInstance 9 | p->addModel(modelMasterMixer); 10 | p->addModel(modelBufferedMult); 11 | p->addModel(modelUnityMix); 12 | p->addModel(modelDaisyBlank); 13 | p->addModel(modelDaisyChannel); 14 | p->addModel(modelDaisyChannel2); 15 | p->addModel(modelDaisyChannelSends2); 16 | p->addModel(modelDaisyChannelVu); 17 | p->addModel(modelDaisyMaster); 18 | p->addModel(modelDaisyMaster2); 19 | p->addModel(modelHorsehair); 20 | p->addModel(modelBlank1); 21 | p->addModel(modelBlank3); 22 | p->addModel(modelBlank5); 23 | 24 | // Any other pluginInstance initialization may go here. 25 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 26 | } 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above 2 | RACK_DIR ?= ../.. 3 | 4 | # FLAGS will be passed to both the C and C++ compiler 5 | FLAGS += 6 | CFLAGS += 7 | CXXFLAGS += 8 | 9 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 10 | # Static libraries are fine. 11 | LDFLAGS += 12 | 13 | # Add .cpp and .c files to the build 14 | SOURCES += $(wildcard src/*.cpp) 15 | 16 | # Add files to the ZIP package when running `make dist` 17 | # The compiled plugin is automatically added. 18 | DISTRIBUTABLES += $(wildcard LICENSE*) $(wildcard res/*.svg) res/fonts 19 | 20 | # Include the VCV Rack plugin Makefile framework 21 | include $(RACK_DIR)/plugin.mk 22 | 23 | # Run `make dist` to prepare the distributed files 24 | # Run `make install` to install to local environment 25 | 26 | # To run the Makefile in the `res` directory: to generate the optimized 27 | # versions of module panel SVGs 28 | images: 29 | $(MAKE) -C res 30 | 31 | # Run to lint and apply defined codestyle fixes 32 | lint: 33 | astyle --suffix=none --options=.astylerc -r 'src/*' 34 | -------------------------------------------------------------------------------- /res/src/DaisyChannelVu.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /res/src/DaisyChannelVu-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /res/Makefile: -------------------------------------------------------------------------------- 1 | # Make file to generate the 'optimized' versions of the panel SVGs from the sources 2 | # 3 | # This is required to embed font paths into the final SVG files 4 | # Using svgo will optimize the SVG for filesize and performance 5 | # Inkscape is used to export the font information to paths 6 | # Install svgo with 'npm -g install svgo' 7 | 8 | # Notes: 9 | # $(wildcard *.svg) should be used instead of just *.svg 10 | # $@ outputs the target name 11 | # $? outputs all prereqs newer than the target 12 | # $^ outputs all prereqs 13 | # $< outputs the first prereq 14 | 15 | INKSCAPE=inkscape 16 | SVGO=svgo 17 | SVGS=BufferedMult-dark.svg BufferedMult.svg DaisyBlank-dark.svg DaisyBlank.svg DaisyChannel-dark.svg DaisyChannel.svg DaisyChannel2-dark.svg DaisyChannel2.svg DaisyChannelSends2-dark.svg DaisyChannelSends2.svg DaisyChannelVu-dark.svg DaisyChannelVu.svg DaisyMaster-dark.svg DaisyMaster.svg DaisyMaster2-dark.svg DaisyMaster2.svg Horsehair-dark.svg Horsehair.svg MasterMixer-dark.svg MasterMixer.svg UnityMix-dark.svg UnityMix.svg 18 | 19 | all: $(SVGS) 20 | 21 | # This is an example of building an svg file without the make vars so you can see a 'real example': 22 | # MasterMixer.svg: src/MasterMixer.src.svg 23 | # $(INKSCAPE) src/MasterMixer.src.svg --export-plain-svg --export-type=svg --export-filename=MasterMixer-textpaths.svg --export-text-to-path 24 | # $(SVGO) -i MasterMixer-textpaths.svg -o MasterMixer.svg 25 | # rm MasterMixer-textpaths.svg 26 | 27 | $(SVGS): %.svg: src/%.src.svg 28 | @echo "$(basename $@) $<" 29 | $(INKSCAPE) $< --export-plain-svg --export-type=svg --export-filename=$(basename $@)-textpaths.svg --export-text-to-path 30 | $(SVGO) -i $(basename $@)-textpaths.svg -o $@ 31 | rm $(basename $@)-textpaths.svg 32 | 33 | src/%.src.svg: 34 | touch $@ 35 | -------------------------------------------------------------------------------- /res/src/DaisyChannelSends2.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | AUX 20 | 21 | 22 | GROUP 23 | 1 24 | 2 25 | 26 | 27 | OUT 28 | DAISY 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /res/src/DaisyChannelSends2-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | AUX 20 | 21 | 22 | GROUP 23 | 1 24 | 2 25 | 26 | 27 | OUT 28 | DAISY 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /res/src/DaisyChannel2.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | DC2 23 | 24 | 25 | IN 26 | CV 27 | PAN 28 | 29 | 30 | OUT 31 | DAISY 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /res/src/DaisyChannel2-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | DC2 23 | 24 | 25 | IN 26 | CV 27 | PAN 28 | 29 | 30 | OUT 31 | DAISY 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /res/src/BufferedMult.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | MULT 25 | 26 | 27 | IN A 28 | IN B 29 | 30 | 31 | OUT 32 | OUT 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /res/src/BufferedMult-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | MULT 25 | 26 | 27 | IN A 28 | IN B 29 | 30 | 31 | OUT 32 | OUT 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /res/src/DaisyChannel.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | DCH 25 | 26 | 27 | IN 28 | CV 29 | 30 | 31 | OUT 32 | DAISY 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /res/src/DaisyChannel-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | DCH 25 | 26 | 27 | IN 28 | CV 29 | 30 | 31 | OUT 32 | DAISY 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /res/src/DaisyMaster2.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | D-MX2 28 | 29 | 30 | MASTER 31 | CV 32 | 33 | 34 | OUT 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /res/src/DaisyMaster2-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | D-MX2 28 | 29 | 30 | MASTER 31 | CV 32 | 33 | 34 | OUT 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Quantal Audio VCV Rack Modules | Changelog 2 | 3 | ## 2.2.2 (2025-02-14) 4 | 5 | - Fix bug with smooth level CV to support 16-channel polyphony 6 | 7 | ## 2.2.1 (2025-02-13) 8 | 9 | - Add ability to solo a Daisy channel by long-holding the mute button 10 | - Add keyboard shortcuts 'm' and 's' to mute and solo Daisy channel 11 | - Add context menu option to Daisy channel to make 'Direct outs pre-mute' 12 | - Add context menu option to Daisy channel, Daisy master, Mixer-2 to make 13 | 'Smooth level CV' with 6ms slew 14 | - Slight optimization in VU meter light calculation 15 | - Add channel strip labels to Daisy channel strips: will be labeled 1-16 when connected 16 | 17 | ## 2.2.0 (2025-02-02) 18 | 19 | - Add support for dark theme versions of all modules 20 | - Add context menu commands in Daisy master to spawn channel strips 21 | - New module: Daisy blank, a chainable module within Daisy mixer modules 22 | - Update Daisy aux module to support grouping of signals in groups 1 or 2. 23 | Updates mechanism to send to aux groups using new context menu sliders in 24 | Daisy channel strip module: can send this channel to group 1 or 2 aux. 25 | - Deprecate original Daisy mixer modules (mono v1 with wired approach) 26 | 27 | ## 2.1.3 (2023-03-06) 28 | 29 | - Fix bug with noise floor present in Daisy chainged signals; was caused by 30 | expander messages not being thread-safe. 31 | 32 | ## 2.1.2 (2023-03-03) 33 | 34 | - New module: Daisy VU meter. As an expander module to Daisy channel, Daisy 35 | aux and Daisy master modules. 36 | 37 | ## 2.1.0 (2023-03-01) 38 | 39 | - New module: Daisy aux. For collecting signals from previous Daisy channels 40 | and sending to an 'outboard' effect. Can return signal to another Daisy 41 | channel in mixer chain. 42 | - Add panning parameter knob to Daisy channel module 43 | - New modules: Daisy channel (stereo), Daisy mixer; uses expander technology 44 | to daisy chain mixer signals. 45 | 46 | ## 2.0.1 (2021-11-20) 47 | 48 | - Add polyphony support and improve Horsehair performance 49 | - Update license 50 | 51 | ## 2.0.0 (2021-10-23) 52 | 53 | - Upgrade to Rack 2.0 54 | 55 | ## 1.0.0 (2019-06-29) 56 | 57 | - Upgrade to Rack 1.0 58 | 59 | ## 0.6.4 (2019-01-01) 60 | 61 | - New module: Horsehair VCO 62 | - Fix bugs in Unity mix 63 | 64 | ## 0.6.3 (2018-12-19) 65 | 66 | - New modules: Daisy channel (mono v1) and Daisy master (v1) 67 | 68 | ## 0.6.1 (2018-11-27) 69 | 70 | - New module: Buffered multiple and Unity mix 71 | - Standardize module naming conventions 72 | 73 | ## 0.6.0 (2018-11-24) 74 | 75 | - New modules: Blank1, Blank3, Blank5, Mixer-2 76 | -------------------------------------------------------------------------------- /res/src/DaisyMaster.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | DSY-MX 29 | 30 | 31 | MASTER 32 | CV 33 | 34 | 35 | OUT 36 | DAISY 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /res/src/UnityMix.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | MIX 27 | 28 | 29 | IN A 30 | IN B 31 | 32 | 33 | OUT 34 | OUT 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /res/src/UnityMix-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | MIX 27 | 28 | 29 | IN A 30 | IN B 31 | 32 | 33 | OUT 34 | OUT 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /res/src/DaisyMaster-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | DSY-MX 29 | 30 | 31 | MASTER 32 | CV 33 | 34 | 35 | OUT 36 | DAISY 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Daisy.hpp: -------------------------------------------------------------------------------- 1 | #if !defined(DAISY_CONSTANTS_H) 2 | #define DAISY_CONSTANTS_H 1 3 | 4 | #include "QuantalAudio.hpp" 5 | 6 | // Hypothetically the max number of channels that could be chained 7 | constexpr float DAISY_DIVISOR = 16.f; 8 | 9 | // How frequently the light draw step is processed 10 | constexpr int DAISY_LIGHT_DIVISION = 512; 11 | 12 | // How frequently the UI step is processed 13 | constexpr int DAISY_UI_DIVISION = 128; 14 | 15 | /** 16 | * Object to hold stereo polyphonic voltages 17 | */ 18 | struct StereoVoltages { 19 | int channels = 0; 20 | float voltages_l[16] = {}; 21 | float voltages_r[16] = {}; 22 | 23 | /** 24 | * Copies an array of size at least `channels` to this obj's voltages. 25 | * Remember to set the number of channels *before* calling this method. 26 | */ 27 | void writeVoltages(const float* v_l, const float* v_r) { 28 | for (int c = 0; c < channels; c++) { 29 | voltages_l[c] = v_l[c]; 30 | voltages_r[c] = v_r[c]; 31 | } 32 | } 33 | 34 | void writeVoltages(const StereoVoltages sv, int channels) { 35 | this->channels = channels; 36 | for (int c = 0; c < channels; c++) { 37 | voltages_l[c] = sv.voltages_l[c]; 38 | voltages_r[c] = sv.voltages_r[c]; 39 | } 40 | } 41 | 42 | /** 43 | * Copies this objs voltages to an array of size at least `channels` 44 | */ 45 | void sendVoltages(float* v_l, float* v_r) { 46 | for (int c = 0; c < channels; c++) { 47 | v_l[c] = voltages_l[c]; 48 | v_r[c] = voltages_r[c]; 49 | } 50 | } 51 | }; 52 | 53 | struct DaisyMessage { 54 | // Daisy-chained mix signal 55 | StereoVoltages signals = {}; 56 | 57 | // Single module's signal 58 | StereoVoltages singleSignals = {}; 59 | 60 | // Aux 1 send signal 61 | StereoVoltages aux1Signals = {}; 62 | 63 | // Aux 2 send signal 64 | StereoVoltages aux2Signals = {}; 65 | 66 | // Solo signals 67 | StereoVoltages soloSignals = {}; 68 | 69 | // Meta data about this daisy chain 70 | int channel_strip_id = 1; 71 | float first_pos_x = 0.0f; 72 | float first_pos_y = 0.0f; 73 | }; 74 | 75 | struct SimpleSlewer { 76 | float value = 0.f; 77 | 78 | void setSlewSpeed(const float speed) { 79 | float sampleRate = APP->engine->getSampleRate(); 80 | delta = 1.f / (sampleRate * 0.001f * speed); 81 | } 82 | 83 | float process(float new_value) { 84 | if (new_value == value) { 85 | return value; 86 | } 87 | 88 | value += math::clamp(new_value - value, -delta, delta); 89 | return value; 90 | } 91 | 92 | private: 93 | 94 | float delta = 0.0005f; 95 | }; 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/BufferedMult.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | 3 | struct BufferedMult : Module { 4 | enum ParamIds { 5 | CONNECT_PARAM, 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | ENUMS(CH_INPUT, 2), 10 | NUM_INPUTS 11 | }; 12 | enum OutputIds { 13 | ENUMS(CH_OUTPUT, 6), 14 | NUM_OUTPUTS 15 | }; 16 | enum LightsIds { 17 | NUM_LIGHTS 18 | }; 19 | 20 | BufferedMult() { 21 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 22 | configSwitch(CONNECT_PARAM, 0.0f, 1.0f, 1.0f, "connect mode", {"Group All (2:6)", "Groups A, B (1:3 x 2)"}); 23 | 24 | configInput(CH_INPUT + 0, "A"); 25 | configInput(CH_INPUT + 1, "B"); 26 | 27 | configOutput(CH_OUTPUT + 0, "A1"); 28 | configOutput(CH_OUTPUT + 1, "A2"); 29 | configOutput(CH_OUTPUT + 2, "A3"); 30 | configOutput(CH_OUTPUT + 3, "B1"); 31 | configOutput(CH_OUTPUT + 4, "B2"); 32 | configOutput(CH_OUTPUT + 5, "B3"); 33 | } 34 | 35 | void process(const ProcessArgs &args) override { 36 | bool unconnect = (params[CONNECT_PARAM].getValue() > 0.0f); 37 | 38 | // Input 0 -> Outputs 0 1 2 39 | float signals[16] = {}; 40 | inputs[CH_INPUT + 0].readVoltages(signals); 41 | int channels = inputs[CH_INPUT + 0].getChannels(); 42 | for (int i = 0; i <= 2; i++) { 43 | outputs[CH_OUTPUT + i].setChannels(channels); 44 | outputs[CH_OUTPUT + i].writeVoltages(signals); 45 | } 46 | 47 | // Input 1 -> Outputs 3 4 5 48 | // otherwise Outputs 3 4 5 is copy of Input 0 49 | if (unconnect) { 50 | inputs[CH_INPUT + 1].readVoltages(signals); 51 | channels = inputs[CH_INPUT + 1].getChannels(); 52 | } 53 | 54 | for (int i = 3; i <= 5; i++) { 55 | outputs[CH_OUTPUT + i].setChannels(channels); 56 | outputs[CH_OUTPUT + i].writeVoltages(signals); 57 | } 58 | } 59 | }; 60 | 61 | struct BufferedMultWidget : ModuleWidget { 62 | explicit BufferedMultWidget(BufferedMult *module) { 63 | setModule(module); 64 | setPanel( 65 | createPanel( 66 | asset::plugin(pluginInstance, "res/BufferedMult.svg"), 67 | asset::plugin(pluginInstance, "res/BufferedMult-dark.svg") 68 | ) 69 | ); 70 | 71 | // Screws 72 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 73 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 74 | 75 | // Connect switch 76 | addParam(createParam(Vec(RACK_GRID_WIDTH - 7.0f, 182.0), module, BufferedMult::CONNECT_PARAM)); 77 | 78 | // Group A 79 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 50.0), module, BufferedMult::CH_INPUT + 0)); 80 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 92.0), module, BufferedMult::CH_OUTPUT + 0)); 81 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 120.0), module, BufferedMult::CH_OUTPUT + 1)); 82 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 148.0), module, BufferedMult::CH_OUTPUT + 2)); 83 | 84 | // Group B 85 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 222.0), module, BufferedMult::CH_INPUT + 1)); 86 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 264.0), module, BufferedMult::CH_OUTPUT + 3)); 87 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 292.0), module, BufferedMult::CH_OUTPUT + 4)); 88 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 320.0), module, BufferedMult::CH_OUTPUT + 5)); 89 | } 90 | }; 91 | 92 | Model* modelBufferedMult = createModel("BufferedMult"); 93 | -------------------------------------------------------------------------------- /res/src/MasterMixer.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | MIXER-2 35 | 36 | 37 | LEVEL 38 | 1 39 | 2 40 | 41 | 42 | CV 43 | MONO 44 | ST 45 | IN 1 46 | IN 2 47 | 48 | 49 | CH 1 50 | CH 2 51 | MX 1 52 | MX 2 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /res/src/MasterMixer-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | MIXER-2 35 | 36 | 37 | LEVEL 38 | 1 39 | 2 40 | 41 | 42 | CV 43 | MONO 44 | ST 45 | IN 1 46 | IN 2 47 | 48 | 49 | CH 1 50 | CH 2 51 | MX 1 52 | MX 2 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /res/BufferedMult.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/fonts/EnvyCodeR-LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2023, Damien Guard https://damieng.com, with Reserved Font Name "Envy Code R". 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | # SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 8 | 9 | ## PREAMBLE 10 | 11 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 12 | 13 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 14 | 15 | ## DEFINITIONS 16 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 17 | 18 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 19 | 20 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 21 | 22 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 23 | 24 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 25 | 26 | ## PERMISSION & CONDITIONS 27 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 28 | 29 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 30 | 31 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 32 | 33 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 34 | 35 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 36 | 37 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 38 | 39 | ## TERMINATION 40 | This license becomes null and void if any of the above conditions are not met. 41 | 42 | ## DISCLAIMER 43 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 44 | -------------------------------------------------------------------------------- /res/BufferedMult-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/UnityMix.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/UnityMix-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/DaisyMaster.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | #include "Daisy.hpp" 3 | 4 | struct DaisyMaster : Module { 5 | enum ParamIds { 6 | MIX_LVL_PARAM, 7 | MUTE_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | MIX_CV_INPUT, 12 | CHAIN_INPUT, 13 | NUM_INPUTS 14 | }; 15 | enum OutputIds { 16 | MIX_OUTPUT, 17 | NUM_OUTPUTS 18 | }; 19 | enum LightsIds { 20 | MUTE_LIGHT, 21 | NUM_LIGHTS 22 | }; 23 | 24 | bool muted = false; 25 | dsp::SchmittTrigger muteTrigger; 26 | 27 | DaisyMaster() { 28 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 29 | configParam(MIX_LVL_PARAM, 0.0f, 2.0f, 1.0f, "Mix level", " dB", -10, 20); 30 | configButton(MUTE_PARAM, "Mute"); 31 | 32 | configInput(MIX_CV_INPUT, "Level CV"); 33 | configInput(CHAIN_INPUT, "Daisy Channel"); 34 | configOutput(MIX_OUTPUT, "Mix"); 35 | } 36 | 37 | json_t* dataToJson() override { 38 | json_t* rootJ = json_object(); 39 | 40 | // mute 41 | json_object_set_new(rootJ, "muted", json_boolean(muted)); 42 | 43 | return rootJ; 44 | } 45 | 46 | void dataFromJson(json_t* rootJ) override { 47 | // mute 48 | json_t* mutedJ = json_object_get(rootJ, "muted"); 49 | if (mutedJ) { 50 | muted = json_is_true(mutedJ); 51 | } 52 | } 53 | 54 | void process(const ProcessArgs &args) override { 55 | if (muteTrigger.process(params[MUTE_PARAM].getValue())) { 56 | muted = !muted; 57 | } 58 | 59 | float mix[16] = {}; 60 | int channels = 1; 61 | 62 | if (!muted) { 63 | float gain = params[MIX_LVL_PARAM].getValue(); 64 | 65 | channels = inputs[CHAIN_INPUT].getChannels(); 66 | 67 | // Bring the voltage back up from the chained low voltage 68 | inputs[CHAIN_INPUT].readVoltages(mix); 69 | for (int c = 0; c < channels; c++) { 70 | mix[c] = clamp(mix[c] * DAISY_DIVISOR, -12.f, 12.f) * gain; 71 | } 72 | 73 | // mix = clamp(inputs[CHAIN_INPUT].getVoltage() * DAISY_DIVISOR, -12.f, 12.f); 74 | 75 | if (inputs[MIX_CV_INPUT].isConnected()) { 76 | for (int c = 0; c < channels; c++) { 77 | const float mix_cv = clamp(inputs[MIX_CV_INPUT].getPolyVoltage(c) / 10.f, 0.f, 1.f); 78 | mix[c] *= mix_cv; 79 | } 80 | } 81 | } 82 | 83 | outputs[MIX_OUTPUT].setChannels(channels); 84 | outputs[MIX_OUTPUT].writeVoltages(mix); 85 | lights[MUTE_LIGHT].value = (muted); 86 | } 87 | }; 88 | 89 | struct DaisyMasterWidget : ModuleWidget { 90 | explicit DaisyMasterWidget(DaisyMaster *module) { 91 | setModule(module); 92 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DaisyMaster.svg"))); 93 | setPanel( 94 | createPanel( 95 | asset::plugin(pluginInstance, "res/DaisyMaster.svg"), 96 | asset::plugin(pluginInstance, "res/DaisyMaster-dark.svg") 97 | ) 98 | ); 99 | 100 | // Screws 101 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 102 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 103 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 104 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 105 | 106 | // Level & CV 107 | addParam(createParam(Vec(RACK_GRID_WIDTH * 1.5f - (36.0f / 2), 52.0), module, DaisyMaster::MIX_LVL_PARAM)); 108 | addInput(createInput(Vec(RACK_GRID_WIDTH * 1.5f - (25.0f / 2), 96.0), module, DaisyMaster::MIX_CV_INPUT)); 109 | 110 | // Mute 111 | addParam(createParam(Vec(RACK_GRID_WIDTH * 1.5f - 9.0f, 206.0), module, DaisyMaster::MUTE_PARAM)); 112 | addChild(createLight>(Vec(RACK_GRID_WIDTH * 1.5f - 4.5f, 210.25f), module, DaisyMaster::MUTE_LIGHT)); 113 | 114 | // Mix output 115 | addOutput(createOutput(Vec((RACK_GRID_WIDTH * 1.5f) - (25.0f / 2), 245.0), module, DaisyMaster::MIX_OUTPUT)); 116 | 117 | // Chain input 118 | addInput(createInput(Vec((RACK_GRID_WIDTH * 1.5f) - (25.0f / 2), 290.5), module, DaisyMaster::CHAIN_INPUT)); 119 | } 120 | }; 121 | 122 | Model* modelDaisyMaster = createModel("DaisyMaster"); 123 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "QuantalAudio", 3 | "name": "QuantalAudio", 4 | "version": "2.2.2", 5 | "license": "GPL-3.0-or-later", 6 | "brand": "QuantalAudio", 7 | "author": "Jansen Price", 8 | "authorEmail": "jansen.price@gmail.com", 9 | "authorUrl": "https://jansenprice.com", 10 | "pluginUrl": "https://github.com/sumpygump/quantal-audio", 11 | "manualUrl": "https://github.com/sumpygump/quantal-audio/blob/master/README.md", 12 | "sourceUrl": "https://github.com/sumpygump/quantal-audio", 13 | "changelogUrl": "https://github.com/sumpygump/quantal-audio/blob/master/changelog.md", 14 | "donateUrl": "", 15 | "modules": [ 16 | { 17 | "slug": "Blank1", 18 | "name": "Blank Panel | 1HP", 19 | "description": "Blank Panel | 1HP", 20 | "tags": ["Blank"] 21 | }, 22 | { 23 | "slug": "Blank3", 24 | "name": "Blank Panel | 3HP", 25 | "description": "Blank Panel | 3HP", 26 | "tags": ["Blank"] 27 | }, 28 | { 29 | "slug": "Blank5", 30 | "name": "Blank Panel | 5HP", 31 | "description": "Blank Panel | 5HP", 32 | "tags": ["Blank"] 33 | }, 34 | { 35 | "slug": "BufferedMult", 36 | "name": "Buffered Mult | 2HP", 37 | "description": "Buffered multiple 1->3 or 1->6", 38 | "tags": ["Multiple", "Polyphonic"], 39 | "manualUrl": "https://github.com/sumpygump/quantal-audio/tree/master?tab=readme-ov-file#mult--buffered-multiple--2hp" 40 | }, 41 | { 42 | "slug": "DaisyChannel2", 43 | "name": "Daisy Mix Channel STEREO | 2HP", 44 | "description": "Modular mixer channel - proximity daisy chainable", 45 | "tags": ["Mixer", "VCA", "Polyphonic", "Expander"], 46 | "manualUrl": "https://github.com/sumpygump/quantal-audio/tree/master?tab=readme-ov-file#dc2--daisy-mix-channel-stereo--2hp" 47 | }, 48 | { 49 | "slug": "DaisyChannelVu", 50 | "name": "Daisy Mix Channel VU | 1HP", 51 | "description": "Modular mixer VU - proximity daisy chainable", 52 | "tags": ["Mixer", "Polyphonic", "Visual", "Expander"], 53 | "manualUrl": "https://github.com/sumpygump/quantal-audio/tree/master?tab=readme-ov-file#vu--daisy-mix-channel-vu-meter--1hp" 54 | }, 55 | { 56 | "slug": "DaisyChannelSends2", 57 | "name": "Daisy Mix Channel Aux Sends STEREO | 2HP", 58 | "description": "Modular mixer channel aux sends - proximity daisy chainable", 59 | "tags": ["Mixer", "Polyphonic", "Expander"], 60 | "manualUrl": "https://github.com/sumpygump/quantal-audio/tree/master?tab=readme-ov-file#aux--daisy-mix-channel-aux-sends--2hp" 61 | }, 62 | { 63 | "slug": "DaisyBlank", 64 | "name": "Daisy Mix Blank Separator | 2HP", 65 | "description": "Modular mixer blank separator - proximity daisy chainable", 66 | "tags": ["Blank", "Expander"], 67 | "manualUrl": "https://github.com/sumpygump/quantal-audio/tree/master?tab=readme-ov-file#daisy-mix-blank-separator--2hp" 68 | }, 69 | { 70 | "slug": "DaisyMaster2", 71 | "name": "Daisy Mix Master STEREO | 3HP", 72 | "description": "Modular mixer master - proximity daisy chain", 73 | "tags": ["Mixer", "Polyphonic", "Expander"], 74 | "manualUrl": "https://github.com/sumpygump/quantal-audio/tree/master?tab=readme-ov-file#d-mx2--daisy-mix-master-stereo--3hp" 75 | }, 76 | { 77 | "slug": "Horsehair", 78 | "name": "Horsehair VCO | 7HP", 79 | "description": "2x VCO saw to pulsewave", 80 | "tags": ["Oscillator", "Polyphonic"], 81 | "manualUrl": "https://github.com/sumpygump/quantal-audio?tab=readme-ov-file#horsehair-vco--7hp" 82 | }, 83 | { 84 | "slug": "Mixer2", 85 | "name": "Mixer 2 | Mono->Stereo | 5HP", 86 | "description": "Mono or stereo 2 channel mixer", 87 | "tags": ["Mixer", "Polyphonic"], 88 | "manualUrl": "https://github.com/sumpygump/quantal-audio?tab=readme-ov-file#mixer-2--master-mixer--5hp" 89 | }, 90 | { 91 | "slug": "UnityMix", 92 | "name": "Unity Mix | 2HP", 93 | "description": "Multiple 3->1 or 6->1", 94 | "tags": ["Multiple", "Polyphonic"], 95 | "manualUrl": "https://github.com/sumpygump/quantal-audio?tab=readme-ov-file#mix--unity-mix--2hp" 96 | }, 97 | { 98 | "slug": "DaisyChannel", 99 | "name": "Daisy Mix Channel MONO | 2HP", 100 | "description": "Modular mixer channel - legacy daisy chainable", 101 | "tags": ["Mixer", "Polyphonic"], 102 | "hidden": true 103 | }, 104 | { 105 | "slug": "DaisyMaster", 106 | "name": "Daisy Mix Master MONO | 3HP", 107 | "description": "Modular mixer master - legacy", 108 | "tags": ["Mixer", "Polyphonic"], 109 | "hidden": true 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /res/DaisyChannel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyMaster2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/DaisyChannel.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | #include "Daisy.hpp" 3 | 4 | struct DaisyChannel : Module { 5 | enum ParamIds { 6 | CH_LVL_PARAM, 7 | MUTE_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | CH_INPUT, 12 | LVL_CV_INPUT, 13 | CHAIN_INPUT, 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | CH_OUTPUT, 18 | CHAIN_OUTPUT, 19 | NUM_OUTPUTS 20 | }; 21 | enum LightsIds { 22 | MUTE_LIGHT, 23 | NUM_LIGHTS 24 | }; 25 | 26 | bool muted = false; 27 | dsp::SchmittTrigger muteTrigger; 28 | 29 | DaisyChannel() { 30 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 31 | configParam(CH_LVL_PARAM, 0.0f, 1.0f, 1.0f, "Channel level", " dB", -10, 20); 32 | configButton(MUTE_PARAM, "Mute"); 33 | 34 | configInput(CH_INPUT, "Channel"); 35 | configInput(LVL_CV_INPUT, "Level CV"); 36 | configInput(CHAIN_INPUT, "Daisy chain"); 37 | 38 | configOutput(CH_OUTPUT, "Channel"); 39 | configOutput(CHAIN_OUTPUT, "Daisy chain"); 40 | } 41 | 42 | json_t* dataToJson() override { 43 | json_t* rootJ = json_object(); 44 | 45 | // mute 46 | json_object_set_new(rootJ, "muted", json_boolean(muted)); 47 | 48 | return rootJ; 49 | } 50 | 51 | void dataFromJson(json_t* rootJ) override { 52 | // mute 53 | const json_t* mutedJ = json_object_get(rootJ, "muted"); 54 | if (mutedJ) { 55 | muted = json_is_true(mutedJ); 56 | } 57 | } 58 | 59 | void process(const ProcessArgs &args) override { 60 | if (muteTrigger.process(params[MUTE_PARAM].getValue())) { 61 | muted = !muted; 62 | } 63 | 64 | float signals[16] = {}; 65 | int channels = 1; 66 | if (!muted) { 67 | channels = inputs[CH_INPUT].getChannels(); 68 | inputs[CH_INPUT].readVoltages(signals); 69 | const float gain = params[CH_LVL_PARAM].getValue(); 70 | for (int c = 0; c < channels; c++) { 71 | signals[c] *= std::pow(gain, 2.f); 72 | } 73 | 74 | if (inputs[LVL_CV_INPUT].isConnected()) { 75 | for (int c = 0; c < channels; c++) { 76 | const float _cv = clamp(inputs[LVL_CV_INPUT].getPolyVoltage(c) / 10.f, 0.f, 1.f); 77 | signals[c] *= _cv; 78 | } 79 | } 80 | } 81 | 82 | outputs[CH_OUTPUT].setChannels(channels); 83 | outputs[CH_OUTPUT].writeVoltages(signals); 84 | 85 | float daisySignals[16] = {}; 86 | const int maxChannels = std::max(inputs[CHAIN_INPUT].getChannels(), channels); 87 | 88 | // Make the voltage small to the chain by dividing by the divisor; 89 | inputs[CHAIN_INPUT].readVoltages(daisySignals); 90 | for (int c = 0; c < maxChannels; c++) { 91 | daisySignals[c] += signals[c] / DAISY_DIVISOR; 92 | } 93 | 94 | outputs[CHAIN_OUTPUT].setChannels(maxChannels); 95 | outputs[CHAIN_OUTPUT].writeVoltages(daisySignals); 96 | 97 | lights[MUTE_LIGHT].value = (muted); 98 | } 99 | }; 100 | 101 | struct DaisyChannelWidget : ModuleWidget { 102 | explicit DaisyChannelWidget(DaisyChannel *module) { 103 | setModule(module); 104 | setPanel( 105 | createPanel( 106 | asset::plugin(pluginInstance, "res/DaisyChannel.svg"), 107 | asset::plugin(pluginInstance, "res/DaisyChannel-dark.svg") 108 | ) 109 | ); 110 | 111 | // Screws 112 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 113 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 114 | 115 | // Channel Input/Output 116 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 50.0), module, DaisyChannel::CH_INPUT)); 117 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 245.0), module, DaisyChannel::CH_OUTPUT)); 118 | 119 | // Level & CV 120 | addParam(createParam(Vec(RACK_GRID_WIDTH - 10.5f, 121.4), module, DaisyChannel::CH_LVL_PARAM)); 121 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 89.0), module, DaisyChannel::LVL_CV_INPUT)); 122 | 123 | // Mute 124 | addParam(createParam(Vec(RACK_GRID_WIDTH - 9.0f, 206.0), module, DaisyChannel::MUTE_PARAM)); 125 | addChild(createLight>(Vec(RACK_GRID_WIDTH - 4.5f, 210.25f), module, DaisyChannel::MUTE_LIGHT)); 126 | 127 | // Chain Input/Output 128 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 290.5), module, DaisyChannel::CHAIN_INPUT)); 129 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 320.0), module, DaisyChannel::CHAIN_OUTPUT)); 130 | } 131 | }; 132 | 133 | Model* modelDaisyChannel = createModel("DaisyChannel"); 134 | -------------------------------------------------------------------------------- /src/UnityMix.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | 3 | struct polysignal { 4 | float signals[16] = {}; 5 | int channels = 1; 6 | }; 7 | 8 | struct UnityMix : Module { 9 | enum ParamIds { 10 | CONNECT_PARAM, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | ENUMS(CH_INPUT, 6), 15 | NUM_INPUTS 16 | }; 17 | enum OutputIds { 18 | ENUMS(CH_OUTPUT, 2), 19 | NUM_OUTPUTS 20 | }; 21 | enum LightsIds { 22 | NUM_LIGHTS 23 | }; 24 | 25 | UnityMix() { 26 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 27 | configSwitch(CONNECT_PARAM, 0.0f, 1.0f, 1.0f, "Connect mode", {"Group All (6:1)", "Groups A, B (3:1 x 2)"}); 28 | 29 | configInput(CH_INPUT + 0, "A1"); 30 | configInput(CH_INPUT + 1, "A2"); 31 | configInput(CH_INPUT + 2, "A3"); 32 | configInput(CH_INPUT + 3, "B1"); 33 | configInput(CH_INPUT + 4, "B2"); 34 | configInput(CH_INPUT + 5, "B3"); 35 | 36 | configOutput(CH_OUTPUT + 0, "Group A"); 37 | configOutput(CH_OUTPUT + 1, "Group B"); 38 | } 39 | 40 | polysignal mixChannels(int in_start, int in_end) { 41 | float in[16] = {}; 42 | struct polysignal mix; 43 | float channels = 0.f; 44 | int maxChannels = 1; 45 | 46 | for (int i = in_start; i <= in_end; i++) { 47 | if (inputs[CH_INPUT + i].isConnected()) { 48 | mix.channels = inputs[CH_INPUT + i].getChannels(); 49 | maxChannels = std::max(mix.channels, maxChannels); 50 | inputs[CH_INPUT + i].readVoltages(in); 51 | 52 | for (int c = 0; c < mix.channels; c++) { 53 | mix.signals[c] += in[c]; 54 | } 55 | 56 | // Keep track of number of plugs so we can average 57 | channels++; 58 | } 59 | } 60 | 61 | if (channels > 0.f) { 62 | mix.channels = maxChannels; 63 | for (int c = 0; c < maxChannels; c++) { 64 | mix.signals[c] = mix.signals[c] / channels; 65 | } 66 | } 67 | 68 | return mix; 69 | } 70 | 71 | void process(const ProcessArgs &args) override { 72 | bool unconnect = (params[CONNECT_PARAM].getValue() > 0.0f); 73 | 74 | if (unconnect) { 75 | // Group A : Inputs 0 1 2 -> Output 0 76 | const polysignal mix1 = mixChannels(0, 2); 77 | outputs[CH_OUTPUT + 0].setChannels(mix1.channels); 78 | outputs[CH_OUTPUT + 0].writeVoltages(mix1.signals); 79 | // Group B : Inputs 3 4 5 -> Output 1 80 | const polysignal mix2 = mixChannels(3, 5); 81 | outputs[CH_OUTPUT + 1].setChannels(mix2.channels); 82 | outputs[CH_OUTPUT + 1].writeVoltages(mix2.signals); 83 | } else { 84 | // Combined : Inputs 0-5 -> Output 1 & 2 85 | const polysignal mix = mixChannels(0, 5); 86 | outputs[CH_OUTPUT + 0].setChannels(mix.channels); 87 | outputs[CH_OUTPUT + 0].writeVoltages(mix.signals); 88 | outputs[CH_OUTPUT + 1].setChannels(mix.channels); 89 | outputs[CH_OUTPUT + 1].writeVoltages(mix.signals); 90 | } 91 | } 92 | }; 93 | 94 | struct UnityMixWidget : ModuleWidget { 95 | explicit UnityMixWidget(UnityMix *module) { 96 | setModule(module); 97 | setPanel( 98 | createPanel( 99 | asset::plugin(pluginInstance, "res/UnityMix.svg"), 100 | asset::plugin(pluginInstance, "res/UnityMix-dark.svg") 101 | ) 102 | ); 103 | 104 | // Screws 105 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 106 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 107 | 108 | // Connect switch 109 | addParam(createParam(Vec(RACK_GRID_WIDTH - 7.0f, 182.0), module, UnityMix::CONNECT_PARAM)); 110 | 111 | // Group A 112 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 50.0), module, UnityMix::CH_INPUT + 0)); 113 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 78.0), module, UnityMix::CH_INPUT + 1)); 114 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 106.0), module, UnityMix::CH_INPUT + 2)); 115 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 148.0), module, UnityMix::CH_OUTPUT + 0)); 116 | 117 | // Group B 118 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 222.0), module, UnityMix::CH_INPUT + 3)); 119 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 250.0), module, UnityMix::CH_INPUT + 4)); 120 | addInput(createInput(Vec(RACK_GRID_WIDTH - 12.5f, 278.0), module, UnityMix::CH_INPUT + 5)); 121 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 320.0), module, UnityMix::CH_OUTPUT + 1)); 122 | } 123 | }; 124 | 125 | Model* modelUnityMix = createModel("UnityMix"); 126 | -------------------------------------------------------------------------------- /res/DaisyChannel-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyMaster2-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyChannel2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/DaisyBlank.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | #include "Daisy.hpp" 3 | 4 | struct DaisyBlank : Module { 5 | enum ParamIds { 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | NUM_INPUTS 10 | }; 11 | enum OutputIds { 12 | NUM_OUTPUTS 13 | }; 14 | enum LightsIds { 15 | LINK_LIGHT_L, 16 | LINK_LIGHT_R, 17 | NUM_LIGHTS 18 | }; 19 | 20 | float link_l = 0.f; 21 | float link_r = 0.f; 22 | int channelStripId = 1; 23 | 24 | Vec widgetPos; 25 | 26 | dsp::ClockDivider lightDivider; 27 | 28 | DaisyMessage daisyInputMessage[2][1]; 29 | DaisyMessage daisyOutputMessage[2][1]; 30 | 31 | DaisyBlank() { 32 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 33 | 34 | configLight(LINK_LIGHT_L, "Daisy chain link input"); 35 | configLight(LINK_LIGHT_R, "Daisy chain link output"); 36 | 37 | // Set the expander messages 38 | leftExpander.producerMessage = &daisyInputMessage[0]; 39 | leftExpander.consumerMessage = &daisyInputMessage[1]; 40 | rightExpander.producerMessage = &daisyOutputMessage[0]; 41 | rightExpander.consumerMessage = &daisyOutputMessage[1]; 42 | 43 | lightDivider.setDivision(DAISY_LIGHT_DIVISION); 44 | } 45 | 46 | void setWidgetPosition(Vec pos) { 47 | widgetPos = pos; 48 | } 49 | 50 | void process(const ProcessArgs &args) override { 51 | 52 | // Assume this module is the first in the chain; it will get 53 | // overwritten if we receive a value from the left expander 54 | Vec firstPos = widgetPos; 55 | 56 | // Get daisy-chained data from left-side linked module 57 | if (leftExpander.module && ( 58 | leftExpander.module->model == modelDaisyChannel2 59 | || leftExpander.module->model == modelDaisyChannelVu 60 | || leftExpander.module->model == modelDaisyChannelSends2 61 | || leftExpander.module->model == modelDaisyBlank 62 | )) { 63 | const DaisyMessage* msgFromModule = static_cast(leftExpander.consumerMessage); 64 | 65 | firstPos = Vec(msgFromModule->first_pos_x, msgFromModule->first_pos_y); 66 | channelStripId = msgFromModule->channel_strip_id; 67 | 68 | // Set daisy-chained output to right-side linked module 69 | if (rightExpander.module && ( 70 | rightExpander.module->model == modelDaisyMaster2 71 | || rightExpander.module->model == modelDaisyChannel2 72 | || rightExpander.module->model == modelDaisyChannelVu 73 | || rightExpander.module->model == modelDaisyChannelSends2 74 | || rightExpander.module->model == modelDaisyBlank 75 | )) { 76 | DaisyMessage* msgToModule = static_cast(rightExpander.module->leftExpander.producerMessage); 77 | 78 | msgToModule->signals = msgFromModule->signals; 79 | msgToModule->aux1Signals = msgFromModule->aux1Signals; 80 | msgToModule->aux2Signals = msgFromModule->aux2Signals; 81 | msgToModule->soloSignals = msgFromModule->soloSignals; 82 | 83 | msgToModule->first_pos_x = firstPos.x; 84 | msgToModule->first_pos_y = firstPos.y; 85 | msgToModule->channel_strip_id = channelStripId; 86 | 87 | rightExpander.module->leftExpander.messageFlipRequested = true; 88 | } 89 | 90 | link_l = 0.8f; 91 | } else { 92 | channelStripId = 1; 93 | link_l = 0.0f; 94 | } 95 | 96 | // Make sure link light to the right is correct 97 | if (rightExpander.module && ( 98 | rightExpander.module->model == modelDaisyMaster2 99 | || rightExpander.module->model == modelDaisyChannel2 100 | || rightExpander.module->model == modelDaisyChannelVu 101 | || rightExpander.module->model == modelDaisyChannelSends2 102 | || rightExpander.module->model == modelDaisyBlank 103 | )) { 104 | link_r = 0.8f; 105 | } else { 106 | link_r = 0.0f; 107 | } 108 | 109 | // Set lights 110 | if (lightDivider.process()) { 111 | lights[LINK_LIGHT_L].setBrightness(link_l); 112 | lights[LINK_LIGHT_R].setBrightness(link_r); 113 | } 114 | } 115 | }; 116 | 117 | struct DaisyBlankWidget : ModuleWidget { 118 | 119 | dsp::ClockDivider uiDivider; 120 | 121 | explicit DaisyBlankWidget(DaisyBlank *module) { 122 | setModule(module); 123 | setPanel( 124 | createPanel( 125 | asset::plugin(pluginInstance, "res/DaisyBlank.svg"), 126 | asset::plugin(pluginInstance, "res/DaisyBlank-dark.svg") 127 | ) 128 | ); 129 | 130 | // Screws 131 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 132 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 133 | 134 | // Link lights 135 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH - 4, 361.0f), module, DaisyBlank::LINK_LIGHT_L)); 136 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH + 4, 361.0f), module, DaisyBlank::LINK_LIGHT_R)); 137 | 138 | uiDivider.setDivision(DAISY_UI_DIVISION); 139 | } 140 | 141 | void step() override { 142 | if (uiDivider.process()) { 143 | DaisyBlank *module = getModule(); 144 | 145 | if (this->box.pos.x > 0.00) { 146 | module->setWidgetPosition(this->box.pos); 147 | } 148 | } 149 | 150 | ModuleWidget::step(); 151 | } 152 | }; 153 | 154 | Model* modelDaisyBlank = createModel("DaisyBlank"); 155 | -------------------------------------------------------------------------------- /res/DaisyChannel2-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyChannelSends2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyMaster.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyChannelSends2-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/DaisyMaster-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/MasterMixer.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | #include "Daisy.hpp" 3 | 4 | constexpr float SLEW_SPEED = 6.f; // For smoothing out CV 5 | 6 | struct MasterMixer : Module { 7 | enum ParamIds { 8 | MIX_LVL_PARAM, 9 | MONO_PARAM, 10 | ENUMS(LVL_PARAM, 2), 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | MIX_CV_INPUT, 15 | ENUMS(CH_INPUT, 2), 16 | NUM_INPUTS 17 | }; 18 | enum OutputIds { 19 | MIX_OUTPUT, 20 | MIX_OUTPUT_2, 21 | ENUMS(CH_OUTPUT, 2), 22 | NUM_OUTPUTS 23 | }; 24 | 25 | bool levelSlew = true; 26 | SimpleSlewer levelSlewer[16]; 27 | 28 | MasterMixer() { 29 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); 30 | configParam(MIX_LVL_PARAM, 0.0f, 2.0f, 1.0f, "Mix level", " dB", -10, 20); 31 | configSwitch(MONO_PARAM, 0.0f, 1.0f, 1.0f, "Mode", {"Stereo", "Mono"}); 32 | configParam(LVL_PARAM + 0, 0.0f, M_SQRT2, 1.0f, "Channel 1 level", " dB", -10, 40); 33 | configParam(LVL_PARAM + 1, 0.0f, M_SQRT2, 1.0f, "Channel 2 level", " dB", -10, 40); 34 | 35 | configInput(MIX_CV_INPUT, "Mix CV"); 36 | configInput(CH_INPUT + 0, "Channel 1"); 37 | configInput(CH_INPUT + 1, "Channel 2"); 38 | 39 | configOutput(CH_OUTPUT + 0, "Channel 1"); 40 | configOutput(CH_OUTPUT + 1, "Channel 2"); 41 | configOutput(MIX_OUTPUT, "Mix 1"); 42 | configOutput(MIX_OUTPUT_2, "Mix 2"); 43 | 44 | for (int c = 0; c < 16; c++) { 45 | levelSlewer[c].setSlewSpeed(SLEW_SPEED); 46 | } 47 | } 48 | 49 | json_t* dataToJson() override { 50 | json_t* rootJ = json_object(); 51 | 52 | json_object_set_new(rootJ, "level_slew", json_boolean(levelSlew)); 53 | 54 | return rootJ; 55 | } 56 | 57 | void dataFromJson(json_t* rootJ) override { 58 | // level slew 59 | const json_t* levelSlewJ = json_object_get(rootJ, "level_slew"); 60 | if (levelSlewJ) { 61 | levelSlew = json_is_true(levelSlewJ); 62 | } 63 | } 64 | 65 | /** 66 | * When user resets this module 67 | */ 68 | void onReset() override { 69 | levelSlew = true; 70 | } 71 | 72 | void onSampleRateChange() override { 73 | for (int c = 0; c < 16; c++) { 74 | levelSlewer[c].setSlewSpeed(SLEW_SPEED); 75 | } 76 | } 77 | 78 | void process(const ProcessArgs &args) override { 79 | float mix[16] = {}; 80 | float mix_cv[16] = {}; 81 | float mix_out[2][16] = {}; 82 | 83 | int maxChannels = 1; 84 | const bool is_mono = (params[MONO_PARAM].getValue() > 0.0f); 85 | const float master_gain = params[MIX_LVL_PARAM].getValue(); 86 | 87 | for (int i = 0; i < 2; i++) { 88 | int channels = 1; 89 | float ch[16] = {}; 90 | 91 | if (inputs[CH_INPUT + i].isConnected()) { 92 | channels = inputs[CH_INPUT + i].getChannels(); 93 | maxChannels = std::max(maxChannels, channels); 94 | 95 | inputs[CH_INPUT + i].readVoltages(ch); 96 | 97 | const float gain = std::pow(params[LVL_PARAM + i].getValue(), 2.f); 98 | for (int c = 0; c < channels; c++) { 99 | ch[c] *= gain; 100 | mix[c] += ch[c]; 101 | mix_out[i][c] += ch[c]; 102 | } 103 | } 104 | 105 | if (outputs[CH_OUTPUT + i].isConnected()) { 106 | outputs[CH_OUTPUT + i].setChannels(channels); 107 | outputs[CH_OUTPUT + i].writeVoltages(ch); 108 | } 109 | } 110 | 111 | // Gather poly values from CV input 112 | if (inputs[MIX_CV_INPUT].isConnected()) { 113 | for (int c = 0; c < maxChannels; c++) { 114 | mix_cv[c] = clamp(inputs[MIX_CV_INPUT].getPolyVoltage(c) / 10.f, 0.f, 1.f); 115 | if (levelSlew) { 116 | mix_cv[c] = levelSlewer[c].process(mix_cv[c]); 117 | } 118 | } 119 | } else { 120 | for (int c = 0; c < maxChannels; c++) { 121 | mix_cv[c] = 1.f; 122 | } 123 | } 124 | 125 | if (!is_mono && (inputs[CH_INPUT + 0].isConnected() && inputs[CH_INPUT + 1].isConnected())) { 126 | // If the ch 2 jack is active use stereo mode 127 | for (int c = 0; c < maxChannels; c++) { 128 | const float attenuate = master_gain * mix_cv[c]; 129 | mix_out[0][c] *= attenuate; 130 | mix_out[1][c] *= attenuate; 131 | } 132 | outputs[MIX_OUTPUT].setChannels(maxChannels); 133 | outputs[MIX_OUTPUT].writeVoltages(mix_out[0]); 134 | outputs[MIX_OUTPUT_2].setChannels(maxChannels); 135 | outputs[MIX_OUTPUT_2].writeVoltages(mix_out[1]); 136 | } else { 137 | // Otherwise use mono->stereo mode 138 | for (int c = 0; c < maxChannels; c++) { 139 | const float attenuate = master_gain * mix_cv[c]; 140 | mix[c] *= attenuate; 141 | } 142 | outputs[MIX_OUTPUT].setChannels(maxChannels); 143 | outputs[MIX_OUTPUT].writeVoltages(mix); 144 | outputs[MIX_OUTPUT_2].setChannels(maxChannels); 145 | outputs[MIX_OUTPUT_2].writeVoltages(mix); 146 | } 147 | } 148 | }; 149 | 150 | struct MasterMixerWidget : ModuleWidget { 151 | explicit MasterMixerWidget(MasterMixer *module) { 152 | setModule(module); 153 | setPanel( 154 | createPanel( 155 | asset::plugin(pluginInstance, "res/MasterMixer.svg"), 156 | asset::plugin(pluginInstance, "res/MasterMixer-dark.svg") 157 | ) 158 | ); 159 | 160 | // Screws 161 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 162 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 163 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 164 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 165 | 166 | // Level & CV 167 | addParam(createParam(Vec(RACK_GRID_WIDTH * 2.5f - (38.0f / 2), 52.0), module, MasterMixer::MIX_LVL_PARAM)); 168 | addInput(createInput(Vec(RACK_GRID_WIDTH * 2.5f - (25.0f / 2), 96.0), module, MasterMixer::MIX_CV_INPUT)); 169 | 170 | // Mono/stereo switch 171 | addParam(createParam(Vec(RACK_GRID_WIDTH * 2.5f - 7.0f, 162.0f), module, MasterMixer::MONO_PARAM)); 172 | 173 | // LED faders 174 | addParam(createParam(Vec(RACK_GRID_WIDTH * 2.5f - (21.0f + 7.0f), 130.4), module, MasterMixer::LVL_PARAM + 0)); 175 | addParam(createParam(Vec(RACK_GRID_WIDTH * 2.5f + 7.0f, 130.4), module, MasterMixer::LVL_PARAM + 1)); 176 | 177 | // Channel inputs 178 | addInput(createInput(Vec((RACK_GRID_WIDTH * 2.5f) - (25.0f + 5.0f), 232.0), module, MasterMixer::CH_INPUT + 0)); 179 | addInput(createInput(Vec((RACK_GRID_WIDTH * 2.5f) + 5.0f, 232.0), module, MasterMixer::CH_INPUT + 1)); 180 | 181 | // Channel outputs 182 | addOutput(createOutput(Vec((RACK_GRID_WIDTH * 2.5f) - (25.0f + 5.0f), 276.0), module, MasterMixer::CH_OUTPUT + 0)); 183 | addOutput(createOutput(Vec((RACK_GRID_WIDTH * 2.5f) + 5.0f, 276.0), module, MasterMixer::CH_OUTPUT + 1)); 184 | 185 | // Mix outputs 186 | addOutput(createOutput(Vec((RACK_GRID_WIDTH * 2.5f) - (25.0f + 5.0f), 320.0), module, MasterMixer::MIX_OUTPUT)); 187 | addOutput(createOutput(Vec((RACK_GRID_WIDTH * 2.5f) + 5.0f, 320.0), module, MasterMixer::MIX_OUTPUT_2)); 188 | } 189 | 190 | void appendContextMenu(Menu *menu) override { 191 | MasterMixer* module = getModule(); 192 | 193 | menu->addChild(new MenuSeparator); 194 | menu->addChild(createBoolPtrMenuItem("Smooth level CV", "", &module->levelSlew)); 195 | } 196 | }; 197 | 198 | Model* modelMasterMixer = createModel("Mixer2"); 199 | -------------------------------------------------------------------------------- /src/DaisyChannelVu.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | #include "Daisy.hpp" 3 | 4 | static constexpr int VU_LIGHT_COUNT = 32; 5 | 6 | /** Returns the sum of all voltages. */ 7 | float getVoltageSum(const int channels, const float voltages[16]) { 8 | float sum = 0.f; 9 | for (int c = 0; c < channels; c++) { 10 | sum += voltages[c]; 11 | } 12 | return sum; 13 | } 14 | 15 | struct DaisyChannelVu : Module { 16 | enum ParamIds { 17 | NUM_PARAMS 18 | }; 19 | enum InputIds { 20 | NUM_INPUTS 21 | }; 22 | enum OutputIds { 23 | NUM_OUTPUTS 24 | }; 25 | enum LightsIds { 26 | LINK_LIGHT_L, 27 | LINK_LIGHT_R, 28 | ENUMS(VU_LIGHTS_L, VU_LIGHT_COUNT + 8 + 4), 29 | ENUMS(VU_LIGHTS_R, VU_LIGHT_COUNT + 8 + 4), 30 | NUM_LIGHTS 31 | }; 32 | 33 | float link_l = 0.f; 34 | float link_r = 0.f; 35 | int channelStripId = 1; 36 | 37 | Vec widgetPos; 38 | 39 | dsp::ClockDivider lightDivider; 40 | dsp::VuMeter2 vuMeter[2]; 41 | 42 | DaisyMessage daisyInputMessage[2][1]; 43 | DaisyMessage daisyOutputMessage[2][1]; 44 | 45 | DaisyChannelVu() { 46 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 47 | 48 | configLight(LINK_LIGHT_L, "Daisy chain link input"); 49 | configLight(LINK_LIGHT_R, "Daisy chain link output"); 50 | 51 | // Set the expander messages 52 | leftExpander.producerMessage = &daisyInputMessage[0]; 53 | leftExpander.consumerMessage = &daisyInputMessage[1]; 54 | rightExpander.producerMessage = &daisyOutputMessage[0]; 55 | rightExpander.consumerMessage = &daisyOutputMessage[1]; 56 | 57 | lightDivider.setDivision(DAISY_LIGHT_DIVISION); 58 | } 59 | 60 | void setWidgetPosition(Vec pos) { 61 | widgetPos = pos; 62 | } 63 | 64 | void process(const ProcessArgs &args) override { 65 | 66 | // Assume this module is the first in the chain; it will get 67 | // overwritten if we receive a value from the left expander 68 | Vec firstPos = widgetPos; 69 | 70 | // Get daisy-chained data from left-side linked module 71 | if (leftExpander.module && ( 72 | leftExpander.module->model == modelDaisyChannel2 73 | || leftExpander.module->model == modelDaisyChannelVu 74 | || leftExpander.module->model == modelDaisyChannelSends2 75 | || leftExpander.module->model == modelDaisyMaster2 76 | || leftExpander.module->model == modelDaisyBlank 77 | )) { 78 | const DaisyMessage* msgFromModule = static_cast(leftExpander.consumerMessage); 79 | 80 | // Use the single channel to display in VU meter 81 | vuMeter[0].process( 82 | args.sampleTime, 83 | getVoltageSum(msgFromModule->singleSignals.channels, msgFromModule->singleSignals.voltages_l) / 10.f 84 | ); 85 | vuMeter[1].process( 86 | args.sampleTime, 87 | getVoltageSum(msgFromModule->singleSignals.channels, msgFromModule->singleSignals.voltages_r) / 10.f 88 | ); 89 | 90 | firstPos = Vec(msgFromModule->first_pos_x, msgFromModule->first_pos_y); 91 | channelStripId = msgFromModule->channel_strip_id; 92 | 93 | // Set daisy-chained output to right-side linked module 94 | if (rightExpander.module && ( 95 | rightExpander.module->model == modelDaisyMaster2 96 | || rightExpander.module->model == modelDaisyChannel2 97 | || rightExpander.module->model == modelDaisyChannelVu 98 | || rightExpander.module->model == modelDaisyChannelSends2 99 | || rightExpander.module->model == modelDaisyBlank 100 | )) { 101 | DaisyMessage* msgToModule = static_cast(rightExpander.module->leftExpander.producerMessage); 102 | 103 | msgToModule->signals = msgFromModule->signals; 104 | msgToModule->aux1Signals = msgFromModule->aux1Signals; 105 | msgToModule->aux2Signals = msgFromModule->aux2Signals; 106 | msgToModule->soloSignals = msgFromModule->soloSignals; 107 | 108 | msgToModule->first_pos_x = firstPos.x; 109 | msgToModule->first_pos_y = firstPos.y; 110 | msgToModule->channel_strip_id = channelStripId; 111 | 112 | rightExpander.module->leftExpander.messageFlipRequested = true; 113 | } 114 | 115 | link_l = 0.8f; 116 | } else { 117 | vuMeter[0].process(args.sampleTime, 0.0f); 118 | vuMeter[1].process(args.sampleTime, 0.0f); 119 | channelStripId = 1; 120 | link_l = 0.0f; 121 | } 122 | 123 | // Make sure link light to the right is correct 124 | if (rightExpander.module && ( 125 | rightExpander.module->model == modelDaisyMaster2 126 | || rightExpander.module->model == modelDaisyChannel2 127 | || rightExpander.module->model == modelDaisyChannelVu 128 | || rightExpander.module->model == modelDaisyChannelSends2 129 | || rightExpander.module->model == modelDaisyBlank 130 | )) { 131 | link_r = 0.8f; 132 | } else { 133 | link_r = 0.0f; 134 | } 135 | 136 | // Set lights 137 | if (lightDivider.process()) { 138 | for (int i = VU_LIGHT_COUNT + 8 + 3; i >= 0; i--) { 139 | const float dbMax = -60.f + 1.5f * static_cast(i); 140 | const float dbMin = dbMax + 1; 141 | lights[VU_LIGHTS_L + i].setBrightness(vuMeter[0].getBrightness(dbMin, dbMax)); 142 | lights[VU_LIGHTS_R + i].setBrightness(vuMeter[1].getBrightness(dbMin, dbMax)); 143 | } 144 | lights[LINK_LIGHT_L].setBrightness(link_l); 145 | lights[LINK_LIGHT_R].setBrightness(link_r); 146 | } 147 | } 148 | }; 149 | 150 | struct DaisyChannelVuWidget : ModuleWidget { 151 | 152 | dsp::ClockDivider uiDivider; 153 | 154 | explicit DaisyChannelVuWidget(DaisyChannelVu *module) { 155 | setModule(module); 156 | setPanel( 157 | createPanel( 158 | asset::plugin(pluginInstance, "res/DaisyChannelVu.svg"), 159 | asset::plugin(pluginInstance, "res/DaisyChannelVu-dark.svg") 160 | ) 161 | ); 162 | 163 | // Screws 164 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 165 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 166 | 167 | // Link lights 168 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 - 3, 361.0f), module, DaisyChannelVu::LINK_LIGHT_L)); 169 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 + 3, 361.0f), module, DaisyChannelVu::LINK_LIGHT_R)); 170 | 171 | for (int i = 0; i < VU_LIGHT_COUNT; i++) { 172 | float distance = static_cast(i) * 7; 173 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 - 3.f, 339.f - distance), module, DaisyChannelVu::VU_LIGHTS_L + i)); 174 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 + 3.f, 339.f - distance), module, DaisyChannelVu::VU_LIGHTS_R + i)); 175 | } 176 | for (int i = VU_LIGHT_COUNT; i < VU_LIGHT_COUNT + 8; i++) { 177 | float distance = static_cast(i) * 7; 178 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 - 3.f, 339.f - distance), module, DaisyChannelVu::VU_LIGHTS_L + i)); 179 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 + 3.f, 339.f - distance), module, DaisyChannelVu::VU_LIGHTS_R + i)); 180 | } 181 | for (int i = VU_LIGHT_COUNT + 8; i < VU_LIGHT_COUNT + 12; i++) { 182 | float distance = static_cast(i) * 7; 183 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 - 3.f, 339.f - distance), module, DaisyChannelVu::VU_LIGHTS_L + i)); 184 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH / 2 + 3.f, 339.f - distance), module, DaisyChannelVu::VU_LIGHTS_R + i)); 185 | } 186 | 187 | uiDivider.setDivision(DAISY_UI_DIVISION); 188 | } 189 | 190 | void step() override { 191 | if (uiDivider.process()) { 192 | DaisyChannelVu *module = getModule(); 193 | 194 | if (this->box.pos.x > 0.00) { 195 | module->setWidgetPosition(this->box.pos); 196 | } 197 | } 198 | 199 | ModuleWidget::step(); 200 | } 201 | }; 202 | 203 | Model* modelDaisyChannelVu = createModel("DaisyChannelVu"); 204 | -------------------------------------------------------------------------------- /src/DaisyChannelSends2.cpp: -------------------------------------------------------------------------------- 1 | #include "QuantalAudio.hpp" 2 | #include "Daisy.hpp" 3 | 4 | struct DaisyChannelSends2 : Module { 5 | enum ParamIds { 6 | GROUP_PARAM, 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | NUM_INPUTS 11 | }; 12 | enum OutputIds { 13 | CH_OUTPUT_1, // Left 14 | CH_OUTPUT_2, // Right 15 | NUM_OUTPUTS 16 | }; 17 | enum LightsIds { 18 | LINK_LIGHT_L, 19 | LINK_LIGHT_R, 20 | GROUP_BTN_LIGHT, 21 | GROUP1_LIGHT, 22 | GROUP2_LIGHT, 23 | NUM_LIGHTS 24 | }; 25 | 26 | bool muted = false; 27 | int group = 1; 28 | float link_l = 0.f; 29 | float link_r = 0.f; 30 | 31 | int channelStripId = 1; 32 | 33 | Vec widgetPos; 34 | 35 | dsp::ClockDivider lightDivider; 36 | dsp::SchmittTrigger groupChangeTrigger; 37 | 38 | DaisyMessage daisyInputMessage[2][1]; 39 | DaisyMessage daisyOutputMessage[2][1]; 40 | 41 | StereoVoltages daisySignals = {}; 42 | StereoVoltages auxSignals = {}; 43 | StereoVoltages aux1Signals = {}; 44 | StereoVoltages aux2Signals = {}; 45 | StereoVoltages soloSignals = {}; 46 | 47 | DaisyChannelSends2() { 48 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 49 | 50 | configButton(GROUP_PARAM, "Change group"); 51 | 52 | configOutput(CH_OUTPUT_1, "Channel L"); 53 | configOutput(CH_OUTPUT_2, "Channel R"); 54 | 55 | configLight(LINK_LIGHT_L, "Daisy chain link input"); 56 | configLight(LINK_LIGHT_R, "Daisy chain link output"); 57 | 58 | // Set the expander messages 59 | leftExpander.producerMessage = &daisyInputMessage[0]; 60 | leftExpander.consumerMessage = &daisyInputMessage[1]; 61 | rightExpander.producerMessage = &daisyOutputMessage[0]; 62 | rightExpander.consumerMessage = &daisyOutputMessage[1]; 63 | 64 | lightDivider.setDivision(DAISY_LIGHT_DIVISION); 65 | } 66 | 67 | json_t* dataToJson() override { 68 | json_t* rootJ = json_object(); 69 | 70 | // mute 71 | json_object_set_new(rootJ, "group", json_integer(group)); 72 | 73 | return rootJ; 74 | } 75 | 76 | void dataFromJson(json_t* rootJ) override { 77 | // mute 78 | const json_t* groupJ = json_object_get(rootJ, "group"); 79 | if (groupJ) { 80 | group = json_integer_value(groupJ); 81 | } 82 | } 83 | 84 | void setWidgetPosition(Vec pos) { 85 | widgetPos = pos; 86 | } 87 | 88 | void process(const ProcessArgs &args) override { 89 | daisySignals = {}; 90 | auxSignals = {}; 91 | aux1Signals = {}; 92 | aux2Signals = {}; 93 | soloSignals = {}; 94 | 95 | // Assume this module is the first in the chain; it will get 96 | // overwritten if we receive a value from the left expander 97 | Vec firstPos = widgetPos; 98 | 99 | bool groupButton = params[GROUP_PARAM].getValue() > 0.f; 100 | if (groupChangeTrigger.process(params[GROUP_PARAM].getValue())) { 101 | group = group + 1; 102 | if (group > 2) { 103 | group = 1; 104 | } 105 | } 106 | 107 | // Get daisy-chained data from left-side linked module 108 | if (leftExpander.module && ( 109 | leftExpander.module->model == modelDaisyChannel2 110 | || leftExpander.module->model == modelDaisyChannelVu 111 | || leftExpander.module->model == modelDaisyChannelSends2 112 | || leftExpander.module->model == modelDaisyBlank 113 | )) { 114 | DaisyMessage* msgFromModule = static_cast(leftExpander.consumerMessage); 115 | 116 | daisySignals = msgFromModule->signals; 117 | aux1Signals = msgFromModule->aux1Signals; 118 | aux2Signals = msgFromModule->aux2Signals; 119 | 120 | if (group == 1) { 121 | auxSignals = aux1Signals; 122 | } else { 123 | auxSignals = aux2Signals; 124 | } 125 | 126 | soloSignals = msgFromModule->soloSignals; 127 | 128 | firstPos = Vec(msgFromModule->first_pos_x, msgFromModule->first_pos_y); 129 | channelStripId = msgFromModule->channel_strip_id; 130 | 131 | link_l = 0.8f; 132 | } else { 133 | channelStripId = 1; 134 | link_l = 0.0f; 135 | } 136 | 137 | // Set daisy-chained output to right-side linked module 138 | if (rightExpander.module && ( 139 | rightExpander.module->model == modelDaisyMaster2 140 | || rightExpander.module->model == modelDaisyChannel2 141 | || rightExpander.module->model == modelDaisyChannelVu 142 | || rightExpander.module->model == modelDaisyChannelSends2 143 | || rightExpander.module->model == modelDaisyBlank 144 | )) { 145 | DaisyMessage* msgToModule = static_cast(rightExpander.module->leftExpander.producerMessage); 146 | 147 | msgToModule->signals = daisySignals; 148 | msgToModule->aux1Signals = aux1Signals; 149 | msgToModule->aux2Signals = aux2Signals; 150 | msgToModule->soloSignals = soloSignals; 151 | 152 | msgToModule->first_pos_x = firstPos.x; 153 | msgToModule->first_pos_y = firstPos.y; 154 | msgToModule->channel_strip_id = channelStripId; 155 | 156 | link_r = 0.8f; 157 | } else { 158 | link_r = 0.0f; 159 | } 160 | 161 | // Set this channel's output to right-side linked VU module 162 | if (rightExpander.module && rightExpander.module->model == modelDaisyChannelVu) { 163 | // Write this module's output to the single channel message 164 | DaisyMessage* msgToModule = static_cast(rightExpander.module->leftExpander.producerMessage); 165 | msgToModule->singleSignals = auxSignals; 166 | } 167 | 168 | if (rightExpander.module && link_r > 0.0f) { 169 | rightExpander.module->leftExpander.messageFlipRequested = true; 170 | } 171 | 172 | // Set aggregated decoded output 173 | outputs[CH_OUTPUT_1].setChannels(auxSignals.channels); 174 | outputs[CH_OUTPUT_1].writeVoltages(auxSignals.voltages_l); 175 | outputs[CH_OUTPUT_2].setChannels(auxSignals.channels); 176 | outputs[CH_OUTPUT_2].writeVoltages(auxSignals.voltages_r); 177 | 178 | // Set lights 179 | if (lightDivider.process()) { 180 | lights[LINK_LIGHT_L].setBrightness(link_l); 181 | lights[LINK_LIGHT_R].setBrightness(link_r); 182 | 183 | lights[GROUP_BTN_LIGHT].setBrightness(groupButton); 184 | if (group == 1) { 185 | lights[GROUP1_LIGHT].setBrightness(1); 186 | lights[GROUP2_LIGHT].setBrightness(0); 187 | } else { 188 | lights[GROUP1_LIGHT].setBrightness(0); 189 | lights[GROUP2_LIGHT].setBrightness(1); 190 | } 191 | } 192 | } 193 | }; 194 | 195 | struct DaisyChannelSendsWidget2 : ModuleWidget { 196 | 197 | dsp::ClockDivider uiDivider; 198 | 199 | explicit DaisyChannelSendsWidget2(DaisyChannelSends2 *module) { 200 | setModule(module); 201 | setPanel( 202 | createPanel( 203 | asset::plugin(pluginInstance, "res/DaisyChannelSends2.svg"), 204 | asset::plugin(pluginInstance, "res/DaisyChannelSends2-dark.svg") 205 | ) 206 | ); 207 | 208 | // Screws 209 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 210 | addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 211 | 212 | // Switch 213 | addParam(createLightParamCentered>>(Vec(RACK_GRID_WIDTH - 0, 57.5f), module, DaisyChannelSends2::GROUP_PARAM, DaisyChannelSends2::GROUP_BTN_LIGHT)); 214 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH - 2, 80.0f), module, DaisyChannelSends2::GROUP1_LIGHT)); 215 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH - 2, 90.0f), module, DaisyChannelSends2::GROUP2_LIGHT)); 216 | 217 | // Channel Output 218 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 290.0), module, DaisyChannelSends2::CH_OUTPUT_1)); 219 | addOutput(createOutput(Vec(RACK_GRID_WIDTH - 12.5f, 316.0), module, DaisyChannelSends2::CH_OUTPUT_2)); 220 | 221 | // Link lights 222 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH - 4, 361.0f), module, DaisyChannelSends2::LINK_LIGHT_L)); 223 | addChild(createLightCentered>(Vec(RACK_GRID_WIDTH + 4, 361.0f), module, DaisyChannelSends2::LINK_LIGHT_R)); 224 | } 225 | 226 | void step() override { 227 | if (uiDivider.process()) { 228 | DaisyChannelSends2 *module = getModule(); 229 | 230 | if (this->box.pos.x > 0.00) { 231 | module->setWidgetPosition(this->box.pos); 232 | } 233 | } 234 | 235 | ModuleWidget::step(); 236 | } 237 | }; 238 | 239 | Model* modelDaisyChannelSends2 = createModel("DaisyChannelSends2"); 240 | -------------------------------------------------------------------------------- /res/src/Horsehair.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 76 | 77 | 78 | 79 | HORSEHAIR VCO 80 | 81 | 82 | 83 | 84 | V/OCT 85 | PITCH 86 | OCT A 87 | OCT B 88 | SHAPE A 89 | SHAPE B 90 | PW A 91 | PW B 92 | MIX 93 | 94 | 95 | OUT 96 | SIN 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /res/src/Horsehair-dark.src.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 76 | 77 | 78 | 79 | HORSEHAIR VCO 80 | 81 | 82 | 83 | 84 | V/OCT 85 | PITCH 86 | OCT A 87 | OCT B 88 | SHAPE A 89 | SHAPE B 90 | PW A 91 | PW B 92 | MIX 93 | 94 | 95 | OUT 96 | SIN 97 | 98 | 99 | 100 | --------------------------------------------------------------------------------