├── .gitignore ├── .gitmodules ├── LICENSE-GPLv3.txt ├── LICENSE.md ├── Makefile ├── README.md ├── generate_quantizer_presets.py ├── plugin.json ├── presets ├── CVMix │ └── Unity mix.vcvm ├── Mutes │ └── Mute all.vcvm └── Quantizer │ ├── 00_Ionian (Major).vcvm │ ├── 01_Dorian.vcvm │ ├── 02_Phrygian.vcvm │ ├── 03_Lydian.vcvm │ ├── 04_Mixolydian.vcvm │ ├── 05_Aeolian (Minor).vcvm │ ├── 06_Locrian.vcvm │ ├── 07_Aeolian 7 (Harmonic Minor).vcvm │ ├── 08_Locrian 6.vcvm │ ├── 09_Ionian #5.vcvm │ ├── 10_Dorian #4.vcvm │ ├── 11_Phrygian 3.vcvm │ ├── 12_Lydian #2.vcvm │ ├── 13_Locrian b4 bb7.vcvm │ ├── 14_Aeolian 6 7 (Melodic Minor).vcvm │ ├── 15_Phrygian 6.vcvm │ ├── 16_Lydian #5.vcvm │ ├── 17_Lydian b7.vcvm │ ├── 18_Aeolian 3.vcvm │ ├── 19_Locrian 2.vcvm │ ├── 20_Locrian b4.vcvm │ ├── 21_Bebop Dominant.vcvm │ ├── 22_Bebop Major.vcvm │ ├── 23_Bebop Minor.vcvm │ ├── 24_Bebop Melodic Minor.vcvm │ ├── 25_Blues Major.vcvm │ ├── 26_Blues Minor.vcvm │ ├── 27_Blues Diminished.vcvm │ ├── 28_Blues Pentatonic.vcvm │ ├── 29_Blues Rock'n'Roll.vcvm │ ├── 30_Byzantine.vcvm │ ├── 31_Hungarian Minor.vcvm │ ├── 32_Hungarian Gypsy.vcvm │ ├── 33_Spanish Gypsy.vcvm │ ├── 34_Major Pentatonic.vcvm │ ├── 35_Neutral Pentatonic.vcvm │ ├── 36_Rock Pentatonic.vcvm │ ├── 37_Scottish Pentatonic.vcvm │ ├── 38_Minor Pentatonic.vcvm │ ├── 39_Whole.vcvm │ ├── 40_Whole-Half.vcvm │ ├── 41_Half-Whole.vcvm │ ├── 42_Augmented.vcvm │ ├── 43_Byzantine.vcvm │ ├── 44_Chromatic.vcvm │ ├── 45_Enigmatic (Ascending).vcvm │ ├── 46_Enigmatic (Descending).vcvm │ ├── 47_Hungarian Major.vcvm │ ├── 48_Hungarian Minor.vcvm │ ├── 49_Neapolitan Major.vcvm │ ├── 50_Neapolitan Minor.vcvm │ ├── 51_Overtone.vcvm │ ├── 52_Prometheus.vcvm │ ├── 53_Prometheus Neapolitan.vcvm │ └── 54_Spanish 8 Tone.vcvm ├── res ├── 8vert-dark.svg ├── 8vert.svg ├── ADSR-dark.svg ├── ADSR.svg ├── CVMix-dark.svg ├── CVMix.svg ├── Compare-dark.svg ├── Compare.svg ├── Delay-dark.svg ├── Delay.svg ├── Fade-dark.svg ├── Fade.svg ├── Gates-dark.svg ├── Gates.svg ├── LFO-dark.svg ├── LFO.svg ├── Logic-dark.svg ├── Logic.svg ├── Merge-dark.svg ├── Merge.svg ├── MidSide-dark.svg ├── MidSide.svg ├── Mixer-dark.svg ├── Mixer.svg ├── Mult-dark.svg ├── Mult.svg ├── Mutes-dark.svg ├── Mutes.svg ├── Noise-dark.svg ├── Noise.svg ├── Octave-dark.svg ├── Octave.svg ├── Process-dark.svg ├── Process.svg ├── Pulses-dark.svg ├── Pulses.svg ├── Push-dark.svg ├── Push.svg ├── Quantizer-dark.svg ├── Quantizer.svg ├── Random-dark.svg ├── Random.svg ├── RandomValues-dark.svg ├── RandomValues.svg ├── Rescale-dark.svg ├── Rescale.svg ├── SEQ3-dark.svg ├── SEQ3.svg ├── SHASR-dark.svg ├── SHASR.svg ├── Scope-dark.svg ├── Scope.svg ├── SequentialSwitch1-dark.svg ├── SequentialSwitch1.svg ├── SequentialSwitch2-dark.svg ├── SequentialSwitch2.svg ├── Split-dark.svg ├── Split.svg ├── Sum-dark.svg ├── Sum.svg ├── Unity.svg ├── VCA-1-dark.svg ├── VCA-1.svg ├── VCA.svg ├── VCF-dark.svg ├── VCF.svg ├── VCMixer-dark.svg ├── VCMixer.svg ├── VCO-dark.svg ├── VCO.svg ├── VCVBezelBig.svg ├── Viz-dark.svg ├── Viz.svg ├── WTLFO-dark.svg ├── WTLFO.svg ├── WTVCO-dark.svg └── WTVCO.svg └── src ├── 8vert.cpp ├── ADSR.cpp ├── CVMix.cpp ├── Compare.cpp ├── Delay.cpp ├── Fade.cpp ├── Gates.cpp ├── LFO.cpp ├── Logic.cpp ├── Merge.cpp ├── MidSide.cpp ├── Mixer.cpp ├── Mult.cpp ├── Mutes.cpp ├── Noise.cpp ├── Octave.cpp ├── Process.cpp ├── Pulses.cpp ├── Push.cpp ├── Quantizer.cpp ├── Random.cpp ├── RandomValues.cpp ├── Rescale.cpp ├── SEQ3.cpp ├── SHASR.cpp ├── Scope.cpp ├── SequentialSwitch.cpp ├── Split.cpp ├── Sum.cpp ├── Unity.cpp ├── VCA-1.cpp ├── VCA.cpp ├── VCF.cpp ├── VCMixer.cpp ├── VCO.cpp ├── Viz.cpp ├── WTLFO.cpp ├── WTVCO.cpp ├── Wavetable.hpp ├── dr_wav.c ├── dr_wav.h ├── plugin.cpp └── plugin.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all dotfiles except git dotfiles 2 | .* 3 | !.git* 4 | 5 | # Binaries and build targets 6 | *.a 7 | *.so 8 | *.dylib 9 | *.dll 10 | /build 11 | /dep 12 | /dist 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VCVRack/Fundamental/d12b22a170f2b4e7940572ac13ad24869f8ea806/.gitmodules -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All **source code** is copyright © 2016-2023 VCV. 2 | 3 | This program is free software: you can redistribute it and/or modify it under the terms of the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.en.html) as published by the [Free Software Foundation](https://www.fsf.org/), either version 3 of the License, or (at your option) any later version. 4 | 5 | The **VCV logo and icon** are copyright © 2017 VCV and may not be used in derivative works. 6 | 7 | The **"VCV" name** is trademarked and may not be used for unofficial products. 8 | 9 | The **visual design of the Fundamental modules** is copyright © 2019-2023 [VCV](https://vcvrack.com/) and licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). 10 | Commercial use and derivative works are not allowed. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RACK_DIR ?= ../.. 2 | 3 | FLAGS += -Idep/include 4 | SOURCES += $(wildcard src/*.cpp) 5 | SOURCES += $(wildcard src/*.c) 6 | DISTRIBUTABLES += res 7 | DISTRIBUTABLES += $(wildcard LICENSE*) 8 | DISTRIBUTABLES += $(wildcard presets) 9 | 10 | include $(RACK_DIR)/plugin.mk 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VCV Free modules 2 | 3 | *Essential synthesizer modules included with VCV Rack* 4 | 5 | Contact [VCV Support](https://vcvrack.com/support) to request a feature or report a bug. 6 | 7 | We cannot accept code contributions for this plugin. 8 | See [Contributing to Rack](https://github.com/VCVRack/Rack/blob/v2/.github/CONTRIBUTING.md). 9 | 10 | ## Building 11 | 12 | Follow [Building Rack plugins](https://vcvrack.com/manual/Building#Building-Rack-plugins) in the VCV Rack manual. 13 | -------------------------------------------------------------------------------- /generate_quantizer_presets.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | scales = """ 5 | Ionian (Major) 1-2-3-4-5-6-7 6 | Dorian 1-2-b3-4-5-6-b7 7 | Phrygian 1-b2-b3-4-5-b6-b7 8 | Lydian 1-2-3-#4-5-6-7 9 | Mixolydian 1-2-3-4-5-6-b7 10 | Aeolian (Minor) 1-2-b3-4-5-b6-b7 11 | Locrian 1-b2-b3-4-b5-b6-b7 12 | Aeolian 7 (Harmonic Minor) 1-2-b3-4-5-b6-7 13 | Locrian 6 1-b2-b3-4-b5-6-b7 14 | Ionian #5 1-2-3-4-#5-6-7 15 | Dorian #4 1-2-b3-#4-5-6-b7 16 | Phrygian 3 1-b2-3-4-5-b6-b7 17 | Lydian #2 1-#2-3-#4-5-6-7 18 | Locrian b4 bb7 1-b2-b3-b4-b5-b6-bb7 19 | Aeolian 6 7 (Melodic Minor) 1-2-b3-4-5-6-7 20 | Phrygian 6 1-b2-b3-4-5-6-b7 21 | Lydian #5 1-2-3-#4-#5-6-7 22 | Lydian b7 1-2-3-#4-5-6-b7 23 | Aeolian 3 1-2-3-4-5-b6-b7 24 | Locrian 2 1-2-b3-4-b5-b6-b7 25 | Locrian b4 1-b2-b3-b4-b5-b6-b7 26 | Bebop Dominant 1-2-3-4-5-6-#6-7 27 | Bebop Major 1-2-3-4-5-b6-6-7 28 | Bebop Minor 1-2-b3-3-4-5-6-b7 29 | Bebop Melodic Minor 1-2-b3-4-5-b6-6-7 30 | Blues Major 1-2-b3-3-5-6 31 | Blues Minor 1-b3-4-b5-5-b7 32 | Blues Diminished 1-b2-b3-3-b5-5-6-b7 33 | Blues Pentatonic 1-b3-4-5-b7 34 | Blues Rock'n'Roll 1-2-b3-3-4-b5-5-6-b7 35 | Byzantine 1-b2-3-4-5-b6-7 36 | Hungarian Minor 1-2-b3-b5-5-b6-7 37 | Hungarian Gypsy 1-2-b3-#4-5-b6-b7 38 | Spanish Gypsy 1-b2-3-4-5-b6-b7 39 | Major Pentatonic 1-2-3-5-6 40 | Neutral Pentatonic 1-2-4-5-b7 41 | Rock Pentatonic 1-b3-4-#5-b7 42 | Scottish Pentatonic 1-2-4-5-6 43 | Minor Pentatonic 1-b3-4-5-b7 44 | Whole 1-2-3-#4-#5-#6 45 | Whole-Half 1-2-b3-4-#4-#5-6-7 46 | Half-Whole 1-b2-b3-3-b5-5-6-b7 47 | Augmented 1-#2-3-5-#5-7 48 | Byzantine 1-b2-3-4-5-b6-7 49 | Chromatic 1-#1-2-#2-3-4-#4-5-#5-6-#6-7 50 | Enigmatic (Ascending) 1-b2-3-#4-#5-#6-7 51 | Enigmatic (Descending) 1-b2-3-4-b6-b7-7 52 | Hungarian Major 1-b3-3-b5-5-6-b7 53 | Hungarian Minor 1-2-b3-b5-5-b6-7 54 | Neapolitan Major 1-b2-b3-4-5-6-7 55 | Neapolitan Minor 1-b2-b3-4-5-b6-7 56 | Overtone 1-2-3-#4-5-6-b7 57 | Prometheus 1-2-3-b5-6-b7 58 | Prometheus Neapolitan 1-b2-3-b5-6-b7 59 | Spanish 8 Tone 1-b2-b3-3-4-b5-b6-b7 60 | """ 61 | 62 | note_indexes = { 63 | "1": 0, 64 | "#1": 1, 65 | "b2": 1, 66 | "2": 2, 67 | "#2": 3, 68 | "b3": 3, 69 | "3": 4, 70 | "b4": 4, 71 | "4": 5, 72 | "#4": 6, 73 | "b5": 6, 74 | "5": 7, 75 | "#5": 8, 76 | "b6": 8, 77 | "6": 9, 78 | "bb7": 9, 79 | "#6": 10, 80 | "b7": 10, 81 | "7": 11, 82 | } 83 | 84 | dir = "presets/Quantizer" 85 | os.makedirs(dir, exist_ok=True) 86 | count = 0 87 | 88 | for line in scales.splitlines(): 89 | if not line: 90 | continue 91 | 92 | data = { 93 | "plugin": "Fundamental", 94 | "model": "Quantizer", 95 | "version": "2.0.0", 96 | "params": [], 97 | "data": { 98 | "enabledNotes": [ 99 | False, 100 | False, 101 | False, 102 | False, 103 | False, 104 | False, 105 | False, 106 | False, 107 | False, 108 | False, 109 | False, 110 | False, 111 | ] 112 | } 113 | } 114 | 115 | name, notes = line.split("\t") 116 | notes = notes.split("-") 117 | 118 | for note in notes: 119 | note_index = note_indexes[note] 120 | data["data"]["enabledNotes"][note_index] = True 121 | 122 | path = os.path.join(dir, f"{count:02d}_{name}.vcvm") 123 | print(path) 124 | print(data) 125 | with open(path, "w", newline='\n') as f: 126 | json.dump(data, f, indent=2) 127 | 128 | count += 1 129 | -------------------------------------------------------------------------------- /presets/CVMix/Unity mix.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "CVMix", 4 | "version": "2.2.0", 5 | "params": [ 6 | { 7 | "value": 1.0, 8 | "id": 0 9 | }, 10 | { 11 | "value": 1.0, 12 | "id": 1 13 | }, 14 | { 15 | "value": 1.0, 16 | "id": 2 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /presets/Mutes/Mute all.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Mutes", 4 | "version": "2.2.0", 5 | "params": [ 6 | { 7 | "value": 1.0, 8 | "id": 0 9 | }, 10 | { 11 | "value": 1.0, 12 | "id": 1 13 | }, 14 | { 15 | "value": 1.0, 16 | "id": 2 17 | }, 18 | { 19 | "value": 1.0, 20 | "id": 3 21 | }, 22 | { 23 | "value": 1.0, 24 | "id": 4 25 | }, 26 | { 27 | "value": 1.0, 28 | "id": 5 29 | }, 30 | { 31 | "value": 1.0, 32 | "id": 6 33 | }, 34 | { 35 | "value": 1.0, 36 | "id": 7 37 | }, 38 | { 39 | "value": 1.0, 40 | "id": 8 41 | }, 42 | { 43 | "value": 1.0, 44 | "id": 9 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /presets/Quantizer/00_Ionian (Major).vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/01_Dorian.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/02_Phrygian.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/03_Lydian.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/04_Mixolydian.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/05_Aeolian (Minor).vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/06_Locrian.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | false, 13 | true, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/07_Aeolian 7 (Harmonic Minor).vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/08_Locrian 6.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | false, 13 | true, 14 | true, 15 | false, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/09_Ionian #5.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | true, 14 | false, 15 | false, 16 | true, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/10_Dorian #4.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/11_Phrygian 3.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/12_Lydian #2.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/13_Locrian b4 bb7.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | true, 13 | false, 14 | true, 15 | false, 16 | true, 17 | true, 18 | false, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/14_Aeolian 6 7 (Melodic Minor).vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/15_Phrygian 6.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/16_Lydian #5.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | false, 16 | true, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/17_Lydian b7.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/18_Aeolian 3.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/19_Locrian 2.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/20_Locrian b4.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | true, 13 | false, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/21_Bebop Dominant.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | true, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/22_Bebop Major.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | true, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/23_Bebop Minor.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | true, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/24_Bebop Melodic Minor.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | true, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/25_Blues Major.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | true, 13 | false, 14 | false, 15 | true, 16 | false, 17 | true, 18 | false, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/26_Blues Minor.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | false, 13 | true, 14 | true, 15 | true, 16 | false, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/27_Blues Diminished.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/28_Blues Pentatonic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/29_Blues Rock'n'Roll.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | true, 13 | true, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/30_Byzantine.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/31_Hungarian Minor.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | false, 14 | true, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/32_Hungarian Gypsy.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | false, 14 | true, 15 | true, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/33_Spanish Gypsy.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/34_Major Pentatonic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | false, 15 | true, 16 | false, 17 | true, 18 | false, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/35_Neutral Pentatonic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/36_Rock Pentatonic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/37_Scottish Pentatonic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | false, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/38_Minor Pentatonic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/39_Whole.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/40_Whole-Half.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | true, 14 | true, 15 | false, 16 | true, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/41_Half-Whole.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/42_Augmented.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | true, 13 | false, 14 | false, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/43_Byzantine.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/44_Chromatic.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | true, 11 | true, 12 | true, 13 | true, 14 | true, 15 | true, 16 | true, 17 | true, 18 | true, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/45_Enigmatic (Ascending).vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | false, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/46_Enigmatic (Descending).vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | true, 14 | false, 15 | false, 16 | true, 17 | false, 18 | true, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/47_Hungarian Major.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | false, 11 | true, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/48_Hungarian Minor.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | true, 12 | false, 13 | false, 14 | true, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/49_Neapolitan Major.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | false, 17 | true, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/50_Neapolitan Minor.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | false, 13 | true, 14 | false, 15 | true, 16 | true, 17 | false, 18 | false, 19 | true 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/51_Overtone.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | true, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/52_Prometheus.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | false, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/53_Prometheus Neapolitan.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | false, 12 | true, 13 | false, 14 | true, 15 | false, 16 | false, 17 | true, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /presets/Quantizer/54_Spanish 8 Tone.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "Fundamental", 3 | "model": "Quantizer", 4 | "version": "2.0.0", 5 | "params": [], 6 | "data": { 7 | "enabledNotes": [ 8 | true, 9 | true, 10 | false, 11 | true, 12 | true, 13 | true, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /res/Mixer-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /res/Mult-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /res/Octave-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /res/RandomValues-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /res/VCA-1-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /res/VCVBezelBig.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /res/Viz-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/8vert.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct _8vert : Module { 5 | enum ParamIds { 6 | ENUMS(GAIN_PARAMS, 8), 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | ENUMS(IN_INPUTS, 8), 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | ENUMS(OUT_OUTPUTS, 8), 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | NUM_LIGHTS 19 | }; 20 | 21 | dsp::ClockDivider paramDivider; 22 | 23 | _8vert() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | for (int i = 0; i < 8; i++) { 26 | configParam(GAIN_PARAMS + i, -1.f, 1.f, 0.f, string::f("Row %d gain", i + 1), "%", 0, 100); 27 | configInput(IN_INPUTS + i, string::f("Row %d", i + 1)); 28 | configOutput(OUT_OUTPUTS + i, string::f("Row %d", i + 1)); 29 | } 30 | 31 | paramDivider.setDivision(2048); 32 | } 33 | 34 | void process(const ProcessArgs& args) override { 35 | float in[16] = {10.f}; 36 | int channels = 1; 37 | 38 | for (int i = 0; i < 8; i++) { 39 | // Get input 40 | if (inputs[IN_INPUTS + i].isConnected()) { 41 | channels = inputs[IN_INPUTS + i].getChannels(); 42 | inputs[IN_INPUTS + i].readVoltages(in); 43 | } 44 | 45 | if (outputs[OUT_OUTPUTS + i].isConnected()) { 46 | // Apply gain 47 | float out[16]; 48 | float gain = params[GAIN_PARAMS + i].getValue(); 49 | for (int c = 0; c < channels; c++) { 50 | out[c] = gain * in[c]; 51 | } 52 | 53 | // Set output 54 | outputs[OUT_OUTPUTS + i].setChannels(channels); 55 | outputs[OUT_OUTPUTS + i].writeVoltages(out); 56 | } 57 | } 58 | 59 | if (paramDivider.process()) { 60 | refreshParamQuantities(); 61 | } 62 | } 63 | 64 | /** Set the gain param units to either V or %, depending on whether a cable is connected. */ 65 | void refreshParamQuantities() { 66 | bool normalized = true; 67 | 68 | for (int i = 0; i < 8; i++) { 69 | ParamQuantity* pq = paramQuantities[GAIN_PARAMS + i]; 70 | if (!pq) 71 | continue; 72 | 73 | if (inputs[IN_INPUTS + i].isConnected()) 74 | normalized = false; 75 | if (normalized) { 76 | pq->unit = "V"; 77 | pq->displayMultiplier = 10.f; 78 | } 79 | else { 80 | pq->unit = "%"; 81 | pq->displayMultiplier = 100.f; 82 | } 83 | } 84 | } 85 | }; 86 | 87 | 88 | struct _8vertWidget : ModuleWidget { 89 | _8vertWidget(_8vert* module) { 90 | setModule(module); 91 | setPanel(createPanel(asset::plugin(pluginInstance, "res/8vert.svg"), asset::plugin(pluginInstance, "res/8vert-dark.svg"))); 92 | 93 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 94 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 95 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 96 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 97 | 98 | addParam(createParamCentered(mm2px(Vec(20.351, 21.968)), module, _8vert::GAIN_PARAMS + 0)); 99 | addParam(createParamCentered(mm2px(Vec(20.351, 34.982)), module, _8vert::GAIN_PARAMS + 1)); 100 | addParam(createParamCentered(mm2px(Vec(20.351, 48.004)), module, _8vert::GAIN_PARAMS + 2)); 101 | addParam(createParamCentered(mm2px(Vec(20.351, 61.026)), module, _8vert::GAIN_PARAMS + 3)); 102 | addParam(createParamCentered(mm2px(Vec(20.351, 74.048)), module, _8vert::GAIN_PARAMS + 4)); 103 | addParam(createParamCentered(mm2px(Vec(20.351, 87.07)), module, _8vert::GAIN_PARAMS + 5)); 104 | addParam(createParamCentered(mm2px(Vec(20.351, 100.093)), module, _8vert::GAIN_PARAMS + 6)); 105 | addParam(createParamCentered(mm2px(Vec(20.351, 113.115)), module, _8vert::GAIN_PARAMS + 7)); 106 | 107 | addInput(createInputCentered(mm2px(Vec(7.331, 21.968)), module, _8vert::IN_INPUTS + 0)); 108 | addInput(createInputCentered(mm2px(Vec(7.331, 34.982)), module, _8vert::IN_INPUTS + 1)); 109 | addInput(createInputCentered(mm2px(Vec(7.331, 48.004)), module, _8vert::IN_INPUTS + 2)); 110 | addInput(createInputCentered(mm2px(Vec(7.331, 61.026)), module, _8vert::IN_INPUTS + 3)); 111 | addInput(createInputCentered(mm2px(Vec(7.331, 74.048)), module, _8vert::IN_INPUTS + 4)); 112 | addInput(createInputCentered(mm2px(Vec(7.331, 87.07)), module, _8vert::IN_INPUTS + 5)); 113 | addInput(createInputCentered(mm2px(Vec(7.331, 100.093)), module, _8vert::IN_INPUTS + 6)); 114 | addInput(createInputCentered(mm2px(Vec(7.331, 113.115)), module, _8vert::IN_INPUTS + 7)); 115 | 116 | addOutput(createOutputCentered(mm2px(Vec(33.371, 21.968)), module, _8vert::OUT_OUTPUTS + 0)); 117 | addOutput(createOutputCentered(mm2px(Vec(33.371, 34.982)), module, _8vert::OUT_OUTPUTS + 1)); 118 | addOutput(createOutputCentered(mm2px(Vec(33.371, 48.004)), module, _8vert::OUT_OUTPUTS + 2)); 119 | addOutput(createOutputCentered(mm2px(Vec(33.371, 61.026)), module, _8vert::OUT_OUTPUTS + 3)); 120 | addOutput(createOutputCentered(mm2px(Vec(33.371, 74.048)), module, _8vert::OUT_OUTPUTS + 4)); 121 | addOutput(createOutputCentered(mm2px(Vec(33.371, 87.07)), module, _8vert::OUT_OUTPUTS + 5)); 122 | addOutput(createOutputCentered(mm2px(Vec(33.371, 100.093)), module, _8vert::OUT_OUTPUTS + 6)); 123 | addOutput(createOutputCentered(mm2px(Vec(33.371, 113.115)), module, _8vert::OUT_OUTPUTS + 7)); 124 | } 125 | }; 126 | 127 | 128 | Model* model_8vert = createModel<_8vert, _8vertWidget>("8vert"); 129 | -------------------------------------------------------------------------------- /src/CVMix.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | using simd::float_4; 5 | 6 | 7 | struct CVMix : Module { 8 | enum ParamId { 9 | ENUMS(LEVEL_PARAMS, 3), 10 | PARAMS_LEN 11 | }; 12 | enum InputId { 13 | ENUMS(CV_INPUTS, 3), 14 | INPUTS_LEN 15 | }; 16 | enum OutputId { 17 | MIX_OUTPUT, 18 | OUTPUTS_LEN 19 | }; 20 | enum LightId { 21 | LIGHTS_LEN 22 | }; 23 | 24 | CVMix() { 25 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 26 | for (int i = 0; i < 3; i++) 27 | configParam(LEVEL_PARAMS + i, -1.f, 1.f, 0.f, string::f("Level %d", i + 1), "%", 0, 100); 28 | 29 | for (int i = 0; i < 3; i++) { 30 | configInput(CV_INPUTS + i, string::f("CV %d", i + 1)); 31 | getInputInfo(CV_INPUTS + i)->description = "Normalled to 10 V"; 32 | } 33 | 34 | configOutput(MIX_OUTPUT, "Mix"); 35 | } 36 | 37 | void process(const ProcessArgs& args) override { 38 | if (!outputs[MIX_OUTPUT].isConnected()) 39 | return; 40 | 41 | // Get number of channels 42 | int channels = 1; 43 | for (int i = 0; i < 3; i++) 44 | channels = std::max(channels, inputs[CV_INPUTS + i].getChannels()); 45 | 46 | for (int c = 0; c < channels; c += 4) { 47 | // Sum CV inputs 48 | float_4 mix = 0.f; 49 | for (int i = 0; i < 3; i++) { 50 | // Normalize inputs to 10V 51 | float_4 cv = inputs[CV_INPUTS + i].getNormalPolyVoltageSimd(10.f, c); 52 | 53 | // Apply gain 54 | cv *= params[LEVEL_PARAMS + i].getValue(); 55 | mix += cv; 56 | } 57 | 58 | // Set mix output 59 | outputs[MIX_OUTPUT].setVoltageSimd(mix, c); 60 | } 61 | outputs[MIX_OUTPUT].setChannels(channels); 62 | } 63 | }; 64 | 65 | 66 | struct CVMixWidget : ModuleWidget { 67 | CVMixWidget(CVMix* module) { 68 | setModule(module); 69 | setPanel(createPanel(asset::plugin(pluginInstance, "res/CVMix.svg"), asset::plugin(pluginInstance, "res/CVMix-dark.svg"))); 70 | 71 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 72 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 73 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 74 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 75 | 76 | addParam(createParamCentered(mm2px(Vec(7.62, 24.723)), module, CVMix::LEVEL_PARAMS + 0)); 77 | addParam(createParamCentered(mm2px(Vec(7.62, 41.327)), module, CVMix::LEVEL_PARAMS + 1)); 78 | addParam(createParamCentered(mm2px(Vec(7.62, 57.922)), module, CVMix::LEVEL_PARAMS + 2)); 79 | 80 | addInput(createInputCentered(mm2px(Vec(7.62, 76.539)), module, CVMix::CV_INPUTS + 0)); 81 | addInput(createInputCentered(mm2px(Vec(7.62, 86.699)), module, CVMix::CV_INPUTS + 1)); 82 | addInput(createInputCentered(mm2px(Vec(7.62, 96.859)), module, CVMix::CV_INPUTS + 2)); 83 | 84 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, CVMix::MIX_OUTPUT)); 85 | } 86 | }; 87 | 88 | 89 | Model* modelCVMix = createModel("CVMix"); -------------------------------------------------------------------------------- /src/Compare.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Compare : Module { 5 | enum ParamId { 6 | B_PARAM, 7 | PARAMS_LEN 8 | }; 9 | enum InputId { 10 | A_INPUT, 11 | B_INPUT, 12 | INPUTS_LEN 13 | }; 14 | enum OutputId { 15 | MAX_OUTPUT, 16 | MIN_OUTPUT, 17 | CLIP_OUTPUT, 18 | LIM_OUTPUT, 19 | CLIPGATE_OUTPUT, 20 | LIMGATE_OUTPUT, 21 | GREATER_OUTPUT, 22 | LESS_OUTPUT, 23 | OUTPUTS_LEN 24 | }; 25 | enum LightId { 26 | ENUMS(CLIP_LIGHT, 2), 27 | ENUMS(LIM_LIGHT, 2), 28 | ENUMS(GREATER_LIGHT, 2), 29 | ENUMS(LESS_LIGHT, 2), 30 | LIGHTS_LEN 31 | }; 32 | 33 | dsp::ClockDivider lightDivider; 34 | 35 | Compare() { 36 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 37 | configParam(B_PARAM, -10.f, 10.f, 0.f, "B offset", " V"); 38 | configInput(A_INPUT, "A"); 39 | configInput(B_INPUT, "B"); 40 | configOutput(MAX_OUTPUT, "Maximum"); 41 | configOutput(MIN_OUTPUT, "Minimum"); 42 | configOutput(CLIP_OUTPUT, "Clip"); 43 | configOutput(LIM_OUTPUT, "Limit"); 44 | configOutput(CLIPGATE_OUTPUT, "Clip gate"); 45 | configOutput(LIMGATE_OUTPUT, "Limit gate"); 46 | configOutput(GREATER_OUTPUT, "A>B"); 47 | configOutput(LESS_OUTPUT, "A b ? 10.f : 0.f, c); 88 | anyGreater = anyGreater || (a > b); 89 | outputs[LESS_OUTPUT].setVoltage(a < b ? 10.f : 0.f, c); 90 | anyLess = anyLess || (a < b); 91 | } 92 | 93 | outputs[MAX_OUTPUT].setChannels(channels); 94 | outputs[MIN_OUTPUT].setChannels(channels); 95 | outputs[CLIP_OUTPUT].setChannels(channels); 96 | outputs[LIM_OUTPUT].setChannels(channels); 97 | outputs[CLIPGATE_OUTPUT].setChannels(channels); 98 | outputs[LIMGATE_OUTPUT].setChannels(channels); 99 | outputs[GREATER_OUTPUT].setChannels(channels); 100 | outputs[LESS_OUTPUT].setChannels(channels); 101 | 102 | if (lightDivider.process()) { 103 | float lightTime = args.sampleTime * lightDivider.getDivision(); 104 | lights[CLIP_LIGHT + 0].setBrightnessSmooth(anyClipped && channels <= 1, lightTime); 105 | lights[CLIP_LIGHT + 1].setBrightnessSmooth(anyClipped && channels > 1, lightTime); 106 | lights[LIM_LIGHT + 0].setBrightnessSmooth(anyLimmed && channels <= 1, lightTime); 107 | lights[LIM_LIGHT + 1].setBrightnessSmooth(anyLimmed && channels > 1, lightTime); 108 | lights[GREATER_LIGHT + 0].setBrightnessSmooth(anyGreater && channels <= 1, lightTime); 109 | lights[GREATER_LIGHT + 1].setBrightnessSmooth(anyGreater && channels > 1, lightTime); 110 | lights[LESS_LIGHT + 0].setBrightnessSmooth(anyLess && channels <= 1, lightTime); 111 | lights[LESS_LIGHT + 1].setBrightnessSmooth(anyLess && channels > 1, lightTime); 112 | } 113 | } 114 | }; 115 | 116 | 117 | struct CompareWidget : ModuleWidget { 118 | CompareWidget(Compare* module) { 119 | setModule(module); 120 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Compare.svg"), asset::plugin(pluginInstance, "res/Compare-dark.svg"))); 121 | 122 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 123 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 124 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 125 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 126 | 127 | addParam(createParamCentered(mm2px(Vec(12.646, 26.755)), module, Compare::B_PARAM)); 128 | 129 | addInput(createInputCentered(mm2px(Vec(7.299, 52.31)), module, Compare::A_INPUT)); 130 | addInput(createInputCentered(mm2px(Vec(18.136, 52.31)), module, Compare::B_INPUT)); 131 | 132 | addOutput(createOutputCentered(mm2px(Vec(7.297, 67.53)), module, Compare::MAX_OUTPUT)); 133 | addOutput(createOutputCentered(mm2px(Vec(18.134, 67.53)), module, Compare::MIN_OUTPUT)); 134 | addOutput(createOutputCentered(mm2px(Vec(7.297, 82.732)), module, Compare::CLIP_OUTPUT)); 135 | addOutput(createOutputCentered(mm2px(Vec(18.134, 82.732)), module, Compare::LIM_OUTPUT)); 136 | addOutput(createOutputCentered(mm2px(Vec(7.297, 97.958)), module, Compare::CLIPGATE_OUTPUT)); 137 | addOutput(createOutputCentered(mm2px(Vec(18.134, 97.958)), module, Compare::LIMGATE_OUTPUT)); 138 | addOutput(createOutputCentered(mm2px(Vec(7.297, 113.115)), module, Compare::GREATER_OUTPUT)); 139 | addOutput(createOutputCentered(mm2px(Vec(18.134, 113.115)), module, Compare::LESS_OUTPUT)); 140 | 141 | addChild(createLightCentered>>(mm2px(Vec(11.027, 94.233)), module, Compare::CLIP_LIGHT)); 142 | addChild(createLightCentered>>(mm2px(Vec(21.864, 94.233)), module, Compare::LIM_LIGHT)); 143 | addChild(createLightCentered>>(mm2px(Vec(11.027, 109.393)), module, Compare::GREATER_LIGHT)); 144 | addChild(createLightCentered>>(mm2px(Vec(21.864, 109.393)), module, Compare::LESS_LIGHT)); 145 | } 146 | }; 147 | 148 | 149 | Model* modelCompare = createModel("Compare"); -------------------------------------------------------------------------------- /src/Fade.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | using simd::float_4; 5 | 6 | 7 | struct Fade : Module { 8 | enum ParamId { 9 | CROSSFADE_PARAM, 10 | CROSSFADE_CV_PARAM, 11 | PARAMS_LEN 12 | }; 13 | enum InputId { 14 | CROSSFADE_INPUT, 15 | IN1_INPUT, 16 | IN2_INPUT, 17 | INPUTS_LEN 18 | }; 19 | enum OutputId { 20 | OUT1_OUTPUT, 21 | OUT2_OUTPUT, 22 | OUTPUTS_LEN 23 | }; 24 | enum LightId { 25 | LIGHTS_LEN 26 | }; 27 | 28 | /** 29 | 0 linear 30 | 1 -3dB 31 | */ 32 | int panLaw = 0; 33 | 34 | Fade() { 35 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 36 | configParam(CROSSFADE_PARAM, 0.f, 1.f, 0.5f, "Crossfade", "%", 0, 100); 37 | configParam(CROSSFADE_CV_PARAM, -1.f, 1.f, 0.f, "Crossfade CV", "%", 0, 100); 38 | configInput(CROSSFADE_INPUT, "Crossfade"); 39 | configInput(IN1_INPUT, "Ch 1"); 40 | configInput(IN2_INPUT, "Ch 2"); 41 | configOutput(OUT1_OUTPUT, "Ch 1"); 42 | configOutput(OUT2_OUTPUT, "Ch 2"); 43 | 44 | configBypass(IN1_INPUT, OUT1_OUTPUT); 45 | configBypass(IN2_INPUT, OUT2_OUTPUT); 46 | } 47 | 48 | void onReset(const ResetEvent& e) override { 49 | Module::onReset(e); 50 | panLaw = 0; 51 | } 52 | 53 | void process(const ProcessArgs& args) override { 54 | if (!outputs[OUT1_OUTPUT].isConnected() && !outputs[OUT2_OUTPUT].isConnected()) 55 | return; 56 | 57 | int channels = std::max({1, inputs[IN1_INPUT].getChannels(), inputs[IN2_INPUT].getChannels()}); 58 | 59 | for (int c = 0; c < channels; c += 4) { 60 | // Get crossfade amount 61 | float_4 crossfade = params[CROSSFADE_PARAM].getValue(); 62 | crossfade += inputs[CROSSFADE_INPUT].getPolyVoltageSimd(c) / 10.f * params[CROSSFADE_CV_PARAM].getValue(); 63 | crossfade = clamp(crossfade, 0.f, 1.f); 64 | float_4 crossfade1 = 1.f - crossfade; 65 | 66 | // Apply sqrt pan law 67 | if (panLaw == 1) { 68 | crossfade = (simd::sqrt(crossfade)); 69 | crossfade1 = simd::sqrt(crossfade1); 70 | } 71 | 72 | // Get inputs 73 | float_4 in1 = inputs[IN1_INPUT].getPolyVoltageSimd(c); 74 | float_4 in2 = inputs[IN2_INPUT].getPolyVoltageSimd(c); 75 | 76 | // Calculate outputs 77 | float_4 out1 = crossfade1 * in1 + crossfade * in2; 78 | float_4 out2 = crossfade * in1 + crossfade1 * in2; 79 | outputs[OUT1_OUTPUT].setVoltageSimd(out1, c); 80 | outputs[OUT2_OUTPUT].setVoltageSimd(out2, c); 81 | } 82 | 83 | outputs[OUT1_OUTPUT].setChannels(channels); 84 | outputs[OUT2_OUTPUT].setChannels(channels); 85 | } 86 | 87 | json_t* dataToJson() override { 88 | json_t* rootJ = json_object(); 89 | json_object_set_new(rootJ, "panLaw", json_integer(panLaw)); 90 | return rootJ; 91 | } 92 | 93 | void dataFromJson(json_t* rootJ) override { 94 | json_t* panLawJ = json_object_get(rootJ, "panLaw"); 95 | if (panLawJ) 96 | panLaw = json_integer_value(panLawJ); 97 | } 98 | }; 99 | 100 | 101 | struct FadeWidget : ModuleWidget { 102 | FadeWidget(Fade* module) { 103 | setModule(module); 104 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Fade.svg"), asset::plugin(pluginInstance, "res/Fade-dark.svg"))); 105 | 106 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 107 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 108 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 109 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 110 | 111 | addParam(createParamCentered(mm2px(Vec(7.62, 24.723)), module, Fade::CROSSFADE_PARAM)); 112 | addParam(createParamCentered(mm2px(Vec(7.62, 37.064)), module, Fade::CROSSFADE_CV_PARAM)); 113 | 114 | addInput(createInputCentered(mm2px(Vec(7.62, 52.987)), module, Fade::CROSSFADE_INPUT)); 115 | addInput(createInputCentered(mm2px(Vec(7.62, 67.53)), module, Fade::IN1_INPUT)); 116 | addInput(createInputCentered(mm2px(Vec(7.62, 82.732)), module, Fade::IN2_INPUT)); 117 | 118 | addOutput(createOutputCentered(mm2px(Vec(7.62, 97.923)), module, Fade::OUT1_OUTPUT)); 119 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Fade::OUT2_OUTPUT)); 120 | } 121 | 122 | void appendContextMenu(Menu* menu) override { 123 | Fade* module = getModule(); 124 | 125 | menu->addChild(new MenuSeparator); 126 | 127 | menu->addChild(createIndexPtrSubmenuItem("Pan law", {"-6 dB (linear)", "-3 dB"}, &module->panLaw)); 128 | } 129 | }; 130 | 131 | 132 | Model* modelFade = createModel("Fade"); -------------------------------------------------------------------------------- /src/Logic.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Logic : Module { 5 | enum ParamId { 6 | B_PARAM, 7 | PARAMS_LEN 8 | }; 9 | enum InputId { 10 | A_INPUT, 11 | B_INPUT, 12 | INPUTS_LEN 13 | }; 14 | enum OutputId { 15 | NOTA_OUTPUT, 16 | NOTB_OUTPUT, 17 | OR_OUTPUT, 18 | NOR_OUTPUT, 19 | AND_OUTPUT, 20 | NAND_OUTPUT, 21 | XOR_OUTPUT, 22 | XNOR_OUTPUT, 23 | OUTPUTS_LEN 24 | }; 25 | enum LightId { 26 | B_BUTTON_LIGHT, 27 | ENUMS(NOTA_LIGHT, 2), 28 | ENUMS(NOTB_LIGHT, 2), 29 | ENUMS(OR_LIGHT, 2), 30 | ENUMS(NOR_LIGHT, 2), 31 | ENUMS(AND_LIGHT, 2), 32 | ENUMS(NAND_LIGHT, 2), 33 | ENUMS(XOR_LIGHT, 2), 34 | ENUMS(XNOR_LIGHT, 2), 35 | LIGHTS_LEN 36 | }; 37 | 38 | dsp::ClockDivider lightDivider; 39 | 40 | Logic() { 41 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 42 | configButton(B_PARAM, "B"); 43 | configInput(A_INPUT, "A"); 44 | configInput(B_INPUT, "B"); 45 | configOutput(NOTA_OUTPUT, "NOT A"); 46 | configOutput(NOTB_OUTPUT, "NOT B"); 47 | configOutput(OR_OUTPUT, "OR"); 48 | configOutput(NOR_OUTPUT, "NOR"); 49 | configOutput(AND_OUTPUT, "AND"); 50 | configOutput(NAND_OUTPUT, "NAND"); 51 | configOutput(XOR_OUTPUT, "XOR"); 52 | configOutput(XNOR_OUTPUT, "XNOR"); 53 | 54 | lightDivider.setDivision(32); 55 | } 56 | 57 | void process(const ProcessArgs& args) override { 58 | int channels = std::max({1, inputs[A_INPUT].getChannels(), inputs[B_INPUT].getChannels()}); 59 | 60 | bool bPush = params[B_PARAM].getValue() > 0.f; 61 | bool anyState[8] = {}; 62 | 63 | for (int c = 0; c < channels; c++) { 64 | bool a = inputs[A_INPUT].getPolyVoltage(c) >= 1.f; 65 | bool b = bPush || inputs[B_INPUT].getPolyVoltage(c) >= 1.f; 66 | 67 | bool states[8] = { 68 | !a, // NOTA 69 | !b, // NOTB 70 | a || b, // OR 71 | !(a || b), // NOR 72 | a && b, // AND 73 | !(a && b), // NAND 74 | a != b, // XOR 75 | a == b, // XNOR 76 | }; 77 | 78 | for (int i = 0; i < 8; i++) { 79 | outputs[NOTA_OUTPUT + i].setVoltage(states[i] ? 10.f : 0.f, c); 80 | if (states[i]) 81 | anyState[i] = true; 82 | } 83 | } 84 | 85 | for (int i = 0; i < 8; i++) { 86 | outputs[NOTA_OUTPUT + i].setChannels(channels); 87 | } 88 | 89 | // Set lights 90 | if (lightDivider.process()) { 91 | float lightTime = args.sampleTime * lightDivider.getDivision(); 92 | lights[B_BUTTON_LIGHT].setBrightness(bPush); 93 | for (int i = 0; i < 8; i++) { 94 | lights[NOTA_LIGHT + 2 * i + 0].setBrightnessSmooth(anyState[i] && channels == 1, lightTime); 95 | lights[NOTA_LIGHT + 2 * i + 1].setBrightnessSmooth(anyState[i] && channels > 1, lightTime); 96 | } 97 | } 98 | } 99 | }; 100 | 101 | 102 | struct LogicWidget : ModuleWidget { 103 | LogicWidget(Logic* module) { 104 | setModule(module); 105 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Logic.svg"), asset::plugin(pluginInstance, "res/Logic-dark.svg"))); 106 | 107 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 108 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 109 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 110 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 111 | 112 | addParam(createLightParamCentered>>(mm2px(Vec(12.7, 26.755)), module, Logic::B_PARAM, Logic::B_BUTTON_LIGHT)); 113 | 114 | addInput(createInputCentered(mm2px(Vec(7.299, 52.31)), module, Logic::A_INPUT)); 115 | addInput(createInputCentered(mm2px(Vec(18.136, 52.31)), module, Logic::B_INPUT)); 116 | 117 | addOutput(createOutputCentered(mm2px(Vec(7.297, 67.53)), module, Logic::NOTA_OUTPUT)); 118 | addOutput(createOutputCentered(mm2px(Vec(18.134, 67.53)), module, Logic::NOTB_OUTPUT)); 119 | addOutput(createOutputCentered(mm2px(Vec(7.297, 82.732)), module, Logic::OR_OUTPUT)); 120 | addOutput(createOutputCentered(mm2px(Vec(18.134, 82.732)), module, Logic::NOR_OUTPUT)); 121 | addOutput(createOutputCentered(mm2px(Vec(7.297, 97.958)), module, Logic::AND_OUTPUT)); 122 | addOutput(createOutputCentered(mm2px(Vec(18.134, 97.958)), module, Logic::NAND_OUTPUT)); 123 | addOutput(createOutputCentered(mm2px(Vec(7.297, 113.115)), module, Logic::XOR_OUTPUT)); 124 | addOutput(createOutputCentered(mm2px(Vec(18.134, 113.115)), module, Logic::XNOR_OUTPUT)); 125 | 126 | addChild(createLightCentered>>(mm2px(Vec(11.027, 63.805)), module, Logic::NOTA_LIGHT)); 127 | addChild(createLightCentered>>(mm2px(Vec(21.864, 63.805)), module, Logic::NOTB_LIGHT)); 128 | addChild(createLightCentered>>(mm2px(Vec(11.027, 79.007)), module, Logic::OR_LIGHT)); 129 | addChild(createLightCentered>>(mm2px(Vec(21.864, 79.007)), module, Logic::NOR_LIGHT)); 130 | addChild(createLightCentered>>(mm2px(Vec(11.027, 94.233)), module, Logic::AND_LIGHT)); 131 | addChild(createLightCentered>>(mm2px(Vec(21.864, 94.233)), module, Logic::NAND_LIGHT)); 132 | addChild(createLightCentered>>(mm2px(Vec(11.027, 109.393)), module, Logic::XOR_LIGHT)); 133 | addChild(createLightCentered>>(mm2px(Vec(21.864, 109.393)), module, Logic::XNOR_LIGHT)); 134 | } 135 | }; 136 | 137 | 138 | Model* modelLogic = createModel("Logic"); -------------------------------------------------------------------------------- /src/Merge.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Merge : Module { 5 | enum ParamIds { 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | ENUMS(MONO_INPUTS, 16), 10 | NUM_INPUTS 11 | }; 12 | enum OutputIds { 13 | POLY_OUTPUT, 14 | NUM_OUTPUTS 15 | }; 16 | enum LightIds { 17 | ENUMS(CHANNEL_LIGHTS, 16), 18 | NUM_LIGHTS 19 | }; 20 | 21 | int channels = -1; 22 | int automaticChannels = 0; 23 | 24 | Merge() { 25 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 26 | for (int i = 0; i < 16; i++) 27 | configInput(MONO_INPUTS + i, string::f("Channel %d", i + 1)); 28 | configOutput(POLY_OUTPUT, "Polyphonic"); 29 | 30 | onReset(); 31 | } 32 | 33 | void onReset() override { 34 | channels = -1; 35 | } 36 | 37 | void process(const ProcessArgs& args) override { 38 | int lastChannel = -1; 39 | for (int c = 0; c < 16; c++) { 40 | float v = 0.f; 41 | if (inputs[MONO_INPUTS + c].isConnected()) { 42 | lastChannel = c; 43 | v = inputs[MONO_INPUTS + c].getVoltage(); 44 | } 45 | outputs[POLY_OUTPUT].setVoltage(v, c); 46 | } 47 | automaticChannels = lastChannel + 1; 48 | 49 | // In order to allow 0 channels, modify `channels` directly instead of using `setChannels()` 50 | outputs[POLY_OUTPUT].channels = (channels >= 0) ? channels : automaticChannels; 51 | } 52 | 53 | json_t* dataToJson() override { 54 | json_t* rootJ = json_object(); 55 | json_object_set_new(rootJ, "channels", json_integer(channels)); 56 | return rootJ; 57 | } 58 | 59 | void dataFromJson(json_t* rootJ) override { 60 | json_t* channelsJ = json_object_get(rootJ, "channels"); 61 | if (channelsJ) 62 | channels = json_integer_value(channelsJ); 63 | } 64 | }; 65 | 66 | 67 | struct MergeChannelDisplay : ChannelDisplay { 68 | Merge* module; 69 | void step() override { 70 | int channels = 16; 71 | if (module) { 72 | channels = module->channels; 73 | if (channels < 0) 74 | channels = module->automaticChannels; 75 | } 76 | 77 | text = string::f("%d", channels); 78 | } 79 | }; 80 | 81 | 82 | struct MergeWidget : ModuleWidget { 83 | MergeWidget(Merge* module) { 84 | setModule(module); 85 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Merge.svg"), asset::plugin(pluginInstance, "res/Merge-dark.svg"))); 86 | 87 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 88 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 89 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 90 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 91 | 92 | addInput(createInputCentered(mm2px(Vec(7.281, 41.995)), module, Merge::MONO_INPUTS + 0)); 93 | addInput(createInputCentered(mm2px(Vec(7.281, 52.155)), module, Merge::MONO_INPUTS + 1)); 94 | addInput(createInputCentered(mm2px(Vec(7.281, 62.315)), module, Merge::MONO_INPUTS + 2)); 95 | addInput(createInputCentered(mm2px(Vec(7.281, 72.475)), module, Merge::MONO_INPUTS + 3)); 96 | addInput(createInputCentered(mm2px(Vec(7.281, 82.635)), module, Merge::MONO_INPUTS + 4)); 97 | addInput(createInputCentered(mm2px(Vec(7.281, 92.795)), module, Merge::MONO_INPUTS + 5)); 98 | addInput(createInputCentered(mm2px(Vec(7.281, 102.955)), module, Merge::MONO_INPUTS + 6)); 99 | addInput(createInputCentered(mm2px(Vec(7.281, 113.115)), module, Merge::MONO_INPUTS + 7)); 100 | addInput(createInputCentered(mm2px(Vec(18.119, 41.995)), module, Merge::MONO_INPUTS + 8)); 101 | addInput(createInputCentered(mm2px(Vec(18.119, 52.155)), module, Merge::MONO_INPUTS + 9)); 102 | addInput(createInputCentered(mm2px(Vec(18.119, 62.315)), module, Merge::MONO_INPUTS + 10)); 103 | addInput(createInputCentered(mm2px(Vec(18.119, 72.475)), module, Merge::MONO_INPUTS + 11)); 104 | addInput(createInputCentered(mm2px(Vec(18.119, 82.635)), module, Merge::MONO_INPUTS + 12)); 105 | addInput(createInputCentered(mm2px(Vec(18.119, 92.795)), module, Merge::MONO_INPUTS + 13)); 106 | addInput(createInputCentered(mm2px(Vec(18.119, 102.955)), module, Merge::MONO_INPUTS + 14)); 107 | addInput(createInputCentered(mm2px(Vec(18.119, 113.115)), module, Merge::MONO_INPUTS + 15)); 108 | 109 | addOutput(createOutputCentered(mm2px(Vec(7.281, 21.967)), module, Merge::POLY_OUTPUT)); 110 | 111 | MergeChannelDisplay* display = createWidget(mm2px(Vec(14.02, 18.611))); 112 | display->box.size = mm2px(Vec(8.197, 8.197)); 113 | display->module = module; 114 | addChild(display); 115 | } 116 | 117 | void appendContextMenu(Menu* menu) override { 118 | Merge* module = dynamic_cast(this->module); 119 | 120 | menu->addChild(new MenuSeparator); 121 | 122 | std::vector channelLabels; 123 | channelLabels.push_back(string::f("Automatic (%d)", module->automaticChannels)); 124 | for (int i = 0; i <= 16; i++) { 125 | channelLabels.push_back(string::f("%d", i)); 126 | } 127 | menu->addChild(createIndexSubmenuItem("Channels", channelLabels, 128 | [=]() {return module->channels + 1;}, 129 | [=](int i) {module->channels = i - 1;} 130 | )); 131 | } 132 | }; 133 | 134 | 135 | Model* modelMerge = createModel("Merge"); 136 | -------------------------------------------------------------------------------- /src/MidSide.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct MidSide : Module { 5 | enum ParamIds { 6 | ENC_WIDTH_PARAM, 7 | DEC_WIDTH_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | ENC_WIDTH_INPUT, 12 | ENC_LEFT_INPUT, 13 | ENC_RIGHT_INPUT, 14 | DEC_WIDTH_INPUT, 15 | DEC_MID_INPUT, 16 | DEC_SIDE_INPUT, 17 | NUM_INPUTS 18 | }; 19 | enum OutputIds { 20 | ENC_MID_OUTPUT, 21 | ENC_SIDE_OUTPUT, 22 | DEC_LEFT_OUTPUT, 23 | DEC_RIGHT_OUTPUT, 24 | NUM_OUTPUTS 25 | }; 26 | enum LightIds { 27 | NUM_LIGHTS 28 | }; 29 | 30 | MidSide() { 31 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 32 | configParam(ENC_WIDTH_PARAM, 0.f, 2.f, 1.f, "Encoder width", "%", 0, 100); 33 | configParam(DEC_WIDTH_PARAM, 0.f, 2.f, 1.f, "Decoder width", "%", 0, 100); 34 | configInput(ENC_WIDTH_INPUT, "Encoder width"); 35 | configInput(ENC_LEFT_INPUT, "Encoder left"); 36 | configInput(ENC_RIGHT_INPUT, "Encoder right"); 37 | configInput(DEC_WIDTH_INPUT, "Decoder width"); 38 | configInput(DEC_MID_INPUT, "Decoder mid"); 39 | configInput(DEC_SIDE_INPUT, "Decoder side"); 40 | configOutput(ENC_MID_OUTPUT, "Encoder mid"); 41 | configOutput(ENC_SIDE_OUTPUT, "Encoder side"); 42 | configOutput(DEC_LEFT_OUTPUT, "Decoder left"); 43 | configOutput(DEC_RIGHT_OUTPUT, "Decoder right"); 44 | } 45 | 46 | void process(const ProcessArgs& args) override { 47 | using simd::float_4; 48 | 49 | // Encoder 50 | int channels = std::max(inputs[ENC_LEFT_INPUT].getChannels(), inputs[ENC_RIGHT_INPUT].getChannels()); 51 | 52 | outputs[ENC_MID_OUTPUT].setChannels(channels); 53 | outputs[ENC_SIDE_OUTPUT].setChannels(channels); 54 | 55 | for (int c = 0; c < channels; c += 4) { 56 | float_4 width = params[ENC_WIDTH_PARAM].getValue(); 57 | width += inputs[ENC_WIDTH_INPUT].getPolyVoltageSimd(c) / 10 * 2; 58 | width = simd::fmax(width, 0.f); 59 | float_4 left = inputs[ENC_LEFT_INPUT].getVoltageSimd(c); 60 | float_4 right = inputs[ENC_RIGHT_INPUT].getVoltageSimd(c); 61 | float_4 mid = (left + right) / 2; 62 | float_4 side = (left - right) / 2 * width; 63 | outputs[ENC_MID_OUTPUT].setVoltageSimd(mid, c); 64 | outputs[ENC_SIDE_OUTPUT].setVoltageSimd(side, c); 65 | } 66 | 67 | // Decoder 68 | if (inputs[DEC_MID_INPUT].isConnected() || inputs[DEC_SIDE_INPUT].isConnected()) 69 | channels = std::max(inputs[DEC_MID_INPUT].getChannels(), inputs[DEC_SIDE_INPUT].getChannels()); 70 | 71 | outputs[DEC_LEFT_OUTPUT].setChannels(channels); 72 | outputs[DEC_RIGHT_OUTPUT].setChannels(channels); 73 | 74 | for (int c = 0; c < channels; c += 4) { 75 | float_4 width = params[DEC_WIDTH_PARAM].getValue(); 76 | width += inputs[DEC_WIDTH_INPUT].getPolyVoltageSimd(c) / 10 * 2; 77 | width = simd::fmax(width, 0.f); 78 | float_4 mid = outputs[ENC_MID_OUTPUT].getVoltageSimd(c); 79 | float_4 side = outputs[ENC_SIDE_OUTPUT].getVoltageSimd(c); 80 | mid = inputs[DEC_MID_INPUT].getNormalVoltageSimd(mid, c); 81 | side = inputs[DEC_SIDE_INPUT].getNormalVoltageSimd(side, c); 82 | float_4 left = mid + side * width; 83 | float_4 right = mid - side * width; 84 | outputs[DEC_LEFT_OUTPUT].setVoltageSimd(left, c); 85 | outputs[DEC_RIGHT_OUTPUT].setVoltageSimd(right, c); 86 | } 87 | } 88 | }; 89 | 90 | 91 | struct MidSideWidget : ModuleWidget { 92 | MidSideWidget(MidSide* module) { 93 | setModule(module); 94 | setPanel(createPanel(asset::plugin(pluginInstance, "res/MidSide.svg"), asset::plugin(pluginInstance, "res/MidSide-dark.svg"))); 95 | 96 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 97 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 98 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 99 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 100 | 101 | addParam(createParamCentered(mm2px(Vec(7.285, 25.203)), module, MidSide::ENC_WIDTH_PARAM)); 102 | addParam(createParamCentered(mm2px(Vec(7.285, 80.583)), module, MidSide::DEC_WIDTH_PARAM)); 103 | 104 | addInput(createInputCentered(mm2px(Vec(18.122, 25.142)), module, MidSide::ENC_WIDTH_INPUT)); 105 | addInput(createInputCentered(mm2px(Vec(7.285, 41.373)), module, MidSide::ENC_LEFT_INPUT)); 106 | addInput(createInputCentered(mm2px(Vec(18.122, 41.373)), module, MidSide::ENC_RIGHT_INPUT)); 107 | addInput(createInputCentered(mm2px(Vec(18.122, 80.603)), module, MidSide::DEC_WIDTH_INPUT)); 108 | addInput(createInputCentered(mm2px(Vec(7.285, 96.859)), module, MidSide::DEC_MID_INPUT)); 109 | addInput(createInputCentered(mm2px(Vec(18.122, 96.859)), module, MidSide::DEC_SIDE_INPUT)); 110 | 111 | addOutput(createOutputCentered(mm2px(Vec(7.285, 57.679)), module, MidSide::ENC_MID_OUTPUT)); 112 | addOutput(createOutputCentered(mm2px(Vec(18.122, 57.679)), module, MidSide::ENC_SIDE_OUTPUT)); 113 | addOutput(createOutputCentered(mm2px(Vec(7.285, 113.115)), module, MidSide::DEC_LEFT_OUTPUT)); 114 | addOutput(createOutputCentered(mm2px(Vec(18.122, 113.115)), module, MidSide::DEC_RIGHT_OUTPUT)); 115 | } 116 | }; 117 | 118 | 119 | Model* modelMidSide = createModel("MidSide"); -------------------------------------------------------------------------------- /src/Mixer.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | using simd::float_4; 5 | 6 | 7 | struct Mixer : Module { 8 | enum ParamId { 9 | LEVEL_PARAM, 10 | PARAMS_LEN 11 | }; 12 | enum InputId { 13 | ENUMS(IN_INPUTS, 6), 14 | INPUTS_LEN 15 | }; 16 | enum OutputId { 17 | OUT_OUTPUT, 18 | OUTPUTS_LEN 19 | }; 20 | enum LightId { 21 | LIGHTS_LEN 22 | }; 23 | 24 | bool invert = false; 25 | bool average = false; 26 | 27 | Mixer() { 28 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 29 | configParam(LEVEL_PARAM, 0.f, 1.f, 1.f, "Level", "%", 0, 100); 30 | for (int i = 0; i < 6; i++) 31 | configInput(IN_INPUTS + i, string::f("Channel %d", i + 1)); 32 | configOutput(OUT_OUTPUT, "Mix"); 33 | } 34 | 35 | void process(const ProcessArgs& args) override { 36 | // Get number of channels and number of connected inputs 37 | int channels = 1; 38 | int connected = 0; 39 | for (int i = 0; i < 6; i++) { 40 | channels = std::max(channels, inputs[IN_INPUTS + i].getChannels()); 41 | if (inputs[IN_INPUTS + i].isConnected()) 42 | connected++; 43 | } 44 | 45 | float gain = params[LEVEL_PARAM].getValue(); 46 | // Invert 47 | if (invert) { 48 | gain *= -1.f; 49 | } 50 | // Average 51 | if (average) { 52 | gain /= std::max(1, connected); 53 | } 54 | 55 | // Iterate polyphonic channels 56 | for (int c = 0; c < channels; c += 4) { 57 | float_4 out = 0.f; 58 | // Mix input 59 | for (int i = 0; i < 6; i++) { 60 | out += inputs[IN_INPUTS + i].getVoltageSimd(c); 61 | } 62 | 63 | // Apply gain 64 | out *= gain; 65 | 66 | // Set output 67 | outputs[OUT_OUTPUT].setVoltageSimd(out, c); 68 | } 69 | 70 | outputs[OUT_OUTPUT].setChannels(channels); 71 | } 72 | 73 | json_t* dataToJson() override { 74 | json_t* rootJ = json_object(); 75 | // average 76 | json_object_set_new(rootJ, "average", json_boolean(average)); 77 | // invert 78 | json_object_set_new(rootJ, "invert", json_boolean(invert)); 79 | return rootJ; 80 | } 81 | 82 | void dataFromJson(json_t* rootJ) override { 83 | // average 84 | json_t* averageJ = json_object_get(rootJ, "average"); 85 | if (averageJ) 86 | average = json_boolean_value(averageJ); 87 | // invert 88 | json_t* invertJ = json_object_get(rootJ, "invert"); 89 | if (invertJ) 90 | invert = json_boolean_value(invertJ); 91 | } 92 | }; 93 | 94 | 95 | struct MixerWidget : ModuleWidget { 96 | MixerWidget(Mixer* module) { 97 | setModule(module); 98 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Mixer.svg"), asset::plugin(pluginInstance, "res/Mixer-dark.svg"))); 99 | 100 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 101 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 102 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 103 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 104 | 105 | addParam(createParamCentered(mm2px(Vec(7.62, 24.723)), module, Mixer::LEVEL_PARAM)); 106 | 107 | addInput(createInputCentered(mm2px(Vec(7.62, 46.059)), module, Mixer::IN_INPUTS + 0)); 108 | addInput(createInputCentered(mm2px(Vec(7.62, 56.219)), module, Mixer::IN_INPUTS + 1)); 109 | addInput(createInputCentered(mm2px(Vec(7.62, 66.379)), module, Mixer::IN_INPUTS + 2)); 110 | addInput(createInputCentered(mm2px(Vec(7.62, 76.539)), module, Mixer::IN_INPUTS + 3)); 111 | addInput(createInputCentered(mm2px(Vec(7.62, 86.699)), module, Mixer::IN_INPUTS + 4)); 112 | addInput(createInputCentered(mm2px(Vec(7.62, 96.859)), module, Mixer::IN_INPUTS + 5)); 113 | 114 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Mixer::OUT_OUTPUT)); 115 | } 116 | 117 | void appendContextMenu(Menu* menu) override { 118 | Mixer* module = dynamic_cast(this->module); 119 | assert(module); 120 | 121 | menu->addChild(new MenuSeparator); 122 | 123 | menu->addChild(createBoolPtrMenuItem("Invert output", "", &module->invert)); 124 | menu->addChild(createBoolPtrMenuItem("Average voltages", "", &module->average)); 125 | } 126 | }; 127 | 128 | 129 | Model* modelMixer = createModel("Mixer"); -------------------------------------------------------------------------------- /src/Mult.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Mult : Module { 5 | enum ParamId { 6 | PARAMS_LEN 7 | }; 8 | enum InputId { 9 | MULT_INPUT, 10 | INPUTS_LEN 11 | }; 12 | enum OutputId { 13 | ENUMS(MULT_OUTPUTS, 8), 14 | OUTPUTS_LEN 15 | }; 16 | enum LightId { 17 | LIGHTS_LEN 18 | }; 19 | 20 | Mult() { 21 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 22 | configInput(MULT_INPUT, "Mult"); 23 | for (int i = 0; i < 8; i++) 24 | configOutput(MULT_OUTPUTS + i, string::f("Mult %d", i + 1)); 25 | } 26 | 27 | void process(const ProcessArgs& args) override { 28 | int channels = std::max(1, inputs[MULT_INPUT].getChannels()); 29 | 30 | // Copy input to outputs 31 | for (int i = 0; i < 8; i++) { 32 | outputs[MULT_OUTPUTS + i].setChannels(channels); 33 | outputs[MULT_OUTPUTS + i].writeVoltages(inputs[MULT_INPUT].getVoltages()); 34 | } 35 | } 36 | }; 37 | 38 | 39 | struct MultWidget : ModuleWidget { 40 | MultWidget(Mult* module) { 41 | setModule(module); 42 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Mult.svg"), asset::plugin(pluginInstance, "res/Mult-dark.svg"))); 43 | 44 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 45 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 46 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 47 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 48 | 49 | addInput(createInputCentered(mm2px(Vec(7.62, 22.001)), module, Mult::MULT_INPUT)); 50 | 51 | addOutput(createOutputCentered(mm2px(Vec(7.62, 42.017)), module, Mult::MULT_OUTPUTS + 0)); 52 | addOutput(createOutputCentered(mm2px(Vec(7.62, 52.155)), module, Mult::MULT_OUTPUTS + 1)); 53 | addOutput(createOutputCentered(mm2px(Vec(7.62, 62.315)), module, Mult::MULT_OUTPUTS + 2)); 54 | addOutput(createOutputCentered(mm2px(Vec(7.62, 72.475)), module, Mult::MULT_OUTPUTS + 3)); 55 | addOutput(createOutputCentered(mm2px(Vec(7.62, 82.635)), module, Mult::MULT_OUTPUTS + 4)); 56 | addOutput(createOutputCentered(mm2px(Vec(7.62, 92.795)), module, Mult::MULT_OUTPUTS + 5)); 57 | addOutput(createOutputCentered(mm2px(Vec(7.62, 102.955)), module, Mult::MULT_OUTPUTS + 6)); 58 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Mult::MULT_OUTPUTS + 7)); 59 | } 60 | }; 61 | 62 | 63 | Model* modelMult = createModel("Mult"); -------------------------------------------------------------------------------- /src/Mutes.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Mutes : Module { 5 | enum ParamIds { 6 | ENUMS(MUTE_PARAMS, 10), 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | ENUMS(IN_INPUTS, 10), 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | ENUMS(OUT_OUTPUTS, 10), 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | ENUMS(MUTE_LIGHTS, 10), 19 | NUM_LIGHTS 20 | }; 21 | 22 | Mutes() { 23 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 24 | for (int i = 0; i < 10; i++) { 25 | configSwitch(MUTE_PARAMS + i, 0.f, 1.f, 0.f, string::f("Row %d mute", i + 1)); 26 | configInput(IN_INPUTS + i, string::f("Row %d", i + 1)); 27 | configOutput(OUT_OUTPUTS + i, string::f("Row %d", i + 1)); 28 | configBypass(IN_INPUTS + i, OUT_OUTPUTS + i); 29 | } 30 | } 31 | 32 | void process(const ProcessArgs& args) override { 33 | const float zero[16] = {}; 34 | float out[16] = {}; 35 | 36 | // Iterate rows 37 | for (int i = 0; i < 10; i++) { 38 | int channels = 1; 39 | bool mute = params[MUTE_PARAMS + i].getValue() > 0.f; 40 | 41 | // Get input 42 | // Inputs are normalized to the input above it, so only set if connected 43 | if (inputs[IN_INPUTS + i].isConnected()) { 44 | channels = inputs[IN_INPUTS + i].getChannels(); 45 | inputs[IN_INPUTS + i].readVoltages(out); 46 | } 47 | 48 | // Set output 49 | if (outputs[OUT_OUTPUTS + i].isConnected()) { 50 | outputs[OUT_OUTPUTS + i].setChannels(channels); 51 | outputs[OUT_OUTPUTS + i].writeVoltages(mute ? zero : out); 52 | } 53 | 54 | // Set light 55 | lights[MUTE_LIGHTS + i].setBrightness(mute); 56 | } 57 | } 58 | 59 | void dataFromJson(json_t* rootJ) override { 60 | // In <2.0, states were stored in data 61 | json_t* statesJ = json_object_get(rootJ, "states"); 62 | if (statesJ) { 63 | for (int i = 0; i < 10; i++) { 64 | json_t* stateJ = json_array_get(statesJ, i); 65 | if (stateJ) 66 | params[MUTE_PARAMS + i].setValue(!json_boolean_value(stateJ)); 67 | } 68 | } 69 | } 70 | 71 | void invert() { 72 | for (int i = 0; i < 10; i++) { 73 | params[MUTE_PARAMS + i].setValue(!params[MUTE_PARAMS + i].getValue()); 74 | } 75 | } 76 | }; 77 | 78 | 79 | struct MutesWidget : ModuleWidget { 80 | MutesWidget(Mutes* module) { 81 | setModule(module); 82 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Mutes.svg"), asset::plugin(pluginInstance, "res/Mutes-dark.svg"))); 83 | 84 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 85 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 86 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 87 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 88 | 89 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 21.968)), module, Mutes::MUTE_PARAMS + 0, Mutes::MUTE_LIGHTS + 0)); 90 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 32.095)), module, Mutes::MUTE_PARAMS + 1, Mutes::MUTE_LIGHTS + 1)); 91 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 42.222)), module, Mutes::MUTE_PARAMS + 2, Mutes::MUTE_LIGHTS + 2)); 92 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 52.35)), module, Mutes::MUTE_PARAMS + 3, Mutes::MUTE_LIGHTS + 3)); 93 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 62.477)), module, Mutes::MUTE_PARAMS + 4, Mutes::MUTE_LIGHTS + 4)); 94 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 72.605)), module, Mutes::MUTE_PARAMS + 5, Mutes::MUTE_LIGHTS + 5)); 95 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 82.732)), module, Mutes::MUTE_PARAMS + 6, Mutes::MUTE_LIGHTS + 6)); 96 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 92.86)), module, Mutes::MUTE_PARAMS + 7, Mutes::MUTE_LIGHTS + 7)); 97 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 102.987)), module, Mutes::MUTE_PARAMS + 8, Mutes::MUTE_LIGHTS + 8)); 98 | addParam(createLightParamCentered>>(mm2px(Vec(20.312, 113.115)), module, Mutes::MUTE_PARAMS + 9, Mutes::MUTE_LIGHTS + 9)); 99 | 100 | addInput(createInputCentered(mm2px(Vec(7.291, 21.968)), module, Mutes::IN_INPUTS + 0)); 101 | addInput(createInputCentered(mm2px(Vec(7.291, 32.095)), module, Mutes::IN_INPUTS + 1)); 102 | addInput(createInputCentered(mm2px(Vec(7.291, 42.222)), module, Mutes::IN_INPUTS + 2)); 103 | addInput(createInputCentered(mm2px(Vec(7.291, 52.35)), module, Mutes::IN_INPUTS + 3)); 104 | addInput(createInputCentered(mm2px(Vec(7.291, 62.477)), module, Mutes::IN_INPUTS + 4)); 105 | addInput(createInputCentered(mm2px(Vec(7.291, 72.605)), module, Mutes::IN_INPUTS + 5)); 106 | addInput(createInputCentered(mm2px(Vec(7.291, 82.732)), module, Mutes::IN_INPUTS + 6)); 107 | addInput(createInputCentered(mm2px(Vec(7.291, 92.86)), module, Mutes::IN_INPUTS + 7)); 108 | addInput(createInputCentered(mm2px(Vec(7.291, 102.987)), module, Mutes::IN_INPUTS + 8)); 109 | addInput(createInputCentered(mm2px(Vec(7.291, 113.115)), module, Mutes::IN_INPUTS + 9)); 110 | 111 | addOutput(createOutputCentered(mm2px(Vec(33.332, 21.968)), module, Mutes::OUT_OUTPUTS + 0)); 112 | addOutput(createOutputCentered(mm2px(Vec(33.332, 32.095)), module, Mutes::OUT_OUTPUTS + 1)); 113 | addOutput(createOutputCentered(mm2px(Vec(33.332, 42.222)), module, Mutes::OUT_OUTPUTS + 2)); 114 | addOutput(createOutputCentered(mm2px(Vec(33.332, 52.35)), module, Mutes::OUT_OUTPUTS + 3)); 115 | addOutput(createOutputCentered(mm2px(Vec(33.332, 62.477)), module, Mutes::OUT_OUTPUTS + 4)); 116 | addOutput(createOutputCentered(mm2px(Vec(33.332, 72.605)), module, Mutes::OUT_OUTPUTS + 5)); 117 | addOutput(createOutputCentered(mm2px(Vec(33.332, 82.732)), module, Mutes::OUT_OUTPUTS + 6)); 118 | addOutput(createOutputCentered(mm2px(Vec(33.332, 92.86)), module, Mutes::OUT_OUTPUTS + 7)); 119 | addOutput(createOutputCentered(mm2px(Vec(33.332, 102.987)), module, Mutes::OUT_OUTPUTS + 8)); 120 | addOutput(createOutputCentered(mm2px(Vec(33.332, 113.115)), module, Mutes::OUT_OUTPUTS + 9)); 121 | } 122 | 123 | void appendContextMenu(Menu* menu) override { 124 | Mutes* module = dynamic_cast(this->module); 125 | assert(module); 126 | 127 | menu->addChild(new MenuSeparator); 128 | 129 | menu->addChild(createMenuItem("Invert mutes", "", 130 | [=]() {module->invert();} 131 | )); 132 | } 133 | }; 134 | 135 | 136 | Model* modelMutes = createModel("Mutes"); 137 | -------------------------------------------------------------------------------- /src/Noise.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | /** Based on "The Voss algorithm" 5 | http://www.firstpr.com.au/dsp/pink-noise/ 6 | */ 7 | template 8 | struct PinkNoiseGenerator { 9 | int frame = -1; 10 | float values[QUALITY] = {}; 11 | 12 | float process() { 13 | int lastFrame = frame; 14 | frame++; 15 | if (frame >= (1 << QUALITY)) 16 | frame = 0; 17 | int diff = lastFrame ^ frame; 18 | 19 | float sum = 0.f; 20 | for (int i = 0; i < QUALITY; i++) { 21 | if (diff & (1 << i)) { 22 | values[i] = random::uniform() - 0.5f; 23 | } 24 | sum += values[i]; 25 | } 26 | return sum; 27 | } 28 | }; 29 | 30 | 31 | struct InverseAWeightingFFTFilter { 32 | static constexpr int BUFFER_LEN = 1024; 33 | 34 | alignas(16) float inputBuffer[BUFFER_LEN] = {}; 35 | alignas(16) float outputBuffer[BUFFER_LEN] = {}; 36 | int frame = 0; 37 | dsp::RealFFT fft; 38 | 39 | InverseAWeightingFFTFilter() : fft(BUFFER_LEN) {} 40 | 41 | float process(float deltaTime, float x) { 42 | inputBuffer[frame] = x; 43 | if (++frame >= BUFFER_LEN) { 44 | frame = 0; 45 | alignas(16) float freqBuffer[BUFFER_LEN * 2]; 46 | fft.rfft(inputBuffer, freqBuffer); 47 | 48 | for (int i = 0; i < BUFFER_LEN; i++) { 49 | float f = 1 / deltaTime / 2 / BUFFER_LEN * i; 50 | float amp = 0.f; 51 | if (80.f <= f && f <= 20000.f) { 52 | float f2 = f * f; 53 | // Inverse A-weighted curve 54 | amp = ((424.36f + f2) * std::sqrt((11599.3f + f2) * (544496.f + f2)) * (148693636.f + f2)) / (148693636.f * f2 * f2); 55 | } 56 | freqBuffer[2 * i + 0] *= amp / BUFFER_LEN; 57 | freqBuffer[2 * i + 1] *= amp / BUFFER_LEN; 58 | } 59 | 60 | fft.irfft(freqBuffer, outputBuffer); 61 | } 62 | return outputBuffer[frame]; 63 | } 64 | }; 65 | 66 | 67 | 68 | 69 | struct Noise : Module { 70 | enum ParamIds { 71 | NUM_PARAMS 72 | }; 73 | enum InputIds { 74 | NUM_INPUTS 75 | }; 76 | enum OutputIds { 77 | WHITE_OUTPUT, 78 | PINK_OUTPUT, 79 | RED_OUTPUT, 80 | VIOLET_OUTPUT, 81 | BLUE_OUTPUT, 82 | GRAY_OUTPUT, 83 | BLACK_OUTPUT, 84 | NUM_OUTPUTS 85 | }; 86 | enum LightIds { 87 | NUM_LIGHTS 88 | }; 89 | 90 | dsp::ClockDivider blackDivider; 91 | PinkNoiseGenerator<8> pinkNoiseGenerator; 92 | dsp::IIRFilter<2, 2> redFilter; 93 | float lastWhite = 0.f; 94 | float lastPink = 0.f; 95 | InverseAWeightingFFTFilter grayFilter; 96 | 97 | // For calibrating levels 98 | // float meanSqr = 0.f; 99 | 100 | Noise() { 101 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 102 | configOutput(WHITE_OUTPUT, "White noise"); 103 | outputInfos[WHITE_OUTPUT]->description = "0 dB/octave power density"; 104 | configOutput(PINK_OUTPUT, "Pink noise"); 105 | outputInfos[PINK_OUTPUT]->description = "-3 dB/octave power density"; 106 | configOutput(RED_OUTPUT, "Red noise"); 107 | outputInfos[RED_OUTPUT]->description = "-6 dB/octave power density"; 108 | configOutput(VIOLET_OUTPUT, "Violet noise"); 109 | outputInfos[VIOLET_OUTPUT]->description = "+6 dB/octave power density"; 110 | configOutput(BLUE_OUTPUT, "Blue noise"); 111 | outputInfos[BLUE_OUTPUT]->description = "+3 dB/octave power density"; 112 | configOutput(GRAY_OUTPUT, "Gray noise"); 113 | outputInfos[GRAY_OUTPUT]->description = "Psychoacoustic equal loudness"; 114 | configOutput(BLACK_OUTPUT, "Black noise"); 115 | outputInfos[BLACK_OUTPUT]->description = "Uniform random numbers"; 116 | 117 | // Hard-code coefficients for Butterworth lowpass with cutoff 20 Hz @ 44.1kHz. 118 | const float b[] = {0.00425611, 0.00425611}; 119 | const float a[] = {-0.99148778}; 120 | redFilter.setCoefficients(b, a); 121 | } 122 | 123 | void process(const ProcessArgs& args) override { 124 | // All noise is calibrated to 1 RMS. 125 | // Then they should be scaled to match the RMS of a sine wave with 5V amplitude. 126 | const float gain = 5.f / std::sqrt(2.f); 127 | 128 | if (outputs[WHITE_OUTPUT].isConnected() || outputs[RED_OUTPUT].isConnected() || outputs[VIOLET_OUTPUT].isConnected() || outputs[GRAY_OUTPUT].isConnected()) { 129 | // White noise: equal power density 130 | float white = random::normal(); 131 | outputs[WHITE_OUTPUT].setVoltage(white * gain); 132 | 133 | // Red/Brownian noise: -6dB/oct 134 | if (outputs[RED_OUTPUT].isConnected()) { 135 | float red = redFilter.process(white) / 0.0645f; 136 | outputs[RED_OUTPUT].setVoltage(red * gain); 137 | } 138 | 139 | // Violet/purple noise: 6dB/oct 140 | if (outputs[VIOLET_OUTPUT].isConnected()) { 141 | float violet = (white - lastWhite) / 1.41f; 142 | lastWhite = white; 143 | outputs[VIOLET_OUTPUT].setVoltage(violet * gain); 144 | } 145 | 146 | // Gray noise: psychoacoustic equal loudness curve, specifically inverted A-weighted 147 | if (outputs[GRAY_OUTPUT].isConnected()) { 148 | float gray = grayFilter.process(args.sampleTime, white) / 1.67f; 149 | outputs[GRAY_OUTPUT].setVoltage(gray * gain); 150 | } 151 | } 152 | 153 | if (outputs[PINK_OUTPUT].isConnected() || outputs[BLUE_OUTPUT].isConnected()) { 154 | // Pink noise: -3dB/oct 155 | float pink = pinkNoiseGenerator.process() / 0.816f; 156 | outputs[PINK_OUTPUT].setVoltage(pink * gain); 157 | 158 | // Blue noise: 3dB/oct 159 | if (outputs[BLUE_OUTPUT].isConnected()) { 160 | float blue = (pink - lastPink) / 0.705f; 161 | lastPink = pink; 162 | outputs[BLUE_OUTPUT].setVoltage(blue * gain); 163 | } 164 | } 165 | 166 | // Black noise: uniform noise 167 | // Note: I made this definition up. 168 | if (outputs[BLACK_OUTPUT].isConnected()) { 169 | float u = random::uniform(); 170 | outputs[BLACK_OUTPUT].setVoltage(u * 10.f - 5.f); 171 | } 172 | 173 | // meanSqr += (std::pow(gray, 2.f) - meanSqr) * args.sampleTime / 10.f; 174 | // DEBUG("%f", std::sqrt(meanSqr)); 175 | } 176 | }; 177 | 178 | 179 | struct NoiseWidget : ModuleWidget { 180 | NoiseWidget(Noise* module) { 181 | setModule(module); 182 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Noise.svg"), asset::plugin(pluginInstance, "res/Noise-dark.svg"))); 183 | 184 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 185 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 186 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 187 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 188 | 189 | addOutput(createOutputCentered(mm2px(Vec(7.62, 21.897)), module, Noise::WHITE_OUTPUT)); 190 | addOutput(createOutputCentered(mm2px(Vec(7.62, 37.102)), module, Noise::PINK_OUTPUT)); 191 | addOutput(createOutputCentered(mm2px(Vec(7.62, 52.31)), module, Noise::RED_OUTPUT)); 192 | addOutput(createOutputCentered(mm2px(Vec(7.62, 67.53)), module, Noise::VIOLET_OUTPUT)); 193 | addOutput(createOutputCentered(mm2px(Vec(7.62, 82.732)), module, Noise::BLUE_OUTPUT)); 194 | addOutput(createOutputCentered(mm2px(Vec(7.62, 97.923)), module, Noise::GRAY_OUTPUT)); 195 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Noise::BLACK_OUTPUT)); 196 | } 197 | }; 198 | 199 | 200 | Model* modelNoise = createModel("Noise"); -------------------------------------------------------------------------------- /src/Octave.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Octave : Module { 5 | enum ParamIds { 6 | OCTAVE_PARAM, 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | PITCH_INPUT, 11 | OCTAVE_INPUT, 12 | NUM_INPUTS 13 | }; 14 | enum OutputIds { 15 | PITCH_OUTPUT, 16 | NUM_OUTPUTS 17 | }; 18 | enum LightIds { 19 | NUM_LIGHTS 20 | }; 21 | 22 | int lastOctave = 0; 23 | 24 | Octave() { 25 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 26 | configParam(OCTAVE_PARAM, -4.f, 4.f, 0.f, "Shift", " oct"); 27 | getParamQuantity(OCTAVE_PARAM)->snapEnabled = true; 28 | configInput(PITCH_INPUT, "1V/octave pitch"); 29 | configInput(OCTAVE_INPUT, "Octave shift CV"); 30 | configOutput(PITCH_OUTPUT, "Pitch"); 31 | configBypass(PITCH_INPUT, PITCH_OUTPUT); 32 | } 33 | 34 | void process(const ProcessArgs& args) override { 35 | int channels = std::max(inputs[PITCH_INPUT].getChannels(), 1); 36 | int octaveParam = std::round(params[OCTAVE_PARAM].getValue()); 37 | 38 | for (int c = 0; c < channels; c++) { 39 | int octave = octaveParam + std::round(inputs[OCTAVE_INPUT].getPolyVoltage(c)); 40 | float pitch = inputs[PITCH_INPUT].getVoltage(c); 41 | pitch += octave; 42 | outputs[PITCH_OUTPUT].setVoltage(pitch, c); 43 | if (c == 0) 44 | lastOctave = octave; 45 | } 46 | outputs[PITCH_OUTPUT].setChannels(channels); 47 | } 48 | 49 | void dataFromJson(json_t* rootJ) override { 50 | // In Fundamental 1.1.1 and earlier, the octave param was internal data. 51 | json_t* octaveJ = json_object_get(rootJ, "octave"); 52 | if (octaveJ) { 53 | params[OCTAVE_PARAM].setValue(json_integer_value(octaveJ)); 54 | } 55 | } 56 | }; 57 | 58 | 59 | struct OctaveButton : Widget { 60 | int octave; 61 | 62 | void drawLayer(const DrawArgs& args, int layer) override { 63 | if (layer != 1) 64 | return; 65 | 66 | Vec c = box.size.div(2); 67 | 68 | int activeOctave = 0; 69 | int lastOctave = 0; 70 | ParamWidget* paramWidget = getAncestorOfType(); 71 | assert(paramWidget); 72 | engine::ParamQuantity* pq = paramWidget->getParamQuantity(); 73 | if (pq) { 74 | activeOctave = std::round(pq->getValue()); 75 | Octave* module = dynamic_cast(pq->module); 76 | if (module) 77 | lastOctave = module->lastOctave; 78 | } 79 | 80 | if (activeOctave == octave) { 81 | // Enabled 82 | nvgBeginPath(args.vg); 83 | nvgCircle(args.vg, c.x, c.y, mm2px(4.0 / 2)); 84 | if (octave == 0) 85 | nvgFillColor(args.vg, color::alpha(color::WHITE, 0.33)); 86 | else 87 | nvgFillColor(args.vg, SCHEME_YELLOW); 88 | nvgFill(args.vg); 89 | } 90 | else if (lastOctave == octave) { 91 | // Disabled but enabled by CV 92 | nvgBeginPath(args.vg); 93 | nvgCircle(args.vg, c.x, c.y, mm2px(4.0 / 2)); 94 | if (octave == 0) 95 | nvgFillColor(args.vg, color::alpha(color::WHITE, 0.5 * 0.33)); 96 | else 97 | nvgFillColor(args.vg, color::alpha(SCHEME_YELLOW, 0.5)); 98 | nvgFill(args.vg); 99 | } 100 | else { 101 | // Disabled 102 | nvgBeginPath(args.vg); 103 | nvgCircle(args.vg, c.x, c.y, mm2px(4.0 / 2)); 104 | nvgFillColor(args.vg, color::alpha(color::WHITE, 0.33)); 105 | nvgFill(args.vg); 106 | 107 | nvgBeginPath(args.vg); 108 | nvgCircle(args.vg, c.x, c.y, mm2px(3.0 / 2)); 109 | nvgFillColor(args.vg, nvgRGB(0x12, 0x12, 0x12)); 110 | nvgFill(args.vg); 111 | 112 | if (octave == 0) { 113 | nvgBeginPath(args.vg); 114 | nvgCircle(args.vg, c.x, c.y, mm2px(1.0 / 2)); 115 | nvgFillColor(args.vg, color::alpha(color::WHITE, 0.33)); 116 | nvgFill(args.vg); 117 | } 118 | } 119 | } 120 | 121 | void onDragHover(const event::DragHover& e) override { 122 | if (e.button == GLFW_MOUSE_BUTTON_LEFT) { 123 | e.consume(this); 124 | } 125 | Widget::onDragHover(e); 126 | } 127 | 128 | void onDragEnter(const event::DragEnter& e) override; 129 | }; 130 | 131 | 132 | struct OctaveParam : ParamWidget { 133 | OctaveParam() { 134 | box.size = mm2px(Vec(15.263, 55.88)); 135 | const int octaves = 9; 136 | const float margin = mm2px(2.0); 137 | float height = box.size.y - 2 * margin; 138 | for (int i = 0; i < octaves; i++) { 139 | OctaveButton* octaveButton = new OctaveButton(); 140 | octaveButton->box.pos = Vec(0, height / octaves * i + margin); 141 | octaveButton->box.size = Vec(box.size.x, height / octaves); 142 | octaveButton->octave = 4 - i; 143 | addChild(octaveButton); 144 | } 145 | } 146 | }; 147 | 148 | 149 | inline void OctaveButton::onDragEnter(const event::DragEnter& e) { 150 | if (e.button == GLFW_MOUSE_BUTTON_LEFT) { 151 | OctaveParam* origin = dynamic_cast(e.origin); 152 | if (origin) { 153 | ParamWidget* paramWidget = getAncestorOfType(); 154 | assert(paramWidget); 155 | engine::ParamQuantity* pq = paramWidget->getParamQuantity(); 156 | if (pq) { 157 | pq->setValue(octave); 158 | } 159 | } 160 | } 161 | Widget::onDragEnter(e); 162 | } 163 | 164 | 165 | struct OctaveDisplay : LedDisplay { 166 | void setModule(Octave* module) { 167 | addChild(createParam(mm2px(Vec(0.0, 0.0)), module, Octave::OCTAVE_PARAM)); 168 | } 169 | }; 170 | 171 | 172 | struct OctaveWidget : ModuleWidget { 173 | OctaveWidget(Octave* module) { 174 | setModule(module); 175 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Octave.svg"), asset::plugin(pluginInstance, "res/Octave-dark.svg"))); 176 | 177 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 178 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 179 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 180 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 181 | 182 | addInput(createInputCentered(mm2px(Vec(7.62, 80.573)), module, Octave::OCTAVE_INPUT)); 183 | addInput(createInputCentered(mm2px(Vec(7.62, 96.859)), module, Octave::PITCH_INPUT)); 184 | 185 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Octave::PITCH_OUTPUT)); 186 | 187 | OctaveDisplay* display = createWidget(mm2px(Vec(0.0, 13.039))); 188 | display->box.size = mm2px(Vec(15.263, 55.88)); 189 | display->setModule(module); 190 | addChild(display); 191 | } 192 | }; 193 | 194 | 195 | Model* modelOctave = createModel("Octave"); 196 | -------------------------------------------------------------------------------- /src/Process.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct SlewFilter { 5 | float value = 0.f; 6 | 7 | float process(float in, float slew) { 8 | value += math::clamp(in - value, -slew, slew); 9 | return value; 10 | } 11 | float jump(float in) { 12 | value = in; 13 | return value; 14 | } 15 | float getValue() { 16 | return value; 17 | } 18 | }; 19 | 20 | 21 | struct Process : Module { 22 | enum ParamId { 23 | SLEW_PARAM, 24 | GATE_PARAM, 25 | PARAMS_LEN 26 | }; 27 | enum InputId { 28 | SLEW_INPUT, 29 | IN_INPUT, 30 | GATE_INPUT, 31 | INPUTS_LEN 32 | }; 33 | enum OutputId { 34 | SH1_OUTPUT, 35 | SH2_OUTPUT, 36 | TH_OUTPUT, 37 | HT_OUTPUT, 38 | SLEW_OUTPUT, 39 | GLIDE_OUTPUT, 40 | OUTPUTS_LEN 41 | }; 42 | enum LightId { 43 | GATE_LIGHT, 44 | LIGHTS_LEN 45 | }; 46 | 47 | struct Engine { 48 | bool state = false; 49 | // For glide to turn on after 1ms 50 | float onTime = 0.f; 51 | float sample1 = 0.f; 52 | float sample2 = 0.f; 53 | SlewFilter sample1Filter; 54 | SlewFilter sample2Filter; 55 | float holdValue = 0.f; 56 | SlewFilter slewFilter; 57 | SlewFilter glideFilter; 58 | }; 59 | Engine engines[16]; 60 | 61 | Process() { 62 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 63 | 64 | struct SlewQuantity : ParamQuantity { 65 | float getDisplayValue() override { 66 | if (getValue() <= getMinValue()) 67 | return 0.f; 68 | return ParamQuantity::getDisplayValue(); 69 | } 70 | }; 71 | configParam(SLEW_PARAM, std::log2(1e-3f), std::log2(10.f), std::log2(1e-3f), "Slew", " ms/V", 2, 1000); 72 | configButton(GATE_PARAM, "Gate"); 73 | configInput(SLEW_INPUT, "Slew"); 74 | configInput(IN_INPUT, "Voltage"); 75 | configInput(GATE_INPUT, "Gate"); 76 | configOutput(SH1_OUTPUT, "Sample & hold"); 77 | configOutput(SH2_OUTPUT, "Sample & hold 2"); 78 | configOutput(TH_OUTPUT, "Track & hold"); 79 | configOutput(HT_OUTPUT, "Hold & track"); 80 | configOutput(SLEW_OUTPUT, "Slew"); 81 | configOutput(GLIDE_OUTPUT, "Glide"); 82 | } 83 | 84 | void process(const ProcessArgs& args) override { 85 | int channels = inputs[IN_INPUT].getChannels(); 86 | bool gateButton = params[GATE_PARAM].getValue() > 0.f; 87 | float slewParam = params[SLEW_PARAM].getValue(); 88 | // Hard-left param means infinite slew 89 | if (slewParam <= std::log2(1e-3f)) 90 | slewParam = -INFINITY; 91 | 92 | for (int c = 0; c < channels; c++) { 93 | Engine& e = engines[c]; 94 | 95 | float in = inputs[IN_INPUT].getVoltage(c); 96 | float gateValue = inputs[GATE_INPUT].getPolyVoltage(c); 97 | 98 | // Slew rate in V/s 99 | float slew = INFINITY; 100 | if (std::isfinite(slewParam)) { 101 | float slewPitch = slewParam + inputs[SLEW_INPUT].getPolyVoltage(c); 102 | slew = dsp::exp2_taylor5(-slewPitch + 30.f) / std::exp2(30.f); 103 | } 104 | float slewDelta = slew * args.sampleTime; 105 | 106 | // Gate trigger/untrigger 107 | if (!e.state) { 108 | if (gateValue >= 2.f || gateButton) { 109 | // Triggered 110 | e.state = true; 111 | e.onTime = 0.f; 112 | // Hold and track 113 | e.holdValue = in; 114 | // Sample and hold 115 | e.sample2 = e.sample1; 116 | e.sample1 = in; 117 | } 118 | } 119 | else { 120 | if (gateValue <= 0.1f && !gateButton) { 121 | // Untriggered 122 | e.state = false; 123 | // Track and hold 124 | e.holdValue = in; 125 | } 126 | } 127 | 128 | // Track & hold 129 | float tr = e.state ? e.holdValue : in; 130 | float ht = e.state ? in : e.holdValue; 131 | 132 | // Slew 133 | if (e.state) { 134 | e.slewFilter.jump(in); 135 | e.onTime += args.sampleTime; 136 | } 137 | else { 138 | e.slewFilter.process(in, slewDelta); 139 | } 140 | 141 | // Glide 142 | // Wait 1ms before considering gate as legato 143 | if (e.state && e.onTime > 1e-3f) { 144 | e.glideFilter.process(in, slewDelta); 145 | } 146 | else { 147 | e.glideFilter.jump(in); 148 | } 149 | 150 | outputs[SH1_OUTPUT].setVoltage(e.sample1Filter.process(e.sample1, slewDelta), c); 151 | outputs[SH2_OUTPUT].setVoltage(e.sample2Filter.process(e.sample2, slewDelta), c); 152 | outputs[TH_OUTPUT].setVoltage(tr, c); 153 | outputs[HT_OUTPUT].setVoltage(ht, c); 154 | outputs[SLEW_OUTPUT].setVoltage(e.slewFilter.getValue(), c); 155 | outputs[GLIDE_OUTPUT].setVoltage(e.glideFilter.getValue(), c); 156 | } 157 | 158 | outputs[SH1_OUTPUT].setChannels(channels); 159 | outputs[SH2_OUTPUT].setChannels(channels); 160 | outputs[TH_OUTPUT].setChannels(channels); 161 | outputs[HT_OUTPUT].setChannels(channels); 162 | outputs[SLEW_OUTPUT].setChannels(channels); 163 | outputs[GLIDE_OUTPUT].setChannels(channels); 164 | 165 | lights[GATE_LIGHT].setBrightness(gateButton); 166 | } 167 | }; 168 | 169 | 170 | struct ProcessWidget : ModuleWidget { 171 | ProcessWidget(Process* module) { 172 | setModule(module); 173 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Process.svg"), asset::plugin(pluginInstance, "res/Process-dark.svg"))); 174 | 175 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 176 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 177 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 178 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 179 | 180 | addParam(createParamCentered(mm2px(Vec(12.646, 26.755)), module, Process::SLEW_PARAM)); 181 | addParam(createLightParamCentered>(mm2px(Vec(18.136, 52.31)), module, Process::GATE_PARAM, Process::GATE_LIGHT)); 182 | 183 | addInput(createInputCentered(mm2px(Vec(7.299, 52.31)), module, Process::SLEW_INPUT)); 184 | addInput(createInputCentered(mm2px(Vec(7.297, 67.53)), module, Process::IN_INPUT)); 185 | addInput(createInputCentered(mm2px(Vec(18.122, 67.53)), module, Process::GATE_INPUT)); 186 | 187 | addOutput(createOutputCentered(mm2px(Vec(7.297, 82.732)), module, Process::SH1_OUTPUT)); 188 | addOutput(createOutputCentered(mm2px(Vec(18.134, 82.732)), module, Process::SH2_OUTPUT)); 189 | addOutput(createOutputCentered(mm2px(Vec(7.297, 97.958)), module, Process::TH_OUTPUT)); 190 | addOutput(createOutputCentered(mm2px(Vec(18.134, 97.923)), module, Process::HT_OUTPUT)); 191 | addOutput(createOutputCentered(mm2px(Vec(7.297, 113.115)), module, Process::SLEW_OUTPUT)); 192 | addOutput(createOutputCentered(mm2px(Vec(18.134, 113.115)), module, Process::GLIDE_OUTPUT)); 193 | } 194 | }; 195 | 196 | 197 | Model* modelProcess = createModel("Process"); -------------------------------------------------------------------------------- /src/Pulses.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Pulses : Module { 5 | enum ParamIds { 6 | ENUMS(PUSH_PARAMS, 10), 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | NUM_INPUTS 11 | }; 12 | enum OutputIds { 13 | ENUMS(TRIG_OUTPUTS, 10), 14 | ENUMS(GATE_OUTPUTS, 10), 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | ENUMS(PUSH_LIGHTS, 10), 19 | NUM_LIGHTS 20 | }; 21 | 22 | dsp::BooleanTrigger tapTriggers[10]; 23 | dsp::PulseGenerator pulseGenerators[10]; 24 | 25 | Pulses() { 26 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 27 | for (int i = 0; i < 10; i++) { 28 | configButton(PUSH_PARAMS + i, string::f("Row %d push", i + 1)); 29 | configOutput(TRIG_OUTPUTS + i, string::f("Row %d trigger", i + 1)); 30 | configOutput(GATE_OUTPUTS + i, string::f("Row %d gate", i + 1)); 31 | } 32 | } 33 | 34 | void process(const ProcessArgs& args) override { 35 | for (int i = 0; i < 10; i++) { 36 | bool tap = params[PUSH_PARAMS + i].getValue() > 0.f; 37 | if (tapTriggers[i].process(tap)) { 38 | pulseGenerators[i].trigger(1e-3f); 39 | } 40 | bool pulse = pulseGenerators[i].process(args.sampleTime); 41 | outputs[TRIG_OUTPUTS + i].setVoltage(pulse ? 10.f : 0.f); 42 | outputs[GATE_OUTPUTS + i].setVoltage(tap ? 10.f : 0.f); 43 | lights[PUSH_LIGHTS + i].setBrightness(tap); 44 | } 45 | } 46 | }; 47 | 48 | 49 | struct PulsesWidget : ModuleWidget { 50 | PulsesWidget(Pulses* module) { 51 | setModule(module); 52 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Pulses.svg"), asset::plugin(pluginInstance, "res/Pulses-dark.svg"))); 53 | 54 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 55 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 56 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 57 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 58 | 59 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 21.968)), module, Pulses::PUSH_PARAMS + 0, Pulses::PUSH_LIGHTS + 0)); 60 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 32.095)), module, Pulses::PUSH_PARAMS + 1, Pulses::PUSH_LIGHTS + 1)); 61 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 42.222)), module, Pulses::PUSH_PARAMS + 2, Pulses::PUSH_LIGHTS + 2)); 62 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 52.35)), module, Pulses::PUSH_PARAMS + 3, Pulses::PUSH_LIGHTS + 3)); 63 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 62.477)), module, Pulses::PUSH_PARAMS + 4, Pulses::PUSH_LIGHTS + 4)); 64 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 72.605)), module, Pulses::PUSH_PARAMS + 5, Pulses::PUSH_LIGHTS + 5)); 65 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 82.732)), module, Pulses::PUSH_PARAMS + 6, Pulses::PUSH_LIGHTS + 6)); 66 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 92.86)), module, Pulses::PUSH_PARAMS + 7, Pulses::PUSH_LIGHTS + 7)); 67 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 102.987)), module, Pulses::PUSH_PARAMS + 8, Pulses::PUSH_LIGHTS + 8)); 68 | addParam(createLightParamCentered>(mm2px(Vec(7.28, 113.115)), module, Pulses::PUSH_PARAMS + 9, Pulses::PUSH_LIGHTS + 9)); 69 | 70 | addOutput(createOutputCentered(mm2px(Vec(20.313, 21.968)), module, Pulses::TRIG_OUTPUTS + 0)); 71 | addOutput(createOutputCentered(mm2px(Vec(20.313, 32.095)), module, Pulses::TRIG_OUTPUTS + 1)); 72 | addOutput(createOutputCentered(mm2px(Vec(20.313, 42.222)), module, Pulses::TRIG_OUTPUTS + 2)); 73 | addOutput(createOutputCentered(mm2px(Vec(20.313, 52.35)), module, Pulses::TRIG_OUTPUTS + 3)); 74 | addOutput(createOutputCentered(mm2px(Vec(20.313, 62.477)), module, Pulses::TRIG_OUTPUTS + 4)); 75 | addOutput(createOutputCentered(mm2px(Vec(20.313, 72.605)), module, Pulses::TRIG_OUTPUTS + 5)); 76 | addOutput(createOutputCentered(mm2px(Vec(20.313, 82.732)), module, Pulses::TRIG_OUTPUTS + 6)); 77 | addOutput(createOutputCentered(mm2px(Vec(20.313, 92.86)), module, Pulses::TRIG_OUTPUTS + 7)); 78 | addOutput(createOutputCentered(mm2px(Vec(20.313, 102.987)), module, Pulses::TRIG_OUTPUTS + 8)); 79 | addOutput(createOutputCentered(mm2px(Vec(20.313, 113.115)), module, Pulses::TRIG_OUTPUTS + 9)); 80 | addOutput(createOutputCentered(mm2px(Vec(33.321, 21.968)), module, Pulses::GATE_OUTPUTS + 0)); 81 | addOutput(createOutputCentered(mm2px(Vec(33.321, 32.095)), module, Pulses::GATE_OUTPUTS + 1)); 82 | addOutput(createOutputCentered(mm2px(Vec(33.321, 42.222)), module, Pulses::GATE_OUTPUTS + 2)); 83 | addOutput(createOutputCentered(mm2px(Vec(33.321, 52.35)), module, Pulses::GATE_OUTPUTS + 3)); 84 | addOutput(createOutputCentered(mm2px(Vec(33.321, 62.477)), module, Pulses::GATE_OUTPUTS + 4)); 85 | addOutput(createOutputCentered(mm2px(Vec(33.321, 72.605)), module, Pulses::GATE_OUTPUTS + 5)); 86 | addOutput(createOutputCentered(mm2px(Vec(33.321, 82.732)), module, Pulses::GATE_OUTPUTS + 6)); 87 | addOutput(createOutputCentered(mm2px(Vec(33.321, 92.86)), module, Pulses::GATE_OUTPUTS + 7)); 88 | addOutput(createOutputCentered(mm2px(Vec(33.321, 102.987)), module, Pulses::GATE_OUTPUTS + 8)); 89 | addOutput(createOutputCentered(mm2px(Vec(33.321, 113.115)), module, Pulses::GATE_OUTPUTS + 9)); 90 | 91 | } 92 | }; 93 | 94 | 95 | Model* modelPulses = createModel("Pulses"); -------------------------------------------------------------------------------- /src/Push.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Push : Module { 5 | enum ParamId { 6 | PUSH_PARAM, 7 | HOLD_PARAM, 8 | PARAMS_LEN 9 | }; 10 | enum InputId { 11 | HOLD_INPUT, 12 | PUSH_INPUT, 13 | INPUTS_LEN 14 | }; 15 | enum OutputId { 16 | TRIG_OUTPUT, 17 | GATE_OUTPUT, 18 | OUTPUTS_LEN 19 | }; 20 | enum LightId { 21 | PUSH_LIGHT, 22 | HOLD_LIGHT, 23 | LIGHTS_LEN 24 | }; 25 | 26 | dsp::BooleanTrigger holdBoolean; 27 | dsp::SchmittTrigger holdSchmitt; 28 | dsp::BooleanTrigger pushBoolean; 29 | dsp::SchmittTrigger pushSchmitt; 30 | dsp::PulseGenerator trigPulse; 31 | bool hold = false; 32 | 33 | Push() { 34 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 35 | configButton(PUSH_PARAM, "Push"); 36 | configButton(HOLD_PARAM, "Hold"); 37 | configInput(HOLD_INPUT, "Hold"); 38 | configInput(PUSH_INPUT, "Push"); 39 | configOutput(TRIG_OUTPUT, "Trigger"); 40 | configOutput(GATE_OUTPUT, "Gate"); 41 | } 42 | 43 | void onReset(const ResetEvent& e) override { 44 | Module::onReset(e); 45 | hold = false; 46 | } 47 | 48 | void process(const ProcessArgs& args) override { 49 | // Hold button 50 | if (holdBoolean.process(params[HOLD_PARAM].getValue())) 51 | hold ^= true; 52 | 53 | // Hold input 54 | if (holdSchmitt.process(inputs[HOLD_INPUT].getVoltage(), 0.1f, 1.f)) 55 | hold ^= true; 56 | 57 | // Push button 58 | bool push = params[PUSH_PARAM].getValue() > 0.f; 59 | 60 | // Push input 61 | pushSchmitt.process(inputs[PUSH_INPUT].getVoltage(), 0.1f, 1.f); 62 | 63 | // Gate and trigger outputs 64 | bool gate = push || pushSchmitt.isHigh(); 65 | gate ^= hold; 66 | if (pushBoolean.process(gate)) { 67 | trigPulse.trigger(1e-3f); 68 | } 69 | 70 | outputs[TRIG_OUTPUT].setVoltage(trigPulse.process(args.sampleTime) ? 10.f : 0.f); 71 | outputs[GATE_OUTPUT].setVoltage(gate ? 10.f : 0.f); 72 | 73 | lights[HOLD_LIGHT].setBrightnessSmooth(hold, args.sampleTime); 74 | lights[PUSH_LIGHT].setBrightnessSmooth(gate, args.sampleTime); 75 | } 76 | 77 | json_t* dataToJson() override { 78 | json_t* rootJ = json_object(); 79 | json_object_set_new(rootJ, "hold", json_boolean(hold)); 80 | return rootJ; 81 | } 82 | 83 | void dataFromJson(json_t* rootJ) override { 84 | json_t* holdJ = json_object_get(rootJ, "hold"); 85 | if (holdJ) 86 | hold = json_boolean_value(holdJ); 87 | } 88 | }; 89 | 90 | 91 | struct PushWidget : ModuleWidget { 92 | PushWidget(Push* module) { 93 | setModule(module); 94 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Push.svg"), asset::plugin(pluginInstance, "res/Push-dark.svg"))); 95 | 96 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 97 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 98 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 99 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 100 | 101 | addParam(createLightParamCentered>>(mm2px(Vec(7.62, 24.723)), module, Push::PUSH_PARAM, Push::PUSH_LIGHT)); 102 | addParam(createLightParamCentered>>(mm2px(Vec(7.617, 48.074)), module, Push::HOLD_PARAM, Push::HOLD_LIGHT)); 103 | 104 | addInput(createInputCentered(mm2px(Vec(7.612, 64.344)), module, Push::HOLD_INPUT)); 105 | addInput(createInputCentered(mm2px(Vec(7.612, 80.597)), module, Push::PUSH_INPUT)); 106 | 107 | addOutput(createOutputCentered(mm2px(Vec(7.62, 96.864)), module, Push::TRIG_OUTPUT)); 108 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Push::GATE_OUTPUT)); 109 | } 110 | }; 111 | 112 | 113 | Model* modelPush = createModel("Push"); -------------------------------------------------------------------------------- /src/RandomValues.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | using simd::float_4; 5 | 6 | 7 | struct RandomValues : Module { 8 | enum ParamId { 9 | PUSH_PARAM, 10 | PARAMS_LEN 11 | }; 12 | enum InputId { 13 | TRIG_INPUT, 14 | INPUTS_LEN 15 | }; 16 | enum OutputId { 17 | ENUMS(RND_OUTPUTS, 7), 18 | OUTPUTS_LEN 19 | }; 20 | enum LightId { 21 | PUSH_LIGHT, 22 | LIGHTS_LEN 23 | }; 24 | 25 | dsp::BooleanTrigger pushTrigger; 26 | dsp::TSchmittTrigger trigTriggers[4]; 27 | float values[7][16] = {}; 28 | float randomGain = 10.f; 29 | float randomOffset = 0.f; 30 | 31 | RandomValues() { 32 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 33 | configButton(PUSH_PARAM, "Push"); 34 | configInput(TRIG_INPUT, "Trigger"); 35 | for (int i = 0; i < 7; i++) 36 | configOutput(RND_OUTPUTS + i, string::f("Random %d", i + 1)); 37 | 38 | // Initialize values of all channels 39 | for (int c = 0; c < 16; c++) { 40 | randomizeValues(c); 41 | } 42 | } 43 | 44 | void onReset(const ResetEvent& e) override { 45 | Module::onReset(e); 46 | randomGain = 10.f; 47 | randomOffset = 0.f; 48 | } 49 | 50 | void process(const ProcessArgs& args) override { 51 | int channels = std::max(1, inputs[TRIG_INPUT].getChannels()); 52 | 53 | bool pushed = pushTrigger.process(params[PUSH_PARAM].getValue()); 54 | bool light = false; 55 | 56 | for (int c = 0; c < channels; c += 4) { 57 | float_4 triggered = trigTriggers[c / 4].process(inputs[TRIG_INPUT].getVoltageSimd(c), 0.1f, 1.f); 58 | int triggeredMask = simd::movemask(triggered); 59 | 60 | // This branch is infrequent so we don't need to use SIMD. 61 | if (pushed || triggeredMask) { 62 | light = true; 63 | 64 | for (int c2 = 0; c2 < std::min(4, channels - c); c2++) { 65 | if (pushed || (triggeredMask & (1 << c2))) { 66 | randomizeValues(c + c2); 67 | } 68 | } 69 | } 70 | } 71 | 72 | for (int i = 0; i < 7; i++) { 73 | outputs[RND_OUTPUTS + i].setChannels(channels); 74 | outputs[RND_OUTPUTS + i].writeVoltages(values[i]); 75 | } 76 | lights[PUSH_LIGHT].setBrightnessSmooth(light, args.sampleTime); 77 | } 78 | 79 | void randomizeValues(int channel) { 80 | for (int i = 0; i < 7; i++) { 81 | values[i][channel] = random::get() * randomGain + randomOffset; 82 | } 83 | } 84 | 85 | json_t* dataToJson() override { 86 | json_t* rootJ = json_object(); 87 | 88 | json_t* valuesJ = json_array(); 89 | for (int i = 0; i < 7; i++) { 90 | json_t* channelsJ = json_array(); 91 | for (int c = 0; c < 16; c++) { 92 | json_array_insert_new(channelsJ, c, json_real(values[i][c])); 93 | } 94 | json_array_insert_new(valuesJ, i, channelsJ); 95 | } 96 | json_object_set_new(rootJ, "values", valuesJ); 97 | 98 | json_object_set_new(rootJ, "randomGain", json_real(randomGain)); 99 | json_object_set_new(rootJ, "randomOffset", json_real(randomOffset)); 100 | return rootJ; 101 | } 102 | 103 | void dataFromJson(json_t* rootJ) override { 104 | json_t* valuesJ = json_object_get(rootJ, "values"); 105 | if (valuesJ) 106 | for (int i = 0; i < 7; i++) { 107 | json_t* channelsJ = json_array_get(valuesJ, i); 108 | if (channelsJ) 109 | for (int c = 0; c < 16; c++) { 110 | json_t* valueJ = json_array_get(channelsJ, c); 111 | if (valueJ) 112 | values[i][c] = json_number_value(valueJ); 113 | } 114 | } 115 | 116 | json_t* randomGainJ = json_object_get(rootJ, "randomGain"); 117 | if (randomGainJ) 118 | randomGain = json_number_value(randomGainJ); 119 | 120 | json_t* randomOffsetJ = json_object_get(rootJ, "randomOffset"); 121 | if (randomOffsetJ) 122 | randomOffset = json_number_value(randomOffsetJ); 123 | } 124 | }; 125 | 126 | 127 | struct RandomValuesWidget : ModuleWidget { 128 | RandomValuesWidget(RandomValues* module) { 129 | setModule(module); 130 | setPanel(createPanel(asset::plugin(pluginInstance, "res/RandomValues.svg"), asset::plugin(pluginInstance, "res/RandomValues-dark.svg"))); 131 | 132 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 133 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 134 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 135 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 136 | 137 | addParam(createLightParamCentered>(mm2px(Vec(7.62, 21.968)), module, RandomValues::PUSH_PARAM, RandomValues::PUSH_LIGHT)); 138 | 139 | addInput(createInputCentered(mm2px(Vec(7.622, 38.225)), module, RandomValues::TRIG_INPUT)); 140 | 141 | addOutput(createOutputCentered(mm2px(Vec(7.622, 52.35)), module, RandomValues::RND_OUTPUTS + 0)); 142 | addOutput(createOutputCentered(mm2px(Vec(7.622, 62.477)), module, RandomValues::RND_OUTPUTS + 1)); 143 | addOutput(createOutputCentered(mm2px(Vec(7.622, 72.605)), module, RandomValues::RND_OUTPUTS + 2)); 144 | addOutput(createOutputCentered(mm2px(Vec(7.622, 82.732)), module, RandomValues::RND_OUTPUTS + 3)); 145 | addOutput(createOutputCentered(mm2px(Vec(7.622, 92.86)), module, RandomValues::RND_OUTPUTS + 4)); 146 | addOutput(createOutputCentered(mm2px(Vec(7.622, 102.987)), module, RandomValues::RND_OUTPUTS + 5)); 147 | addOutput(createOutputCentered(mm2px(Vec(7.622, 113.013)), module, RandomValues::RND_OUTPUTS + 6)); 148 | } 149 | 150 | void appendContextMenu(Menu* menu) override { 151 | RandomValues* module = getModule(); 152 | 153 | menu->addChild(new MenuSeparator); 154 | 155 | menu->addChild(createRangeItem("Random range", &module->randomGain, &module->randomOffset)); 156 | } 157 | }; 158 | 159 | 160 | Model* modelRandomValues = createModel("RandomValues"); -------------------------------------------------------------------------------- /src/Rescale.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Rescale : Module { 5 | enum ParamId { 6 | GAIN_PARAM, 7 | OFFSET_PARAM, 8 | MAX_PARAM, 9 | MIN_PARAM, 10 | PARAMS_LEN 11 | }; 12 | enum InputId { 13 | IN_INPUT, 14 | INPUTS_LEN 15 | }; 16 | enum OutputId { 17 | OUT_OUTPUT, 18 | OUTPUTS_LEN 19 | }; 20 | enum LightId { 21 | ENUMS(MAX_LIGHT, 2), 22 | ENUMS(MIN_LIGHT, 2), 23 | LIGHTS_LEN 24 | }; 25 | 26 | float multiplier = 1.f; 27 | bool reflectMin = false; 28 | bool reflectMax = false; 29 | dsp::ClockDivider lightDivider; 30 | 31 | Rescale() { 32 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 33 | 34 | struct GainQuantity : ParamQuantity { 35 | float getDisplayValue() override { 36 | Rescale* module = reinterpret_cast(this->module); 37 | if (module->multiplier == 1.f) { 38 | unit = "%"; 39 | displayMultiplier = 100.f; 40 | } 41 | else { 42 | unit = "x"; 43 | displayMultiplier = module->multiplier; 44 | } 45 | return ParamQuantity::getDisplayValue(); 46 | } 47 | }; 48 | configParam(GAIN_PARAM, -1.f, 1.f, 0.f, "Gain", "%", 0, 100); 49 | configParam(OFFSET_PARAM, -10.f, 10.f, 0.f, "Offset", " V"); 50 | configParam(MAX_PARAM, -10.f, 10.f, 10.f, "Maximum", " V"); 51 | configParam(MIN_PARAM, -10.f, 10.f, -10.f, "Minimum", " V"); 52 | configInput(IN_INPUT, "Signal"); 53 | configOutput(OUT_OUTPUT, "Signal"); 54 | configBypass(IN_INPUT, OUT_OUTPUT); 55 | 56 | lightDivider.setDivision(16); 57 | } 58 | 59 | void onReset(const ResetEvent& e) override { 60 | Module::onReset(e); 61 | multiplier = 1.f; 62 | reflectMin = false; 63 | reflectMax = false; 64 | } 65 | 66 | void process(const ProcessArgs& args) override { 67 | using simd::float_4; 68 | 69 | int channels = std::max(1, inputs[IN_INPUT].getChannels()); 70 | 71 | float gain = params[GAIN_PARAM].getValue() * multiplier; 72 | float offset = params[OFFSET_PARAM].getValue(); 73 | float min = params[MIN_PARAM].getValue(); 74 | float max = params[MAX_PARAM].getValue(); 75 | 76 | bool maxLight = false; 77 | bool minLight = false; 78 | bool lightProcess = lightDivider.process(); 79 | 80 | for (int c = 0; c < channels; c += 4) { 81 | float_4 x = inputs[IN_INPUT].getVoltageSimd(c); 82 | x *= gain; 83 | x += offset; 84 | 85 | // Check lights 86 | if (lightProcess) { 87 | // Mask result for non factor of 4 channels. 88 | int mask = 0xffff >> (16 - channels + c); 89 | if (simd::movemask(x <= min) & mask) 90 | minLight = true; 91 | if (simd::movemask(x >= max) & mask) 92 | maxLight = true; 93 | } 94 | 95 | if (max <= min) { 96 | x = min; 97 | } 98 | else if (reflectMin && reflectMax) { 99 | // Cyclically reflect value between min and max 100 | float range = max - min; 101 | x = (x - min) / range; 102 | x = simd::fmod(x + 1.f, 2.f) - 1.f; 103 | x = simd::fabs(x); 104 | x = x * range + min; 105 | } 106 | else if (reflectMin) { 107 | x = simd::fabs(x - min) + min; 108 | x = simd::fmin(x, max); 109 | } 110 | else if (reflectMax) { 111 | x = max - simd::fabs(max - x); 112 | x = simd::fmax(x, min); 113 | } 114 | else { 115 | x = simd::fmin(x, max); 116 | x = simd::fmax(x, min); 117 | } 118 | 119 | outputs[OUT_OUTPUT].setVoltageSimd(x, c); 120 | } 121 | 122 | outputs[OUT_OUTPUT].setChannels(channels); 123 | 124 | // Lights 125 | if (lightProcess) { 126 | float lightTime = args.sampleTime * lightDivider.getDivision(); 127 | lights[MAX_LIGHT + 0].setBrightnessSmooth(maxLight && (channels <= 1), lightTime); 128 | lights[MAX_LIGHT + 1].setBrightnessSmooth(maxLight && (channels > 1), lightTime); 129 | lights[MIN_LIGHT + 0].setBrightnessSmooth(minLight && (channels <= 1), lightTime); 130 | lights[MIN_LIGHT + 1].setBrightnessSmooth(minLight && (channels > 1), lightTime); 131 | } 132 | } 133 | 134 | json_t* dataToJson() override { 135 | json_t* rootJ = json_object(); 136 | json_object_set_new(rootJ, "multiplier", json_real(multiplier)); 137 | json_object_set_new(rootJ, "reflectMin", json_boolean(reflectMin)); 138 | json_object_set_new(rootJ, "reflectMax", json_boolean(reflectMax)); 139 | return rootJ; 140 | } 141 | 142 | void dataFromJson(json_t* rootJ) override { 143 | json_t* multiplierJ = json_object_get(rootJ, "multiplier"); 144 | if (multiplierJ) 145 | multiplier = json_number_value(multiplierJ); 146 | 147 | json_t* reflectMinJ = json_object_get(rootJ, "reflectMin"); 148 | if (reflectMinJ) 149 | reflectMin = json_boolean_value(reflectMinJ); 150 | 151 | json_t* reflectMaxJ = json_object_get(rootJ, "reflectMax"); 152 | if (reflectMaxJ) 153 | reflectMax = json_boolean_value(reflectMaxJ); 154 | } 155 | }; 156 | 157 | 158 | struct RescaleWidget : ModuleWidget { 159 | RescaleWidget(Rescale* module) { 160 | setModule(module); 161 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Rescale.svg"), asset::plugin(pluginInstance, "res/Rescale-dark.svg"))); 162 | 163 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 164 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 165 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 166 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 167 | 168 | addParam(createParamCentered(mm2px(Vec(7.62, 24.723)), module, Rescale::GAIN_PARAM)); 169 | addParam(createParamCentered(mm2px(Vec(7.617, 43.031)), module, Rescale::OFFSET_PARAM)); 170 | addParam(createParamCentered(mm2px(Vec(7.612, 64.344)), module, Rescale::MAX_PARAM)); 171 | addParam(createParamCentered(mm2px(Vec(7.612, 80.597)), module, Rescale::MIN_PARAM)); 172 | 173 | addInput(createInputCentered(mm2px(Vec(7.62, 96.859)), module, Rescale::IN_INPUT)); 174 | 175 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, Rescale::OUT_OUTPUT)); 176 | 177 | addChild(createLightCentered>>(mm2px(Vec(12.327, 57.3)), module, Rescale::MAX_LIGHT)); 178 | addChild(createLightCentered>>(mm2px(Vec(12.327, 73.559)), module, Rescale::MIN_LIGHT)); 179 | } 180 | 181 | void appendContextMenu(Menu* menu) override { 182 | Rescale* module = getModule(); 183 | 184 | menu->addChild(new MenuSeparator); 185 | 186 | menu->addChild(createIndexSubmenuItem("Gain multiplier", {"1x", "10x", "100x", "1000x"}, 187 | [=]() { 188 | return (int) std::log10(module->multiplier); 189 | }, 190 | [=](int mode) { 191 | module->multiplier = std::pow(10.f, (float) mode); 192 | } 193 | )); 194 | 195 | menu->addChild(createBoolPtrMenuItem("Reflect at maximum", "", &module->reflectMax)); 196 | menu->addChild(createBoolPtrMenuItem("Reflect at minimum", "", &module->reflectMin)); 197 | } 198 | }; 199 | 200 | 201 | Model* modelRescale = createModel("Rescale"); -------------------------------------------------------------------------------- /src/SHASR.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct SHASR : Module { 5 | enum ParamId { 6 | RND_PARAM, 7 | PUSH_PARAM, 8 | CLEAR_PARAM, 9 | PARAMS_LEN 10 | }; 11 | enum InputId { 12 | ENUMS(IN_INPUTS, 8), 13 | ENUMS(TRIG_INPUTS, 8), 14 | INPUTS_LEN 15 | }; 16 | enum OutputId { 17 | ENUMS(SH_OUTPUTS, 8), 18 | OUTPUTS_LEN 19 | }; 20 | enum LightId { 21 | RND_LIGHT, 22 | PUSH_LIGHT, 23 | CLEAR_LIGHT, 24 | LIGHTS_LEN 25 | }; 26 | 27 | dsp::BooleanTrigger pushTrigger; 28 | dsp::BooleanTrigger clearTrigger; 29 | dsp::SchmittTrigger triggers[8]; 30 | float values[8] = {}; 31 | float randomGain = 10.f; 32 | float randomOffset = 0.f; 33 | 34 | SHASR() { 35 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 36 | configSwitch(RND_PARAM, 0.f, 1.f, 0.f, "Randomize"); 37 | getParamQuantity(RND_PARAM)->randomizeEnabled = false; 38 | getParamQuantity(RND_PARAM)->description = "Normalizes \"Sample 1 input\" to a random signal"; 39 | configButton(PUSH_PARAM, "Push"); 40 | configButton(CLEAR_PARAM, "Clear"); 41 | for (int i = 0; i < 8; i++) { 42 | configInput(IN_INPUTS + i, string::f("Sample %d", i + 1)); 43 | configInput(TRIG_INPUTS + i, string::f("Trigger %d", i + 1)); 44 | configOutput(SH_OUTPUTS + i, string::f("Sample %d", i + 1)); 45 | } 46 | } 47 | 48 | void onReset(const ResetEvent& e) override { 49 | for (int i = 0; i < 8; i++) { 50 | values[i] = 0.f; 51 | } 52 | randomGain = 10.f; 53 | randomOffset = 0.f; 54 | Module::onReset(e); 55 | } 56 | 57 | void process(const ProcessArgs& args) override { 58 | bool randomize = params[RND_PARAM].getValue(); 59 | bool push = pushTrigger.process(params[PUSH_PARAM].getValue()); 60 | bool clear = clearTrigger.process(params[CLEAR_PARAM].getValue()); 61 | bool lastTrig = false; 62 | float lastValue = 0.f; 63 | 64 | for (int i = 0; i < 8; i++) { 65 | // Check trigger if connected 66 | if (inputs[TRIG_INPUTS + i].isConnected()) { 67 | lastTrig = triggers[i].process(inputs[TRIG_INPUTS + i].getVoltage(), 0.1f, 1.f); 68 | } 69 | // Override first trigger if pushed 70 | if (i == 0 && push) { 71 | lastTrig = true; 72 | } 73 | 74 | // Get sample value if triggered 75 | float sample = 0.f; 76 | if (lastTrig) { 77 | if (i == 0) { 78 | if (randomize) { 79 | sample = random::get() * randomGain + randomOffset; 80 | } 81 | } 82 | else { 83 | sample = lastValue; 84 | } 85 | } 86 | 87 | lastValue = values[i]; 88 | 89 | if (lastTrig) { 90 | values[i] = inputs[IN_INPUTS + i].getNormalVoltage(sample); 91 | } 92 | if (clear) { 93 | values[i] = 0.f; 94 | } 95 | 96 | outputs[SH_OUTPUTS + i].setVoltage(values[i]); 97 | } 98 | 99 | lights[RND_LIGHT].setBrightness(randomize); 100 | lights[PUSH_LIGHT].setBrightnessSmooth(push * 2.f, args.sampleTime); 101 | lights[CLEAR_LIGHT].setBrightnessSmooth(clear * 2.f, args.sampleTime); 102 | } 103 | 104 | json_t* dataToJson() override { 105 | json_t* rootJ = json_object(); 106 | 107 | json_t* valuesJ = json_array(); 108 | for (int i = 0; i < 8; i++) { 109 | json_array_insert_new(valuesJ, i, json_real(values[i])); 110 | } 111 | json_object_set_new(rootJ, "values", valuesJ); 112 | 113 | json_object_set_new(rootJ, "randomGain", json_real(randomGain)); 114 | json_object_set_new(rootJ, "randomOffset", json_real(randomOffset)); 115 | 116 | return rootJ; 117 | } 118 | 119 | void dataFromJson(json_t* rootJ) override { 120 | json_t* valuesJ = json_object_get(rootJ, "values"); 121 | if (valuesJ) { 122 | for (int i = 0; i < 8; i++) { 123 | json_t* valueJ = json_array_get(valuesJ, i); 124 | if (valueJ) 125 | values[i] = json_number_value(valueJ); 126 | } 127 | } 128 | 129 | json_t* randomGainJ = json_object_get(rootJ, "randomGain"); 130 | if (randomGainJ) 131 | randomGain = json_number_value(randomGainJ); 132 | 133 | json_t* randomOffsetJ = json_object_get(rootJ, "randomOffset"); 134 | if (randomOffsetJ) 135 | randomOffset = json_number_value(randomOffsetJ); 136 | } 137 | }; 138 | 139 | 140 | struct SHASRWidget : ModuleWidget { 141 | SHASRWidget(SHASR* module) { 142 | setModule(module); 143 | setPanel(createPanel(asset::plugin(pluginInstance, "res/SHASR.svg"), asset::plugin(pluginInstance, "res/SHASR-dark.svg"))); 144 | 145 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 146 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 147 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 148 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 149 | 150 | addParam(createLightParamCentered>>(mm2px(Vec(6.96, 21.852)), module, SHASR::RND_PARAM, SHASR::RND_LIGHT)); 151 | addParam(createLightParamCentered>(mm2px(Vec(17.788, 21.852)), module, SHASR::PUSH_PARAM, SHASR::PUSH_LIGHT)); 152 | addParam(createLightParamCentered>(mm2px(Vec(28.6, 21.852)), module, SHASR::CLEAR_PARAM, SHASR::CLEAR_LIGHT)); 153 | 154 | addInput(createInputCentered(mm2px(Vec(6.96, 42.113)), module, SHASR::IN_INPUTS + 0)); 155 | addInput(createInputCentered(mm2px(Vec(17.788, 42.055)), module, SHASR::TRIG_INPUTS + 0)); 156 | addInput(createInputCentered(mm2px(Vec(6.96, 52.241)), module, SHASR::IN_INPUTS + 1)); 157 | addInput(createInputCentered(mm2px(Vec(17.788, 52.241)), module, SHASR::TRIG_INPUTS + 1)); 158 | addInput(createInputCentered(mm2px(Vec(6.96, 62.368)), module, SHASR::IN_INPUTS + 2)); 159 | addInput(createInputCentered(mm2px(Vec(17.788, 62.368)), module, SHASR::TRIG_INPUTS + 2)); 160 | addInput(createInputCentered(mm2px(Vec(6.96, 72.496)), module, SHASR::IN_INPUTS + 3)); 161 | addInput(createInputCentered(mm2px(Vec(17.788, 72.496)), module, SHASR::TRIG_INPUTS + 3)); 162 | addInput(createInputCentered(mm2px(Vec(6.96, 82.623)), module, SHASR::IN_INPUTS + 4)); 163 | addInput(createInputCentered(mm2px(Vec(17.788, 82.623)), module, SHASR::TRIG_INPUTS + 4)); 164 | addInput(createInputCentered(mm2px(Vec(6.96, 92.75)), module, SHASR::IN_INPUTS + 5)); 165 | addInput(createInputCentered(mm2px(Vec(17.788, 92.75)), module, SHASR::TRIG_INPUTS + 5)); 166 | addInput(createInputCentered(mm2px(Vec(6.96, 102.878)), module, SHASR::IN_INPUTS + 6)); 167 | addInput(createInputCentered(mm2px(Vec(17.788, 102.878)), module, SHASR::TRIG_INPUTS + 6)); 168 | addInput(createInputCentered(mm2px(Vec(6.96, 113.005)), module, SHASR::IN_INPUTS + 7)); 169 | addInput(createInputCentered(mm2px(Vec(17.788, 113.005)), module, SHASR::TRIG_INPUTS + 7)); 170 | 171 | addOutput(createOutputCentered(mm2px(Vec(28.6, 42.113)), module, SHASR::SH_OUTPUTS + 0)); 172 | addOutput(createOutputCentered(mm2px(Vec(28.6, 52.241)), module, SHASR::SH_OUTPUTS + 1)); 173 | addOutput(createOutputCentered(mm2px(Vec(28.6, 62.368)), module, SHASR::SH_OUTPUTS + 2)); 174 | addOutput(createOutputCentered(mm2px(Vec(28.6, 72.496)), module, SHASR::SH_OUTPUTS + 3)); 175 | addOutput(createOutputCentered(mm2px(Vec(28.6, 82.623)), module, SHASR::SH_OUTPUTS + 4)); 176 | addOutput(createOutputCentered(mm2px(Vec(28.6, 92.75)), module, SHASR::SH_OUTPUTS + 5)); 177 | addOutput(createOutputCentered(mm2px(Vec(28.6, 102.878)), module, SHASR::SH_OUTPUTS + 6)); 178 | addOutput(createOutputCentered(mm2px(Vec(28.6, 113.005)), module, SHASR::SH_OUTPUTS + 7)); 179 | } 180 | 181 | void appendContextMenu(Menu* menu) override { 182 | SHASR* module = getModule(); 183 | 184 | menu->addChild(new MenuSeparator); 185 | 186 | menu->addChild(createRangeItem("Random range", &module->randomGain, &module->randomOffset)); 187 | } 188 | }; 189 | 190 | 191 | Model* modelSHASR = createModel("SHASR"); -------------------------------------------------------------------------------- /src/Split.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Split : Module { 5 | enum ParamIds { 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | POLY_INPUT, 10 | NUM_INPUTS 11 | }; 12 | enum OutputIds { 13 | ENUMS(MONO_OUTPUTS, 16), 14 | NUM_OUTPUTS 15 | }; 16 | enum LightIds { 17 | ENUMS(CHANNEL_LIGHTS, 16), 18 | NUM_LIGHTS 19 | }; 20 | 21 | int lastChannels = 0; 22 | 23 | Split() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | configInput(POLY_INPUT, "Polyphonic"); 26 | for (int i = 0; i < 16; i++) 27 | configOutput(MONO_OUTPUTS + i, string::f("Channel %d", i + 1)); 28 | } 29 | 30 | void process(const ProcessArgs& args) override { 31 | for (int c = 0; c < 16; c++) { 32 | float v = inputs[POLY_INPUT].getVoltage(c); 33 | // To allow users to debug buggy modules, don't assume that undefined channel voltages are 0V. 34 | outputs[MONO_OUTPUTS + c].setVoltage(v); 35 | } 36 | 37 | lastChannels = inputs[POLY_INPUT].getChannels(); 38 | } 39 | }; 40 | 41 | 42 | struct SplitChannelDisplay : ChannelDisplay { 43 | Split* module; 44 | void step() override { 45 | int channels = module ? module->lastChannels : 16; 46 | text = string::f("%d", channels); 47 | } 48 | }; 49 | 50 | 51 | struct SplitWidget : ModuleWidget { 52 | SplitWidget(Split* module) { 53 | setModule(module); 54 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Split.svg"), asset::plugin(pluginInstance, "res/Split-dark.svg"))); 55 | 56 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 57 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 58 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 59 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 60 | 61 | addInput(createInputCentered(mm2px(Vec(7.281, 21.967)), module, Split::POLY_INPUT)); 62 | 63 | addOutput(createOutputCentered(mm2px(Vec(7.281, 41.995)), module, Split::MONO_OUTPUTS + 0)); 64 | addOutput(createOutputCentered(mm2px(Vec(7.281, 52.155)), module, Split::MONO_OUTPUTS + 1)); 65 | addOutput(createOutputCentered(mm2px(Vec(7.281, 62.315)), module, Split::MONO_OUTPUTS + 2)); 66 | addOutput(createOutputCentered(mm2px(Vec(7.281, 72.475)), module, Split::MONO_OUTPUTS + 3)); 67 | addOutput(createOutputCentered(mm2px(Vec(7.281, 82.635)), module, Split::MONO_OUTPUTS + 4)); 68 | addOutput(createOutputCentered(mm2px(Vec(7.281, 92.795)), module, Split::MONO_OUTPUTS + 5)); 69 | addOutput(createOutputCentered(mm2px(Vec(7.281, 102.955)), module, Split::MONO_OUTPUTS + 6)); 70 | addOutput(createOutputCentered(mm2px(Vec(7.281, 113.115)), module, Split::MONO_OUTPUTS + 7)); 71 | addOutput(createOutputCentered(mm2px(Vec(18.119, 41.995)), module, Split::MONO_OUTPUTS + 8)); 72 | addOutput(createOutputCentered(mm2px(Vec(18.119, 52.155)), module, Split::MONO_OUTPUTS + 9)); 73 | addOutput(createOutputCentered(mm2px(Vec(18.119, 62.315)), module, Split::MONO_OUTPUTS + 10)); 74 | addOutput(createOutputCentered(mm2px(Vec(18.119, 72.475)), module, Split::MONO_OUTPUTS + 11)); 75 | addOutput(createOutputCentered(mm2px(Vec(18.119, 82.635)), module, Split::MONO_OUTPUTS + 12)); 76 | addOutput(createOutputCentered(mm2px(Vec(18.119, 92.795)), module, Split::MONO_OUTPUTS + 13)); 77 | addOutput(createOutputCentered(mm2px(Vec(18.119, 102.955)), module, Split::MONO_OUTPUTS + 14)); 78 | addOutput(createOutputCentered(mm2px(Vec(18.119, 113.115)), module, Split::MONO_OUTPUTS + 15)); 79 | 80 | SplitChannelDisplay* display = createWidget(mm2px(Vec(14.02, 18.611))); 81 | display->box.size = mm2px(Vec(8.197, 8.197)); 82 | display->module = module; 83 | addChild(display); 84 | } 85 | }; 86 | 87 | 88 | Model* modelSplit = createModel("Split"); 89 | -------------------------------------------------------------------------------- /src/Sum.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Sum : Module { 5 | enum ParamIds { 6 | LEVEL_PARAM, 7 | NUM_PARAMS 8 | }; 9 | enum InputIds { 10 | POLY_INPUT, 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | MONO_OUTPUT, 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | ENUMS(VU_LIGHTS, 6), 19 | NUM_LIGHTS 20 | }; 21 | 22 | dsp::VuMeter2 vuMeter; 23 | dsp::ClockDivider vuDivider; 24 | dsp::ClockDivider lightDivider; 25 | int lastChannels = 0; 26 | 27 | Sum() { 28 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 29 | configParam(LEVEL_PARAM, 0.f, 1.f, 1.f, "Level", "%", 0.f, 100.f); 30 | configInput(POLY_INPUT, "Polyphonic"); 31 | configOutput(MONO_OUTPUT, "Monophonic"); 32 | 33 | vuMeter.lambda = 1 / 0.1f; 34 | vuDivider.setDivision(16); 35 | lightDivider.setDivision(512); 36 | } 37 | 38 | void process(const ProcessArgs& args) override { 39 | float sum = inputs[POLY_INPUT].getVoltageSum(); 40 | sum *= params[LEVEL_PARAM].getValue(); 41 | outputs[MONO_OUTPUT].setVoltage(sum); 42 | 43 | if (vuDivider.process()) { 44 | vuMeter.process(args.sampleTime * vuDivider.getDivision(), sum / 10.f); 45 | } 46 | 47 | // Set channel lights infrequently 48 | if (lightDivider.process()) { 49 | lastChannels = inputs[POLY_INPUT].getChannels(); 50 | 51 | lights[VU_LIGHTS + 0].setBrightness(vuMeter.getBrightness(0, 0)); 52 | lights[VU_LIGHTS + 1].setBrightness(vuMeter.getBrightness(-3, 0)); 53 | lights[VU_LIGHTS + 2].setBrightness(vuMeter.getBrightness(-6, -3)); 54 | lights[VU_LIGHTS + 3].setBrightness(vuMeter.getBrightness(-12, -6)); 55 | lights[VU_LIGHTS + 4].setBrightness(vuMeter.getBrightness(-24, -12)); 56 | lights[VU_LIGHTS + 5].setBrightness(vuMeter.getBrightness(-36, -24)); 57 | } 58 | } 59 | }; 60 | 61 | 62 | struct SumDisplay : LedDisplay { 63 | Sum* module; 64 | 65 | void drawLayer(const DrawArgs& args, int layer) override { 66 | if (layer == 1) { 67 | static const std::vector posY = { 68 | mm2px(18.068 - 13.039), 69 | mm2px(23.366 - 13.039), 70 | mm2px(28.663 - 13.039), 71 | mm2px(33.961 - 13.039), 72 | mm2px(39.258 - 13.039), 73 | mm2px(44.556 - 13.039), 74 | }; 75 | static const std::vector texts = { 76 | " 0", "-3", "-6", "-12", "-24", "-36", 77 | }; 78 | 79 | std::string fontPath = asset::system("res/fonts/Nunito-Bold.ttf"); 80 | std::shared_ptr font = APP->window->loadFont(fontPath); 81 | if (!font) 82 | return; 83 | 84 | nvgSave(args.vg); 85 | nvgFontFaceId(args.vg, font->handle); 86 | nvgFontSize(args.vg, 11); 87 | nvgTextLetterSpacing(args.vg, 0.0); 88 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 89 | nvgFillColor(args.vg, nvgRGB(99, 99, 99)); 90 | 91 | for (int i = 0; i < 6; i++) { 92 | nvgText(args.vg, 15.0, posY[i], texts[i].c_str(), NULL); 93 | } 94 | nvgRestore(args.vg); 95 | } 96 | LedDisplay::drawLayer(args, layer); 97 | } 98 | }; 99 | 100 | 101 | struct SumChannelDisplay : ChannelDisplay { 102 | Sum* module; 103 | 104 | void step() override { 105 | int channels = 16; 106 | if (module) 107 | channels = module->lastChannels; 108 | text = string::f("%d", channels); 109 | } 110 | }; 111 | 112 | 113 | struct SumWidget : ModuleWidget { 114 | SumWidget(Sum* module) { 115 | setModule(module); 116 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Sum.svg"), asset::plugin(pluginInstance, "res/Sum-dark.svg"))); 117 | 118 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 119 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 120 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 121 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 122 | 123 | addParam(createParamCentered(mm2px(Vec(7.62, 64.284)), module, Sum::LEVEL_PARAM)); 124 | 125 | addInput(createInputCentered(mm2px(Vec(7.62, 96.798)), module, Sum::POLY_INPUT)); 126 | 127 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.066)), module, Sum::MONO_OUTPUT)); 128 | 129 | SumDisplay* display = createWidget(mm2px(Vec(0.0, 13.039))); 130 | display->box.size = mm2px(Vec(15.241, 36.981)); 131 | display->module = module; 132 | addChild(display); 133 | 134 | addChild(createLightCentered>(mm2px(Vec(10.808, 18.081)), module, Sum::VU_LIGHTS + 0)); 135 | addChild(createLightCentered>(mm2px(Vec(10.808, 23.378)), module, Sum::VU_LIGHTS + 1)); 136 | addChild(createLightCentered>(mm2px(Vec(10.808, 28.676)), module, Sum::VU_LIGHTS + 2)); 137 | addChild(createLightCentered>(mm2px(Vec(10.808, 33.973)), module, Sum::VU_LIGHTS + 3)); 138 | addChild(createLightCentered>(mm2px(Vec(10.808, 39.271)), module, Sum::VU_LIGHTS + 4)); 139 | addChild(createLightCentered>(mm2px(Vec(10.808, 44.568)), module, Sum::VU_LIGHTS + 5)); 140 | 141 | SumChannelDisplay* channelDisplay = createWidget(mm2px(Vec(3.521, 77.191))); 142 | channelDisplay->box.size = mm2px(Vec(8.197, 8.197)); 143 | channelDisplay->module = module; 144 | addChild(channelDisplay); 145 | } 146 | }; 147 | 148 | 149 | Model* modelSum = createModel("Sum"); 150 | -------------------------------------------------------------------------------- /src/Unity.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Unity : Module { 5 | enum ParamIds { 6 | AVG1_PARAM, 7 | AVG2_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | ENUMS(IN_INPUTS, 2 * 6), 12 | NUM_INPUTS 13 | }; 14 | enum OutputIds { 15 | MIX1_OUTPUT, 16 | INV1_OUTPUT, 17 | MIX2_OUTPUT, 18 | INV2_OUTPUT, 19 | NUM_OUTPUTS 20 | }; 21 | enum LightIds { 22 | ENUMS(VU_LIGHTS, 2 * 5), 23 | NUM_LIGHTS 24 | }; 25 | 26 | bool merge = false; 27 | dsp::VuMeter2 vuMeters[2]; 28 | dsp::ClockDivider lightDivider; 29 | 30 | Unity() { 31 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 32 | configSwitch(AVG1_PARAM, 0.0, 1.0, 0.0, "Channel 1 mode", {"Sum", "Average"}); 33 | configSwitch(AVG2_PARAM, 0.0, 1.0, 0.0, "Channel 2 mode", {"Sum", "Average"}); 34 | for (int i = 0; i < 2; i++) { 35 | for (int j = 0; j < 6; j++) { 36 | configInput(IN_INPUTS + i * 6 + j, string::f("Channel %d #%d", i + 1, j + 1)); 37 | } 38 | } 39 | configOutput(MIX1_OUTPUT, "Channel 1 mix"); 40 | configOutput(INV1_OUTPUT, "Channel 1 inverse mix"); 41 | configOutput(MIX2_OUTPUT, "Channel 2 mix"); 42 | configOutput(INV2_OUTPUT, "Channel 2 inverse mix"); 43 | 44 | lightDivider.setDivision(256); 45 | } 46 | 47 | void process(const ProcessArgs& args) override { 48 | float mix[2] = {}; 49 | int count[2] = {}; 50 | 51 | for (int i = 0; i < 2; i++) { 52 | // Inputs 53 | for (int j = 0; j < 6; j++) { 54 | mix[i] += inputs[IN_INPUTS + 6 * i + j].getVoltage(); 55 | if (inputs[IN_INPUTS + 6 * i + j].isConnected()) 56 | count[i]++; 57 | } 58 | } 59 | 60 | // Combine 61 | if (merge) { 62 | mix[0] += mix[1]; 63 | mix[1] = mix[0]; 64 | count[0] += count[1]; 65 | count[1] = count[0]; 66 | } 67 | 68 | for (int i = 0; i < 2; i++) { 69 | // Params 70 | if (count[i] > 0 && (int) std::round(params[AVG1_PARAM + i].getValue()) == 1) 71 | mix[i] /= count[i]; 72 | 73 | // Outputs 74 | outputs[MIX1_OUTPUT + 2 * i].setVoltage(mix[i]); 75 | outputs[INV1_OUTPUT + 2 * i].setVoltage(-mix[i]); 76 | vuMeters[i].process(args.sampleTime, mix[i] / 10.f); 77 | } 78 | 79 | if (lightDivider.process()) { 80 | // Lights 81 | for (int i = 0; i < 2; i++) { 82 | lights[VU_LIGHTS + 5 * i + 0].setBrightness(vuMeters[i].getBrightness(0.f, 0.f)); 83 | for (int j = 1; j < 5; j++) { 84 | lights[VU_LIGHTS + 5 * i + j].setBrightness(vuMeters[i].getBrightness(-6.f * (j + 1), -6.f * j)); 85 | } 86 | } 87 | } 88 | } 89 | 90 | void onReset() override { 91 | merge = false; 92 | } 93 | 94 | json_t* dataToJson() override { 95 | json_t* rootJ = json_object(); 96 | // merge 97 | json_object_set_new(rootJ, "merge", json_boolean(merge)); 98 | return rootJ; 99 | } 100 | 101 | void dataFromJson(json_t* rootJ) override { 102 | // merge 103 | json_t* mergeJ = json_object_get(rootJ, "merge"); 104 | if (mergeJ) 105 | merge = json_boolean_value(mergeJ); 106 | } 107 | }; 108 | 109 | 110 | struct UnityWidget : ModuleWidget { 111 | UnityWidget(Unity* module) { 112 | setModule(module); 113 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Unity.svg"))); 114 | 115 | addChild(createWidget(Vec(15, 0))); 116 | addChild(createWidget(Vec(box.size.x - 30, 0))); 117 | addChild(createWidget(Vec(15, 365))); 118 | addChild(createWidget(Vec(box.size.x - 30, 365))); 119 | 120 | addParam(createParam(mm2px(Vec(12.867, 52.961)), module, Unity::AVG1_PARAM)); 121 | addParam(createParam(mm2px(Vec(12.867, 107.006)), module, Unity::AVG2_PARAM)); 122 | 123 | addInput(createInput(mm2px(Vec(2.361, 17.144)), module, Unity::IN_INPUTS + 0 * 6 + 0)); 124 | addInput(createInput(mm2px(Vec(19.907, 17.144)), module, Unity::IN_INPUTS + 0 * 6 + 1)); 125 | addInput(createInput(mm2px(Vec(2.361, 28.145)), module, Unity::IN_INPUTS + 0 * 6 + 2)); 126 | addInput(createInput(mm2px(Vec(19.907, 28.145)), module, Unity::IN_INPUTS + 0 * 6 + 3)); 127 | addInput(createInput(mm2px(Vec(2.361, 39.145)), module, Unity::IN_INPUTS + 0 * 6 + 4)); 128 | addInput(createInput(mm2px(Vec(19.907, 39.145)), module, Unity::IN_INPUTS + 0 * 6 + 5)); 129 | addInput(createInput(mm2px(Vec(2.361, 71.145)), module, Unity::IN_INPUTS + 1 * 6 + 0)); 130 | addInput(createInput(mm2px(Vec(19.907, 71.145)), module, Unity::IN_INPUTS + 1 * 6 + 1)); 131 | addInput(createInput(mm2px(Vec(2.361, 82.145)), module, Unity::IN_INPUTS + 1 * 6 + 2)); 132 | addInput(createInput(mm2px(Vec(19.907, 82.145)), module, Unity::IN_INPUTS + 1 * 6 + 3)); 133 | addInput(createInput(mm2px(Vec(2.361, 93.144)), module, Unity::IN_INPUTS + 1 * 6 + 4)); 134 | addInput(createInput(mm2px(Vec(19.907, 93.144)), module, Unity::IN_INPUTS + 1 * 6 + 5)); 135 | 136 | addOutput(createOutput(mm2px(Vec(2.361, 54.15)), module, Unity::MIX1_OUTPUT)); 137 | addOutput(createOutput(mm2px(Vec(19.907, 54.15)), module, Unity::INV1_OUTPUT)); 138 | addOutput(createOutput(mm2px(Vec(2.361, 108.144)), module, Unity::MIX2_OUTPUT)); 139 | addOutput(createOutput(mm2px(Vec(19.907, 108.144)), module, Unity::INV2_OUTPUT)); 140 | 141 | addChild(createLight>(mm2px(Vec(13.652, 19.663)), module, Unity::VU_LIGHTS + 0 * 5 + 0)); 142 | addChild(createLight>(mm2px(Vec(13.652, 25.163)), module, Unity::VU_LIGHTS + 0 * 5 + 1)); 143 | addChild(createLight>(mm2px(Vec(13.652, 30.663)), module, Unity::VU_LIGHTS + 0 * 5 + 2)); 144 | addChild(createLight>(mm2px(Vec(13.652, 36.162)), module, Unity::VU_LIGHTS + 0 * 5 + 3)); 145 | addChild(createLight>(mm2px(Vec(13.652, 41.662)), module, Unity::VU_LIGHTS + 0 * 5 + 4)); 146 | addChild(createLight>(mm2px(Vec(13.652, 73.663)), module, Unity::VU_LIGHTS + 1 * 5 + 0)); 147 | addChild(createLight>(mm2px(Vec(13.652, 79.163)), module, Unity::VU_LIGHTS + 1 * 5 + 1)); 148 | addChild(createLight>(mm2px(Vec(13.652, 84.663)), module, Unity::VU_LIGHTS + 1 * 5 + 2)); 149 | addChild(createLight>(mm2px(Vec(13.652, 90.162)), module, Unity::VU_LIGHTS + 1 * 5 + 3)); 150 | addChild(createLight>(mm2px(Vec(13.652, 95.662)), module, Unity::VU_LIGHTS + 1 * 5 + 4)); 151 | } 152 | 153 | void appendContextMenu(Menu* menu) override { 154 | Unity* module = dynamic_cast(this->module); 155 | assert(module); 156 | 157 | menu->addChild(new MenuSeparator); 158 | 159 | menu->addChild(createBoolPtrMenuItem("Merge channels 1 & 2", "", &module->merge)); 160 | } 161 | }; 162 | 163 | 164 | Model* modelUnity = createModel("Unity"); 165 | -------------------------------------------------------------------------------- /src/VCA-1.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct VCA_1 : Module { 5 | enum ParamIds { 6 | LEVEL_PARAM, 7 | EXP_PARAM, // removed from panel in 2.0, still in context menu 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | CV_INPUT, 12 | IN_INPUT, 13 | NUM_INPUTS 14 | }; 15 | enum OutputIds { 16 | OUT_OUTPUT, 17 | NUM_OUTPUTS 18 | }; 19 | enum LightIds { 20 | NUM_LIGHTS 21 | }; 22 | 23 | int lastChannels = 1; 24 | float lastGains[16] = {}; 25 | 26 | VCA_1() { 27 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 28 | configParam(LEVEL_PARAM, 0.0, 1.0, 1.0, "Level", "%", 0, 100); 29 | configSwitch(EXP_PARAM, 0.0, 1.0, 1.0, "Response mode", {"Exponential", "Linear"}); 30 | configInput(CV_INPUT, "CV"); 31 | configInput(IN_INPUT, "Channel"); 32 | configOutput(OUT_OUTPUT, "Channel"); 33 | configBypass(IN_INPUT, OUT_OUTPUT); 34 | } 35 | 36 | void process(const ProcessArgs& args) override { 37 | int channels = std::max({1, inputs[IN_INPUT].getChannels(), inputs[CV_INPUT].getChannels()}); 38 | float level = params[LEVEL_PARAM].getValue(); 39 | 40 | for (int c = 0; c < channels; c++) { 41 | // Get input 42 | float in = inputs[IN_INPUT].getPolyVoltage(c); 43 | 44 | // Get gain 45 | float gain = level; 46 | if (inputs[CV_INPUT].isConnected()) { 47 | float cv = clamp(inputs[CV_INPUT].getPolyVoltage(c) / 10.f, 0.f, 1.f); 48 | if (int(params[EXP_PARAM].getValue()) == 0) 49 | cv = std::pow(cv, 4.f); 50 | gain *= cv; 51 | } 52 | 53 | // Apply gain 54 | in *= gain; 55 | lastGains[c] = gain; 56 | 57 | // Set output 58 | outputs[OUT_OUTPUT].setVoltage(in, c); 59 | } 60 | 61 | outputs[OUT_OUTPUT].setChannels(channels); 62 | lastChannels = channels; 63 | } 64 | }; 65 | 66 | 67 | struct VCA_1VUKnob : SliderKnob { 68 | void drawLayer(const DrawArgs& args, int layer) override { 69 | if (layer != 1) 70 | return; 71 | 72 | VCA_1* module = dynamic_cast(this->module); 73 | 74 | Rect r = box.zeroPos(); 75 | NVGcolor bgColor = nvgRGB(0x12, 0x12, 0x12); 76 | 77 | int channels = module ? module->lastChannels : 1; 78 | engine::ParamQuantity* pq = getParamQuantity(); 79 | float value = pq ? pq->getValue() : 1.f; 80 | 81 | // Segment value 82 | if (value >= 0.005f) { 83 | nvgBeginPath(args.vg); 84 | nvgRect(args.vg, 85 | r.pos.x, 86 | r.pos.y + r.size.y * (1 - value), 87 | r.size.x, 88 | r.size.y * value); 89 | nvgFillColor(args.vg, color::mult(color::WHITE, 0.25)); 90 | nvgFill(args.vg); 91 | } 92 | 93 | // Segment gain 94 | nvgBeginPath(args.vg); 95 | bool segmentFill = false; 96 | for (int c = 0; c < channels; c++) { 97 | float gain = module ? module->lastGains[c] : 1.f; 98 | if (gain >= 0.005f) { 99 | segmentFill = true; 100 | nvgRect(args.vg, 101 | r.pos.x + r.size.x * c / channels, 102 | r.pos.y + r.size.y * (1 - gain), 103 | r.size.x / channels, 104 | r.size.y * gain); 105 | } 106 | } 107 | nvgFillColor(args.vg, SCHEME_YELLOW); 108 | // If nvgFill() is called with 0 path elements, it can fill other undefined paths. 109 | if (segmentFill) { 110 | nvgFill(args.vg); 111 | } 112 | 113 | // Invisible separators 114 | const int segs = 25; 115 | nvgBeginPath(args.vg); 116 | for (int i = 1; i < segs; i++) { 117 | nvgRect(args.vg, 118 | r.pos.x - 1.0, 119 | r.pos.y + r.size.y * i / segs, 120 | r.size.x + 2.0, 121 | 1.0); 122 | } 123 | nvgFillColor(args.vg, bgColor); 124 | nvgFill(args.vg); 125 | } 126 | }; 127 | 128 | 129 | struct VCA_1Display : LedDisplay { 130 | }; 131 | 132 | 133 | struct VCA_1Widget : ModuleWidget { 134 | VCA_1Widget(VCA_1* module) { 135 | setModule(module); 136 | setPanel(createPanel(asset::plugin(pluginInstance, "res/VCA-1.svg"), asset::plugin(pluginInstance, "res/VCA-1-dark.svg"))); 137 | 138 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 139 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 140 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 141 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 142 | 143 | addInput(createInputCentered(mm2px(Vec(7.62, 80.603)), module, VCA_1::CV_INPUT)); 144 | addInput(createInputCentered(mm2px(Vec(7.62, 96.859)), module, VCA_1::IN_INPUT)); 145 | 146 | addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, VCA_1::OUT_OUTPUT)); 147 | 148 | VCA_1Display* display = createWidget(mm2px(Vec(0.0, 13.039))); 149 | display->box.size = mm2px(Vec(15.263, 55.88)); 150 | addChild(display); 151 | 152 | VCA_1VUKnob* knob = createParam(mm2px(Vec(2.253, 15.931)), module, VCA_1::LEVEL_PARAM); 153 | knob->box.size = mm2px(Vec(10.734, 50.253)); 154 | addChild(knob); 155 | } 156 | 157 | void appendContextMenu(Menu* menu) override { 158 | VCA_1* module = dynamic_cast(this->module); 159 | assert(module); 160 | 161 | menu->addChild(new MenuSeparator); 162 | 163 | menu->addChild(createBoolMenuItem("Exponential response", "", 164 | [=]() {return module->params[VCA_1::EXP_PARAM].getValue() == 0.f;}, 165 | [=](bool value) {module->params[VCA_1::EXP_PARAM].setValue(!value);} 166 | )); 167 | } 168 | }; 169 | 170 | 171 | Model* modelVCA_1 = createModel("VCA-1"); 172 | -------------------------------------------------------------------------------- /src/VCA.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | // Deprecated. Use VCA-1 instead. 5 | 6 | 7 | struct VCA : Module { 8 | enum ParamIds { 9 | LEVEL1_PARAM, 10 | LEVEL2_PARAM, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | EXP1_INPUT, 15 | LIN1_INPUT, 16 | IN1_INPUT, 17 | EXP2_INPUT, 18 | LIN2_INPUT, 19 | IN2_INPUT, 20 | NUM_INPUTS 21 | }; 22 | enum OutputIds { 23 | OUT1_OUTPUT, 24 | OUT2_OUTPUT, 25 | NUM_OUTPUTS 26 | }; 27 | 28 | VCA() { 29 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); 30 | configParam(LEVEL1_PARAM, 0.0, 1.0, 1.0, "Channel 1 level", "%", 0, 100); 31 | configParam(LEVEL2_PARAM, 0.0, 1.0, 1.0, "Channel 2 level", "%", 0, 100); 32 | configInput(EXP1_INPUT, "Channel 1 exponential CV"); 33 | configInput(EXP2_INPUT, "Channel 2 exponential CV"); 34 | configInput(LIN1_INPUT, "Channel 1 linear CV"); 35 | configInput(LIN2_INPUT, "Channel 2 linear CV"); 36 | configInput(IN1_INPUT, "Channel 1"); 37 | configInput(IN2_INPUT, "Channel 2"); 38 | configOutput(OUT1_OUTPUT, "Channel 1"); 39 | configOutput(OUT2_OUTPUT, "Channel 2"); 40 | configBypass(IN1_INPUT, OUT1_OUTPUT); 41 | configBypass(IN2_INPUT, OUT2_OUTPUT); 42 | } 43 | 44 | void processChannel(Input& in, Param& level, Input& lin, Input& exp, Output& out) { 45 | // Get input 46 | int channels = std::max(in.getChannels(), 1); 47 | simd::float_4 v[4]; 48 | for (int c = 0; c < channels; c += 4) { 49 | v[c / 4] = simd::float_4::load(in.getVoltages(c)); 50 | } 51 | 52 | // Apply knob gain 53 | float gain = level.getValue(); 54 | for (int c = 0; c < channels; c += 4) { 55 | v[c / 4] *= gain; 56 | } 57 | 58 | // Apply linear CV gain 59 | if (lin.isConnected()) { 60 | if (lin.isPolyphonic()) { 61 | for (int c = 0; c < channels; c += 4) { 62 | simd::float_4 cv = simd::float_4::load(lin.getVoltages(c)) / 10.f; 63 | cv = clamp(cv, 0.f, 1.f); 64 | v[c / 4] *= cv; 65 | } 66 | } 67 | else { 68 | float cv = lin.getVoltage() / 10.f; 69 | cv = clamp(cv, 0.f, 1.f); 70 | for (int c = 0; c < channels; c += 4) { 71 | v[c / 4] *= cv; 72 | } 73 | } 74 | } 75 | 76 | // Apply exponential CV gain 77 | const float expBase = 50.f; 78 | if (exp.isConnected()) { 79 | if (exp.isPolyphonic()) { 80 | for (int c = 0; c < channels; c += 4) { 81 | simd::float_4 cv = simd::float_4::load(exp.getVoltages(c)) / 10.f; 82 | cv = clamp(cv, 0.f, 1.f); 83 | cv = rescale(pow(expBase, cv), 1.f, expBase, 0.f, 1.f); 84 | v[c / 4] *= cv; 85 | } 86 | } 87 | else { 88 | float cv = exp.getVoltage() / 10.f; 89 | cv = clamp(cv, 0.f, 1.f); 90 | cv = rescale(std::pow(expBase, cv), 1.f, expBase, 0.f, 1.f); 91 | for (int c = 0; c < channels; c += 4) { 92 | v[c / 4] *= cv; 93 | } 94 | } 95 | } 96 | 97 | // Set output 98 | out.setChannels(channels); 99 | for (int c = 0; c < channels; c += 4) { 100 | v[c / 4].store(out.getVoltages(c)); 101 | } 102 | } 103 | 104 | void process(const ProcessArgs& args) override { 105 | processChannel(inputs[IN1_INPUT], params[LEVEL1_PARAM], inputs[LIN1_INPUT], inputs[EXP1_INPUT], outputs[OUT1_OUTPUT]); 106 | processChannel(inputs[IN2_INPUT], params[LEVEL2_PARAM], inputs[LIN2_INPUT], inputs[EXP2_INPUT], outputs[OUT2_OUTPUT]); 107 | } 108 | }; 109 | 110 | 111 | 112 | struct VCAWidget : ModuleWidget { 113 | VCAWidget(VCA* module) { 114 | setModule(module); 115 | setPanel(createPanel(asset::plugin(pluginInstance, "res/VCA.svg"))); 116 | 117 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 118 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 119 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 120 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 121 | 122 | addParam(createParam(mm2px(Vec(6.35, 19.11753)), module, VCA::LEVEL1_PARAM)); 123 | addParam(createParam(mm2px(Vec(6.35, 74.80544)), module, VCA::LEVEL2_PARAM)); 124 | 125 | addInput(createInput(mm2px(Vec(2.5907, 38.19371)), module, VCA::EXP1_INPUT)); 126 | addInput(createInput(mm2px(Vec(14.59752, 38.19371)), module, VCA::LIN1_INPUT)); 127 | addInput(createInput(mm2px(Vec(2.5907, 52.80642)), module, VCA::IN1_INPUT)); 128 | addInput(createInput(mm2px(Vec(2.5907, 93.53435)), module, VCA::EXP2_INPUT)); 129 | addInput(createInput(mm2px(Vec(14.59752, 93.53435)), module, VCA::LIN2_INPUT)); 130 | addInput(createInput(mm2px(Vec(2.5907, 108.14706)), module, VCA::IN2_INPUT)); 131 | 132 | addOutput(createOutput(mm2px(Vec(14.59752, 52.80642)), module, VCA::OUT1_OUTPUT)); 133 | addOutput(createOutput(mm2px(Vec(14.59752, 108.14706)), module, VCA::OUT2_OUTPUT)); 134 | } 135 | }; 136 | 137 | 138 | Model* modelVCA = createModel("VCA"); 139 | -------------------------------------------------------------------------------- /src/VCMixer.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct VCMixer : Module { 5 | enum ParamIds { 6 | MIX_LVL_PARAM, 7 | ENUMS(LVL_PARAMS, 4), 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | MIX_CV_INPUT, 12 | ENUMS(CH_INPUTS, 4), 13 | ENUMS(CV_INPUTS, 4), 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | MIX_OUTPUT, 18 | ENUMS(CH_OUTPUTS, 4), 19 | NUM_OUTPUTS 20 | }; 21 | enum LightIds { 22 | ENUMS(LVL_LIGHTS, 4), 23 | NUM_LIGHTS 24 | }; 25 | 26 | dsp::VuMeter2 chMeters[4]; 27 | dsp::ClockDivider lightDivider; 28 | 29 | bool chExp = false; 30 | bool mixExp = false; 31 | 32 | VCMixer() { 33 | config(0, 0, 0, 0); 34 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 35 | // x^1 scaling up to 6 dB 36 | configParam(MIX_LVL_PARAM, 0.0, 2.0, 1.0, "Mix level", " dB", -10, 20); 37 | // x^2 scaling up to 6 dB 38 | configParam(LVL_PARAMS + 0, 0.0, M_SQRT2, 1.0, "Channel 1 level", " dB", -10, 40); 39 | configParam(LVL_PARAMS + 1, 0.0, M_SQRT2, 1.0, "Channel 2 level", " dB", -10, 40); 40 | configParam(LVL_PARAMS + 2, 0.0, M_SQRT2, 1.0, "Channel 3 level", " dB", -10, 40); 41 | configParam(LVL_PARAMS + 3, 0.0, M_SQRT2, 1.0, "Channel 4 level", " dB", -10, 40); 42 | configInput(MIX_CV_INPUT, "Mix CV"); 43 | for (int i = 0; i < 4; i++) 44 | configInput(CH_INPUTS + i, string::f("Channel %d", i + 1)); 45 | for (int i = 0; i < 4; i++) 46 | configInput(CV_INPUTS + i, string::f("Channel %d CV", i + 1)); 47 | configOutput(MIX_OUTPUT, "Mix"); 48 | for (int i = 0; i < 4; i++) 49 | configOutput(CH_OUTPUTS + i, string::f("Channel %d", i + 1)); 50 | 51 | lightDivider.setDivision(512); 52 | } 53 | 54 | void onReset(const ResetEvent& e) override { 55 | chExp = false; 56 | mixExp = false; 57 | Module::onReset(e); 58 | } 59 | 60 | void process(const ProcessArgs& args) override { 61 | using simd::float_4; 62 | 63 | // Get number of poly channels for mix output 64 | int channels = 1; 65 | for (int i = 0; i < 4; i++) { 66 | channels = std::max(channels, inputs[CH_INPUTS + i].getChannels()); 67 | } 68 | 69 | // Iterate polyphony channels (voices) 70 | float chSum[4] = {}; 71 | 72 | for (int c = 0; c < channels; c += 4) { 73 | float_4 mix = 0.f; 74 | 75 | // Channel strips 76 | for (int i = 0; i < 4; i++) { 77 | float_4 out = 0.f; 78 | 79 | if (inputs[CH_INPUTS + i].isConnected()) { 80 | // Get input 81 | out = inputs[CH_INPUTS + i].getPolyVoltageSimd(c); 82 | 83 | // Apply fader gain 84 | float gain = std::pow(params[LVL_PARAMS + i].getValue(), 2.f); 85 | out *= gain; 86 | 87 | // Apply CV gain 88 | if (inputs[CV_INPUTS + i].isConnected()) { 89 | float_4 cv = inputs[CV_INPUTS + i].getPolyVoltageSimd(c) / 10.f; 90 | cv = simd::fmax(0.f, cv); 91 | if (chExp) 92 | cv = (cv * cv) * (cv * cv); 93 | out *= cv; 94 | } 95 | 96 | // Sum channel for VU meter 97 | for (int c2 = 0; c2 < 4; c2++) { 98 | chSum[i] += out[c2]; 99 | } 100 | 101 | // Add to mix 102 | mix += out; 103 | } 104 | 105 | // Set channel output 106 | outputs[CH_OUTPUTS + i].setVoltageSimd(out, c); 107 | } 108 | 109 | // Mix output 110 | if (outputs[MIX_OUTPUT].isConnected()) { 111 | // Apply mix knob gain 112 | float gain = params[MIX_LVL_PARAM].getValue(); 113 | mix *= gain; 114 | 115 | // Apply mix CV gain 116 | if (inputs[MIX_CV_INPUT].isConnected()) { 117 | float_4 cv = inputs[MIX_CV_INPUT].getPolyVoltageSimd(c) / 10.f; 118 | cv = simd::fmax(0.f, cv); 119 | if (mixExp) 120 | cv = (cv * cv) * (cv * cv); 121 | mix *= cv; 122 | } 123 | 124 | // Set mix output 125 | outputs[MIX_OUTPUT].setVoltageSimd(mix, c); 126 | } 127 | } 128 | 129 | // Set output channels 130 | for (int i = 0; i < 4; i++) { 131 | outputs[CH_OUTPUTS + i].setChannels(channels); 132 | } 133 | outputs[MIX_OUTPUT].setChannels(channels); 134 | 135 | // VU lights 136 | for (int i = 0; i < 4; i++) { 137 | chMeters[i].process(args.sampleTime, chSum[i] / 5.f); 138 | } 139 | if (lightDivider.process()) { 140 | for (int i = 0; i < 4; i++) { 141 | lights[LVL_LIGHTS + i].setBrightness(chMeters[i].getBrightness(-24.f, 0.f)); 142 | } 143 | } 144 | } 145 | 146 | json_t* dataToJson() override { 147 | json_t* rootJ = json_object(); 148 | // chExp 149 | json_object_set_new(rootJ, "chExp", json_boolean(chExp)); 150 | // mixExp 151 | json_object_set_new(rootJ, "mixExp", json_boolean(mixExp)); 152 | return rootJ; 153 | } 154 | 155 | void dataFromJson(json_t* rootJ) override { 156 | // chExp 157 | json_t* chExpJ = json_object_get(rootJ, "chExp"); 158 | if (chExpJ) 159 | chExp = json_boolean_value(chExpJ); 160 | // mixExp 161 | json_t* mixExpJ = json_object_get(rootJ, "mixExp"); 162 | if (mixExpJ) 163 | mixExp = json_boolean_value(mixExpJ); 164 | } 165 | }; 166 | 167 | 168 | struct VCMixerWidget : ModuleWidget { 169 | VCMixerWidget(VCMixer* module) { 170 | setModule(module); 171 | setPanel(createPanel(asset::plugin(pluginInstance, "res/VCMixer.svg"), asset::plugin(pluginInstance, "res/VCMixer-dark.svg"))); 172 | 173 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 174 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 175 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 176 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 177 | 178 | addParam(createLightParamCentered>(mm2px(Vec(6.604, 33.605)), module, VCMixer::LVL_PARAMS + 0, VCMixer::LVL_LIGHTS + 0)); 179 | addParam(createLightParamCentered>(mm2px(Vec(17.441, 33.605)), module, VCMixer::LVL_PARAMS + 1, VCMixer::LVL_LIGHTS + 1)); 180 | addParam(createLightParamCentered>(mm2px(Vec(28.279, 33.605)), module, VCMixer::LVL_PARAMS + 2, VCMixer::LVL_LIGHTS + 2)); 181 | addParam(createLightParamCentered>(mm2px(Vec(39.116, 33.605)), module, VCMixer::LVL_PARAMS + 3, VCMixer::LVL_LIGHTS + 3)); 182 | addParam(createParamCentered(mm2px(Vec(22.776, 64.366)), module, VCMixer::MIX_LVL_PARAM)); 183 | 184 | addInput(createInputCentered(mm2px(Vec(6.604, 64.347)), module, VCMixer::MIX_CV_INPUT)); 185 | addInput(createInputCentered(mm2px(Vec(6.604, 80.549)), module, VCMixer::CV_INPUTS + 0)); 186 | addInput(createInputCentered(mm2px(Vec(17.441, 80.549)), module, VCMixer::CV_INPUTS + 1)); 187 | addInput(createInputCentered(mm2px(Vec(28.279, 80.549)), module, VCMixer::CV_INPUTS + 2)); 188 | addInput(createInputCentered(mm2px(Vec(39.116, 80.549)), module, VCMixer::CV_INPUTS + 3)); 189 | addInput(createInputCentered(mm2px(Vec(6.604, 96.859)), module, VCMixer::CH_INPUTS + 0)); 190 | addInput(createInputCentered(mm2px(Vec(17.441, 96.859)), module, VCMixer::CH_INPUTS + 1)); 191 | addInput(createInputCentered(mm2px(Vec(28.279, 96.859)), module, VCMixer::CH_INPUTS + 2)); 192 | addInput(createInputCentered(mm2px(Vec(39.116, 96.821)), module, VCMixer::CH_INPUTS + 3)); 193 | 194 | addOutput(createOutputCentered(mm2px(Vec(39.116, 64.347)), module, VCMixer::MIX_OUTPUT)); 195 | addOutput(createOutputCentered(mm2px(Vec(6.604, 113.115)), module, VCMixer::CH_OUTPUTS + 0)); 196 | addOutput(createOutputCentered(mm2px(Vec(17.441, 113.115)), module, VCMixer::CH_OUTPUTS + 1)); 197 | addOutput(createOutputCentered(mm2px(Vec(28.279, 113.115)), module, VCMixer::CH_OUTPUTS + 2)); 198 | addOutput(createOutputCentered(mm2px(Vec(39.116, 113.115)), module, VCMixer::CH_OUTPUTS + 3)); 199 | } 200 | 201 | void appendContextMenu(Menu* menu) override { 202 | VCMixer* module = getModule(); 203 | assert(module); 204 | 205 | menu->addChild(new MenuSeparator); 206 | 207 | menu->addChild(createBoolPtrMenuItem("Exponential channel VCAs", "", &module->chExp)); 208 | menu->addChild(createBoolPtrMenuItem("Exponential mix VCA", "", &module->mixExp)); 209 | } 210 | }; 211 | 212 | 213 | Model* modelVCMixer = createModel("VCMixer"); 214 | -------------------------------------------------------------------------------- /src/Viz.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | struct Viz : Module { 5 | enum ParamIds { 6 | NUM_PARAMS 7 | }; 8 | enum InputIds { 9 | POLY_INPUT, 10 | NUM_INPUTS 11 | }; 12 | enum OutputIds { 13 | NUM_OUTPUTS 14 | }; 15 | enum LightIds { 16 | ENUMS(VU_LIGHTS, 16 * 2), 17 | NUM_LIGHTS 18 | }; 19 | 20 | int lastChannel = 0; 21 | dsp::ClockDivider lightDivider; 22 | 23 | Viz() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | configInput(POLY_INPUT, "Polyphonic"); 26 | 27 | lightDivider.setDivision(16); 28 | } 29 | 30 | void process(const ProcessArgs& args) override { 31 | if (lightDivider.process()) { 32 | lastChannel = inputs[POLY_INPUT].getChannels(); 33 | float deltaTime = args.sampleTime * lightDivider.getDivision(); 34 | 35 | for (int c = 0; c < 16; c++) { 36 | float v = inputs[POLY_INPUT].getVoltage(c) / 10.f; 37 | lights[VU_LIGHTS + c * 2 + 0].setSmoothBrightness(v, deltaTime); 38 | lights[VU_LIGHTS + c * 2 + 1].setSmoothBrightness(-v, deltaTime); 39 | } 40 | } 41 | } 42 | }; 43 | 44 | 45 | struct VizDisplay : LedDisplay { 46 | Viz* module; 47 | 48 | void drawLayer(const DrawArgs& args, int layer) override { 49 | if (layer == 1) { 50 | static const std::vector posY = { 51 | mm2px(18.068 - 13.039), 52 | mm2px(23.366 - 13.039), 53 | mm2px(28.663 - 13.039), 54 | mm2px(33.961 - 13.039), 55 | mm2px(39.258 - 13.039), 56 | mm2px(44.556 - 13.039), 57 | mm2px(49.919 - 13.039), 58 | mm2px(55.217 - 13.039), 59 | mm2px(60.514 - 13.039), 60 | mm2px(65.812 - 13.039), 61 | mm2px(71.109 - 13.039), 62 | mm2px(76.473 - 13.039), 63 | mm2px(81.771 - 13.039), 64 | mm2px(87.068 - 13.039), 65 | mm2px(92.366 - 13.039), 66 | mm2px(97.663 - 13.039), 67 | }; 68 | 69 | std::string fontPath = asset::system("res/fonts/Nunito-Bold.ttf"); 70 | std::shared_ptr font = APP->window->loadFont(fontPath); 71 | if (!font) 72 | return; 73 | 74 | nvgSave(args.vg); 75 | nvgFontFaceId(args.vg, font->handle); 76 | nvgFontSize(args.vg, 11); 77 | nvgTextLetterSpacing(args.vg, 0.0); 78 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 79 | 80 | for (int c = 0; c < 16; c++) { 81 | if (!module || c < module->lastChannel) 82 | nvgFillColor(args.vg, nvgRGB(255, 255, 255)); 83 | else 84 | nvgFillColor(args.vg, nvgRGB(99, 99, 99)); 85 | std::string text = string::f("%d", c + 1); 86 | nvgText(args.vg, 15.0, posY[c], text.c_str(), NULL); 87 | } 88 | nvgRestore(args.vg); 89 | } 90 | LedDisplay::drawLayer(args, layer); 91 | } 92 | }; 93 | 94 | 95 | struct VizWidget : ModuleWidget { 96 | VizWidget(Viz* module) { 97 | setModule(module); 98 | setPanel(createPanel(asset::plugin(pluginInstance, "res/Viz.svg"), asset::plugin(pluginInstance, "res/Viz-dark.svg"))); 99 | 100 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 101 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 102 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 103 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 104 | 105 | addInput(createInputCentered(mm2px(Vec(7.62, 113.115)), module, Viz::POLY_INPUT)); 106 | 107 | VizDisplay* display = createWidget(mm2px(Vec(0.0, 13.039))); 108 | display->box.size = mm2px(Vec(15.237, 89.344)); 109 | display->module = module; 110 | addChild(display); 111 | 112 | addChild(createLightCentered>(mm2px(Vec(10.846, 18.068)), module, Viz::VU_LIGHTS + 2 * 0)); 113 | addChild(createLightCentered>(mm2px(Vec(10.846, 23.366)), module, Viz::VU_LIGHTS + 2 * 1)); 114 | addChild(createLightCentered>(mm2px(Vec(10.846, 28.663)), module, Viz::VU_LIGHTS + 2 * 2)); 115 | addChild(createLightCentered>(mm2px(Vec(10.846, 33.961)), module, Viz::VU_LIGHTS + 2 * 3)); 116 | addChild(createLightCentered>(mm2px(Vec(10.846, 39.258)), module, Viz::VU_LIGHTS + 2 * 4)); 117 | addChild(createLightCentered>(mm2px(Vec(10.846, 44.556)), module, Viz::VU_LIGHTS + 2 * 5)); 118 | addChild(createLightCentered>(mm2px(Vec(10.846, 49.919)), module, Viz::VU_LIGHTS + 2 * 6)); 119 | addChild(createLightCentered>(mm2px(Vec(10.846, 55.217)), module, Viz::VU_LIGHTS + 2 * 7)); 120 | addChild(createLightCentered>(mm2px(Vec(10.846, 60.514)), module, Viz::VU_LIGHTS + 2 * 8)); 121 | addChild(createLightCentered>(mm2px(Vec(10.846, 65.812)), module, Viz::VU_LIGHTS + 2 * 9)); 122 | addChild(createLightCentered>(mm2px(Vec(10.846, 71.109)), module, Viz::VU_LIGHTS + 2 * 10)); 123 | addChild(createLightCentered>(mm2px(Vec(10.846, 76.473)), module, Viz::VU_LIGHTS + 2 * 11)); 124 | addChild(createLightCentered>(mm2px(Vec(10.846, 81.771)), module, Viz::VU_LIGHTS + 2 * 12)); 125 | addChild(createLightCentered>(mm2px(Vec(10.846, 87.068)), module, Viz::VU_LIGHTS + 2 * 13)); 126 | addChild(createLightCentered>(mm2px(Vec(10.846, 92.366)), module, Viz::VU_LIGHTS + 2 * 14)); 127 | addChild(createLightCentered>(mm2px(Vec(10.846, 97.663)), module, Viz::VU_LIGHTS + 2 * 15)); 128 | } 129 | }; 130 | 131 | 132 | Model* modelViz = createModel("Viz"); 133 | -------------------------------------------------------------------------------- /src/dr_wav.c: -------------------------------------------------------------------------------- 1 | #define DR_WAV_IMPLEMENTATION 2 | #include "dr_wav.h" 3 | -------------------------------------------------------------------------------- /src/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | Plugin* pluginInstance; 5 | 6 | void init(Plugin* p) { 7 | pluginInstance = p; 8 | 9 | p->addModel(modelVCO); 10 | p->addModel(modelVCO2); 11 | p->addModel(modelVCF); 12 | p->addModel(modelVCA_1); 13 | p->addModel(modelVCA); 14 | p->addModel(modelLFO); 15 | p->addModel(modelLFO2); 16 | p->addModel(modelDelay); 17 | p->addModel(modelADSR); 18 | p->addModel(modelMixer); 19 | p->addModel(modelVCMixer); 20 | p->addModel(model_8vert); 21 | p->addModel(modelUnity); 22 | p->addModel(modelMutes); 23 | p->addModel(modelPulses); 24 | p->addModel(modelScope); 25 | p->addModel(modelSEQ3); 26 | p->addModel(modelSequentialSwitch1); 27 | p->addModel(modelSequentialSwitch2); 28 | p->addModel(modelOctave); 29 | p->addModel(modelQuantizer); 30 | p->addModel(modelSplit); 31 | p->addModel(modelMerge); 32 | p->addModel(modelSum); 33 | p->addModel(modelViz); 34 | p->addModel(modelMidSide); 35 | p->addModel(modelNoise); 36 | p->addModel(modelRandom); 37 | p->addModel(modelCVMix); 38 | p->addModel(modelFade); 39 | p->addModel(modelLogic); 40 | p->addModel(modelCompare); 41 | p->addModel(modelGates); 42 | p->addModel(modelProcess); 43 | p->addModel(modelMult); 44 | p->addModel(modelRescale); 45 | p->addModel(modelRandomValues); 46 | p->addModel(modelPush); 47 | p->addModel(modelSHASR); 48 | } 49 | 50 | 51 | MenuItem* createRangeItem(std::string label, float* gain, float* offset) { 52 | struct Range { 53 | float gain; 54 | float offset; 55 | 56 | bool operator==(const Range& other) const { 57 | return gain == other.gain && offset == other.offset; 58 | } 59 | }; 60 | 61 | static const std::vector ranges = { 62 | {10.f, 0.f}, 63 | {5.f, 0.f}, 64 | {1.f, 0.f}, 65 | {20.f, -10.f}, 66 | {10.f, -5.f}, 67 | {2.f, -1.f}, 68 | }; 69 | static std::vector labels; 70 | if (labels.empty()) { 71 | for (const Range& range : ranges) { 72 | labels.push_back(string::f("%gV to %gV", range.offset, range.offset + range.gain)); 73 | } 74 | } 75 | 76 | return createIndexSubmenuItem(label, labels, 77 | [=]() { 78 | auto it = std::find(ranges.begin(), ranges.end(), Range{*gain, *offset}); 79 | return std::distance(ranges.begin(), it); 80 | }, 81 | [=](int i) { 82 | *gain = ranges[i].gain; 83 | *offset = ranges[i].offset; 84 | } 85 | ); 86 | } -------------------------------------------------------------------------------- /src/plugin.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | using namespace rack; 5 | 6 | 7 | extern Plugin* pluginInstance; 8 | 9 | extern Model* modelVCO; 10 | extern Model* modelVCO2; 11 | extern Model* modelVCF; 12 | extern Model* modelVCA_1; 13 | extern Model* modelVCA; 14 | extern Model* modelLFO; 15 | extern Model* modelLFO2; 16 | extern Model* modelDelay; 17 | extern Model* modelADSR; 18 | extern Model* modelMixer; 19 | extern Model* modelVCMixer; 20 | extern Model* model_8vert; 21 | extern Model* modelUnity; 22 | extern Model* modelMutes; 23 | extern Model* modelPulses; 24 | extern Model* modelScope; 25 | extern Model* modelSEQ3; 26 | extern Model* modelSequentialSwitch1; 27 | extern Model* modelSequentialSwitch2; 28 | extern Model* modelOctave; 29 | extern Model* modelQuantizer; 30 | extern Model* modelSplit; 31 | extern Model* modelMerge; 32 | extern Model* modelSum; 33 | extern Model* modelViz; 34 | extern Model* modelMidSide; 35 | extern Model* modelNoise; 36 | extern Model* modelRandom; 37 | extern Model* modelCVMix; 38 | extern Model* modelFade; 39 | extern Model* modelLogic; 40 | extern Model* modelCompare; 41 | extern Model* modelGates; 42 | extern Model* modelProcess; 43 | extern Model* modelMult; 44 | extern Model* modelRescale; 45 | extern Model* modelRandomValues; 46 | extern Model* modelPush; 47 | extern Model* modelSHASR; 48 | 49 | 50 | struct DigitalDisplay : Widget { 51 | std::string fontPath; 52 | std::string bgText; 53 | std::string text; 54 | float fontSize; 55 | NVGcolor bgColor = nvgRGB(0x46,0x46, 0x46); 56 | NVGcolor fgColor = SCHEME_YELLOW; 57 | Vec textPos; 58 | 59 | void prepareFont(const DrawArgs& args) { 60 | // Get font 61 | std::shared_ptr font = APP->window->loadFont(fontPath); 62 | if (!font) 63 | return; 64 | nvgFontFaceId(args.vg, font->handle); 65 | nvgFontSize(args.vg, fontSize); 66 | nvgTextLetterSpacing(args.vg, 0.0); 67 | nvgTextAlign(args.vg, NVG_ALIGN_RIGHT); 68 | } 69 | 70 | void draw(const DrawArgs& args) override { 71 | // Background 72 | nvgBeginPath(args.vg); 73 | nvgRoundedRect(args.vg, 0, 0, box.size.x, box.size.y, 2); 74 | nvgFillColor(args.vg, nvgRGB(0x19, 0x19, 0x19)); 75 | nvgFill(args.vg); 76 | 77 | prepareFont(args); 78 | 79 | // Background text 80 | nvgFillColor(args.vg, bgColor); 81 | nvgText(args.vg, textPos.x, textPos.y, bgText.c_str(), NULL); 82 | } 83 | 84 | void drawLayer(const DrawArgs& args, int layer) override { 85 | if (layer == 1) { 86 | prepareFont(args); 87 | 88 | // Foreground text 89 | nvgFillColor(args.vg, fgColor); 90 | nvgText(args.vg, textPos.x, textPos.y, text.c_str(), NULL); 91 | } 92 | Widget::drawLayer(args, layer); 93 | } 94 | }; 95 | 96 | 97 | struct ChannelDisplay : DigitalDisplay { 98 | ChannelDisplay() { 99 | fontPath = asset::system("res/fonts/DSEG7ClassicMini-BoldItalic.ttf"); 100 | textPos = Vec(22, 20); 101 | bgText = "18"; 102 | fontSize = 16; 103 | } 104 | }; 105 | 106 | 107 | template 108 | struct YellowRedLight : TBase { 109 | YellowRedLight() { 110 | this->addBaseColor(SCHEME_YELLOW); 111 | this->addBaseColor(SCHEME_RED); 112 | } 113 | }; 114 | 115 | 116 | template 117 | struct YellowBlueLight : TBase { 118 | YellowBlueLight() { 119 | this->addBaseColor(SCHEME_YELLOW); 120 | this->addBaseColor(SCHEME_BLUE); 121 | } 122 | }; 123 | 124 | 125 | struct VCVBezelBig : app::SvgSwitch { 126 | VCVBezelBig() { 127 | momentary = true; 128 | addFrame(Svg::load(asset::plugin(pluginInstance, "res/VCVBezelBig.svg"))); 129 | } 130 | }; 131 | 132 | 133 | template 134 | struct VCVBezelLightBig : TBase { 135 | VCVBezelLightBig() { 136 | this->borderColor = color::BLACK_TRANSPARENT; 137 | this->bgColor = color::BLACK_TRANSPARENT; 138 | this->box.size = mm2px(math::Vec(11.1936, 11.1936)); 139 | } 140 | }; 141 | 142 | 143 | MenuItem* createRangeItem(std::string label, float* gain, float* offset); 144 | --------------------------------------------------------------------------------