├── .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 |
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 |
12 |
--------------------------------------------------------------------------------
/res/blank-5.svg:
--------------------------------------------------------------------------------
1 |
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 |
17 |
--------------------------------------------------------------------------------
/res/src/DaisyBlank-dark.src.svg:
--------------------------------------------------------------------------------
1 |
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 |
26 |
--------------------------------------------------------------------------------
/res/src/DaisyChannelVu-dark.src.svg:
--------------------------------------------------------------------------------
1 |
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 |
32 |
--------------------------------------------------------------------------------
/res/src/DaisyChannelSends2-dark.src.svg:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------
/res/src/DaisyChannel2.src.svg:
--------------------------------------------------------------------------------
1 |
35 |
--------------------------------------------------------------------------------
/res/src/DaisyChannel2-dark.src.svg:
--------------------------------------------------------------------------------
1 |
35 |
--------------------------------------------------------------------------------
/res/src/BufferedMult.src.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/res/src/BufferedMult-dark.src.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/res/src/DaisyChannel.src.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/res/src/DaisyChannel-dark.src.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/res/src/DaisyMaster2.src.svg:
--------------------------------------------------------------------------------
1 |
38 |
39 |
--------------------------------------------------------------------------------
/res/src/DaisyMaster2-dark.src.svg:
--------------------------------------------------------------------------------
1 |
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 |
40 |
41 |
--------------------------------------------------------------------------------
/res/src/UnityMix.src.svg:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/res/src/UnityMix-dark.src.svg:
--------------------------------------------------------------------------------
1 |
38 |
--------------------------------------------------------------------------------
/res/src/DaisyMaster-dark.src.svg:
--------------------------------------------------------------------------------
1 |
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 |
56 |
--------------------------------------------------------------------------------
/res/src/MasterMixer-dark.src.svg:
--------------------------------------------------------------------------------
1 |
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 |
100 |
--------------------------------------------------------------------------------
/res/src/Horsehair-dark.src.svg:
--------------------------------------------------------------------------------
1 |
100 |
--------------------------------------------------------------------------------