├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── Makefile ├── README.md ├── manual ├── CV.md ├── CV.pdf ├── CVSeq.md ├── CVSeq.pdf ├── Carbon.md ├── Carbon.pdf ├── DTMF.md ├── DTMF.pdf ├── Eq.md ├── Eq.pdf ├── K.md ├── K.pdf ├── M.md ├── M.pdf ├── Mixer.md ├── Mixer.pdf ├── MixerCV.md ├── MixerCV.pdf ├── Noise.md ├── Noise.pdf ├── Not.md ├── Not.pdf ├── Oscar2.md ├── Oscar2.pdf ├── Pan.md ├── Pan.pdf ├── Shift.md ├── Shift.pdf ├── Tine.md ├── Tine.pdf ├── Tsunami.md ├── Tsunami.pdf ├── X.md ├── X.pdf └── images │ ├── CV-full.png │ ├── CV-small.png │ ├── CV.png │ ├── CVSeq-full.png │ ├── CVSeq-small.png │ ├── CVSeq.png │ ├── Carbon-full.png │ ├── Carbon-small.png │ ├── Carbon.png │ ├── DTMF-full.png │ ├── DTMF-small.png │ ├── DTMF.png │ ├── Eq-full.png │ ├── Eq-small.png │ ├── Eq.png │ ├── K-full.png │ ├── K-small.png │ ├── K.png │ ├── M-full.png │ ├── M-small.png │ ├── M.png │ ├── Mixer-full.png │ ├── Mixer-small.png │ ├── Mixer.png │ ├── MixerCV-full.png │ ├── MixerCV-small.png │ ├── MixerCV.png │ ├── Noise-full.png │ ├── Noise-small.png │ ├── Noise.png │ ├── Not-full.png │ ├── Not-small.png │ ├── Not.png │ ├── Oscar2-full.png │ ├── Oscar2-small.png │ ├── Oscar2.png │ ├── Pan-full.png │ ├── Pan-small.png │ ├── Pan.png │ ├── SVModular.png │ ├── Shift-full.png │ ├── Shift-small.png │ ├── Shift.png │ ├── Tine-full.png │ ├── Tine-small.png │ ├── Tine.png │ ├── Tsunami-full.png │ ├── Tsunami-small.png │ ├── Tsunami.png │ ├── X-full.png │ ├── X-small.png │ └── X.png ├── plugin.json ├── res ├── CDSlider.svg ├── CDSliderHandle.svg ├── CV.svg ├── CVSeq.svg ├── Carbon.svg ├── DTMF.svg ├── Eq.svg ├── K.svg ├── Knob.svg ├── KnobSmall.svg ├── M.svg ├── Mixer.svg ├── MixerCV.svg ├── Noise.svg ├── Not.svg ├── Oscar2.svg ├── Pan.svg ├── Port.svg ├── Shift.svg ├── Tine.svg ├── Tsunami.svg ├── X.svg └── digit.ttf └── src ├── CharredDesert.cpp ├── CharredDesert.hpp ├── LFO.cpp ├── LFO.hpp ├── controller ├── Biquad.cpp ├── Biquad.h ├── CV.cpp ├── CV.hpp ├── CVSeq.cpp ├── CVSeq.hpp ├── Carbon.cpp ├── Carbon.hpp ├── DTMF.cpp ├── DTMF.hpp ├── Eq.cpp ├── Eq.hpp ├── K.cpp ├── K.hpp ├── M.cpp ├── M.hpp ├── Mixer.cpp ├── Mixer.hpp ├── MixerCV.cpp ├── MixerCV.hpp ├── Noise.cpp ├── Noise.hpp ├── Not.cpp ├── Not.hpp ├── Oscar2.cpp ├── Oscar2.hpp ├── Pan.cpp ├── Pan.hpp ├── Shift.cpp ├── Shift.hpp ├── Tine.cpp ├── Tine.hpp ├── Tsunami.cpp ├── Tsunami.hpp ├── X.cpp ├── X.hpp ├── filters.cpp └── filters.hpp ├── model ├── Compressor.hpp ├── Delay.hpp └── MessageBus.hpp └── view ├── CV.cpp ├── CVSeq.cpp ├── Carbon.cpp ├── DTMF.cpp ├── Eq.cpp ├── K.cpp ├── M.cpp ├── Mixer.cpp ├── MixerCV.cpp ├── Noise.cpp ├── Not.cpp ├── Oscar2.cpp ├── Pan.cpp ├── Shift.cpp ├── Tine.cpp ├── Tsunami.cpp ├── X.cpp └── components.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | dep 4 | plugin.dylib 5 | plugin.dll 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/SynthDevKit"] 2 | path = deps/SynthDevKit 3 | url = https://github.com/JerrySievert/SynthDevKit.git 4 | [submodule "arptest"] 5 | path = arptest 6 | url = https://github.com/JerrySievert/arptest.git 7 | [submodule "deps/rack-components"] 8 | path = deps/rack-components 9 | url = https://github.com/JerrySievert/rack-components 10 | branch = v1.0 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | 124 | In addition, the flame SVG used on the module faces has its own license: 125 | 126 | Flame SVG (c) by Matt Hawdon 127 | 128 | Flame SVG is licensed under a 129 | Creative Commons Attribution-NonCommercial 3.0 Unported License. 130 | 131 | You should have received a copy of the license along with this 132 | work. If not, see . 133 | 134 | Drop-shadow code in components.hpp has the following license: 135 | 136 | Copyright (c) 2017-2018, Lindenberg Research / Patrick Lindenberg 137 | All rights reserved. 138 | 139 | Redistribution and use in source and binary forms, with or without 140 | modification, are permitted provided that the following conditions are met: 141 | 142 | * Commercial redistribution of the code, or parts, in any form 143 | must be granted by the author. 144 | 145 | * Redistributions of source code must retain the above copyright notice, this 146 | list of conditions and the following disclaimer. 147 | 148 | * Redistributions in binary form must reproduce the above copyright notice, 149 | this list of conditions and the following disclaimer in the documentation 150 | and/or other materials provided with the distribution. 151 | 152 | * Neither the name of Lindenberg Research nor the names of its 153 | contributors may be used to endorse or promote products derived from 154 | this software without specific prior written permission. 155 | 156 | 157 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 158 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 159 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 160 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 161 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 162 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 163 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 164 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 165 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 166 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 167 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # FLAGS will be passed to both the C and C++ compiler 2 | FLAGS += 3 | CFLAGS += 4 | CXXFLAGS += 5 | 6 | # Careful about linking to libraries, since you can't assume much about the user's environment and library search path. 7 | # Static libraries are fine. 8 | LDFLAGS += 9 | 10 | # Controllers 11 | CONTROLLERS += $(wildcard src/controller/*.cpp) 12 | 13 | # SynthDevKit 14 | SYNTHDEVKIT += $(wildcard deps/SynthDevKit/src/*.cpp) 15 | 16 | # Views 17 | VIEWS += $(wildcard src/view/*.cpp) 18 | 19 | # Add .cpp and .c files to the build 20 | SOURCES += $(wildcard src/*.cpp) $(CONTROLLERS) $(VIEWS) $(SYNTHDEVKIT) 21 | 22 | # Add files to the ZIP package when running `make dist` 23 | # The compiled plugin is automatically added. 24 | DISTRIBUTABLES += $(wildcard LICENSE*) res deps/rack-components/res 25 | 26 | # Must include the VCV plugin Makefile framework 27 | RACK_DIR ?= ../.. 28 | include $(RACK_DIR)/plugin.mk 29 | 30 | # Sources to test for ArpTest - this will usually only include your controllers 31 | TEST_SOURCES += $(CONTROLLERS) $(SYNTHDEVKIT) 32 | 33 | # Add any tests 34 | TEST_SOURCES += $(wildcard tests/*.cpp) 35 | 36 | # By default, ARPTEST_DIR is arptest in your current directory, but you can override it 37 | # In this example, arptest lives one path down 38 | ARPTEST_DIR ?= ./arptest 39 | 40 | include $(ARPTEST_DIR)/build.mk 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Charred Desert 2 | 3 | Interesting modules for [VCVRack](https://github.com/vcvrack/rack). 4 | 5 | The name is an homage to [FrozenWasteland](https://github.com/almosteric/FrozenWasteland), 6 | @almosteric's esoteric module set. 7 | 8 | This is a growing work in progress, with only a couple of utility modules currently. 9 | More modules will be added, so if you are interested in following along, either 10 | star the repo, or keep checking back. 11 | 12 | See: https://legitimatesounding.com/sv-modular/charred-desert/index.html for more information. 13 | 14 | ## Building 15 | 16 | ``` 17 | $ git clone https://github.com/JerrySievert/CharredDesert.git 18 | $ cd CharredDesert 19 | $ git submodule update --init 20 | $ make 21 | ``` 22 | -------------------------------------------------------------------------------- /manual/CV.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CV Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # CV Users Manual 13 | 14 | ![CV Image](images/CV.png "CV") 15 | 16 | CV provides a constant voltage between `0 volts` and `10 volts` when active. 17 | 18 | CV is divided into two parts, each providing the same functionality, making it 19 | two _Miniature_ modules. 20 | 21 | The `ON` switch will light up when the voltage is active, otherwise the 22 | output will be a constant `0 volts`. 23 | 24 | The `VAL` parameter adjusts the voltage level from `0 volts` to `10 volts`. 25 | 26 | `OUT` provides the output of the module. 27 | -------------------------------------------------------------------------------- /manual/CV.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/CV.pdf -------------------------------------------------------------------------------- /manual/CVSeq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CV-Seq Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # CV-SEQ 13 | 14 | ![CV-Seq Image](images/CVSeq.png "CV-Seq") 15 | 16 | **CV-SEQ** is a four-step sequencer built for CV output. Unlike note-based 17 | sequencers, which output in the range of `-5`, *CV-SEQ* operates on CV standards, 18 | outputting `0` through `10`. This is helpful in automating CV tasks, allowing 19 | for precision steps driven by a clock. *CV-SEQ* outputs whatever value is set 20 | by the combination of CV and knob for the duration of the _beat_. If any of 21 | these values change, the output will change as well. 22 | 23 | **CV-SEQ** has 5 inputs and 1 output. The primary input is the `CLK`, or clock; 24 | this provides the step time, or _beat_ that triggers changing which value to 25 | output. The `CLK` triggers on a value of `1.7`. 26 | 27 | `OUT` outputs the current step value. 28 | 29 | Each step is controlled by a pair consisting of a CV input and a knob, representing 30 | the values of `0` to `10`. When the CV input is in use, its input is added to 31 | value represented by the knob. Thus, if the CV input is receiving `3.4`, and 32 | the knob is set to `2.1`, the output will be `3.4`, the value of the input and 33 | of the knob together. 34 | 35 | Each triggered value will output its value for the duration of the _beat_, and 36 | the LED will light up for each trigger position. 37 | -------------------------------------------------------------------------------- /manual/CVSeq.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/CVSeq.pdf -------------------------------------------------------------------------------- /manual/Carbon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Carbon Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Carbon 13 | 14 | ![Carbon Image](images/Carbon.png "Carbon") 15 | 16 | **Carbon** is a filter made to emulate a Moog resonance filter. It has frequency 17 | and resonance inputs also controllable by CV. 18 | 19 | 20 | **Carbon** has 3 inputs and 1 output. The primary input is `IN`, which provides 21 | audio to be filtered. 22 | 23 | `OUT` outputs filtered value. 24 | 25 | A frequency is chosen by knob and CV input. Knob values range from `20hz` to `7000hz`, 26 | whereas CV inputs will multiply by `1000` (against `0` to `10` values). These are added 27 | together and clamped to `0` minimum, and `7000` maximum. 28 | 29 | Resonance is controlled by the `REZ` parameter and CV input. The knob provides 30 | values between `0` and `4`, and the CV input scales by dividing by `10`. These 31 | are added for the final value, ranging from `0` to `4`. 32 | -------------------------------------------------------------------------------- /manual/Carbon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Carbon.pdf -------------------------------------------------------------------------------- /manual/DTMF.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "DTMF Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # DTMF 13 | 14 | ![DTMF Image](images/DTMF.png "DTMF") 15 | 16 | 17 | **DTMF** plays dual tone multi-frequency sounds, the tones you hear when you 18 | press the buttons on a phone. 19 | 20 | **DTMF** has 2 inputs: `GATE` and `V/OCT`, and a single `OUT`. 21 | 22 | `GATE` is a CV input that acts as an ON/OFF switch for the tones. When the `GATE` 23 | receives a value of at least `1.7`, it starts playing whichever tone is specified 24 | via the `V/OCT` input. 25 | 26 | There are 16 tones to choose from, each with a different note in octaves 4 and 5: 27 | 28 | |Note|Tone|Note|Tone|Note|Tone|Note|Tone| 29 | |----|----|----|----|----|----|----|----| 30 | |C-4 | 1 |C#-4| 2 |D-4 | 3 |D#-4| A | 31 | |E-4 | 4 |F-4 | 5 |F#-4| 5 |G-4 | B | 32 | |G#-4| 7 |A-4 | 8 |A#-4| 9 |B-4 | C | 33 | |C-5 | * |C#-5| 0 |D-5 | # |D#-5| D | 34 | -------------------------------------------------------------------------------- /manual/DTMF.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/DTMF.pdf -------------------------------------------------------------------------------- /manual/Eq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "EQ Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # EQ 13 | 14 | ![EQ Image](images/Eq.png "EQ") 15 | 16 | 17 | **EQ** is a simple 7 function Equalizer, with `FREQ` and `Q`. **EQ** is meant 18 | to act either as a band-based equalizer or as part of a chain of equalizers. 19 | 20 | **EQ** has one `IN` input, and a single `OUT` output. `FREQ` allows for the 21 | selection of frequency, `Q` for the width of the band to be equalized; lower `Q` 22 | means a larger band, whereas higher `Q` is a smaller band of frequencies. 23 | 24 | The `TYPE` allows you to select which type of frequency filter is being used: 25 | 26 | * low pass 27 | * high pass 28 | * band pass 29 | * notch 30 | * low shelf 31 | * high shelf 32 | -------------------------------------------------------------------------------- /manual/Eq.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Eq.pdf -------------------------------------------------------------------------------- /manual/K.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "K Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # K 13 | 14 | ![K Image](images/K.png "K") 15 | 16 | 17 | **K** is a simple and small compressor. 18 | 19 | **K** has one `IN` input, and a single `OUT` output. `THR` or Threshold specifies 20 | the voltage threshold for the compressor to kick in. 21 | 22 | `RATIO` specifies the ratio to scale any values above the threshold. 23 | 24 | `ATT` or Attack specifies the attack value in milliseconds to allow for the 25 | compression envelope to activate. 26 | 27 | `REL` or Release specifies the release value in milliseconds to allow for the 28 | compression envelope to activate. 29 | -------------------------------------------------------------------------------- /manual/K.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/K.pdf -------------------------------------------------------------------------------- /manual/M.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "M Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # M 13 | 14 | ![M Image](images/M.png "M") 15 | 16 | 17 | **M** is a small footprint mixer consisting of two parallel modules. 18 | 19 | Each `IN` input is tied to the half-module, allowing for two inputs to be 20 | mixed. 21 | 22 | `OUT` provides the mixed output, while `MIX` allows for the amount of each 23 | signal to be mixed in. 24 | -------------------------------------------------------------------------------- /manual/M.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/M.pdf -------------------------------------------------------------------------------- /manual/Mixer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mixer Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Mixer 13 | 14 | ![Mixer Image](images/Mixer.png "Mixer") 15 | 16 | 17 | **Mixer** is an 8 input mixing module. 18 | 19 | Each channel consists of an `INPUT`, `LED` volume indicator, `VOLUME` adjustment, 20 | `PAN` for stereo mixing, `SOLO` for specifying the soloing of a track, and 21 | `MUTE` to mute a track. 22 | -------------------------------------------------------------------------------- /manual/Mixer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Mixer.pdf -------------------------------------------------------------------------------- /manual/MixerCV.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "MixerCV Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # MixerCV 13 | 14 | ![MixerCV Image](images/MixerCV.png "MixerCV") 15 | 16 | 17 | **MixerCV** is an 8 input mixing module with the addition of CV inputs to 18 | automate parameters, as well as Send/Receive functionality on a per-track 19 | basis. 20 | 21 | Each channel consists of an `INPUT`, `LED` volume indicator, `VOLUME` adjustment, 22 | `PAN` for stereo mixing, `SOLO` for specifying the soloing of a track, and 23 | `MUTE` to mute a track. 24 | 25 | In addition, `SEND` allows for each track to send to an external effect module, 26 | along with `RECV` to receive the output. The `MIX` or "wetness" of this effect 27 | can be set with the `MIX` knob, as well as with the `MIX` CV input. 28 | -------------------------------------------------------------------------------- /manual/MixerCV.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/MixerCV.pdf -------------------------------------------------------------------------------- /manual/Noise.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Noise Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Noise 13 | 14 | ![Noise Image](images/Noise.png "Noise") 15 | 16 | **NOISE** is a simple noise generator, offering _white_ noise and _pink_ noise. 17 | 18 | 19 | **NOISE** has a single input, `GATE`, which allows noise to be generated. When 20 | `GATE` accepts a signal at or above `1.7`, it generates the selected noise and 21 | outputs via `OUT`. 22 | 23 | Noise can be chosen between _white_ and _pink_. 24 | -------------------------------------------------------------------------------- /manual/Noise.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Noise.pdf -------------------------------------------------------------------------------- /manual/Not.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Not Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Not 13 | 14 | ![Not Image](images/Not.png "Not") 15 | 16 | **NOT** provides inversion functionality, either as a waveform or as CV. 17 | 18 | 19 | **NOT** has one `IN` input, and a single `OUT` output, as well as a type 20 | selection. Types vary the functionality of the module: _CV_ offers a binary 21 | (on/off) output. When the input is `1.7` or above, `1.7` is output, otherwise 22 | `0` is output. When _V/OCT_ is selected, the inverse is output; an input of `3.4` 23 | outputs `-3.4`, and an input of `-1.8` outputs `1.8`. 24 | -------------------------------------------------------------------------------- /manual/Not.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Not.pdf -------------------------------------------------------------------------------- /manual/Oscar2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Oscar^2 Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Oscar^2 13 | 14 | ![Oscar^2 Image](images/Oscar2.png "Oscar^2") 15 | 16 | **OSCAR^2** is an advanced waveform oscillator. It provides two oscillators 17 | that are combined with each other with adjustable parameters. 18 | 19 | **OSCAR^2** has a single `V/OCT` input for note selection, and an `OUT` port for 20 | the resulting waveform. 21 | 22 | Waveforms can be selected via the `SHAPE` selection. This allows for the waveform 23 | to be selected with a combination of CV and knob. The knob allows for you to 24 | select from: 25 | 26 | * Sine 27 | * Triangle 28 | * Sawtooth 29 | * Square 30 | 31 | When CV is added, its value is added to the selection, so if the CV input is `1.3`, 32 | and the knob has chosen _Sine_, then _Triangle_ is chosen. The result will be 33 | displayed. 34 | 35 | `SHIFT` allows for you to shift the waveforms starting position, from `0` to `10`, 36 | where `0` is not shifted at all, and `10` is shifted 100% to the right. The 37 | `SHIFT` CV value can be added to the knob value. 38 | 39 | **OCTAVE** is a nine octave selection. The knob value provides the octave, and 40 | the CV value can be added to this. 41 | 42 | **FINE** provides for fine tuning. 43 | 44 | **RANDOM** changes the possibility of a waveform drawing being skipped. Instead 45 | of outputing a waveform, `0` is output. 46 | 47 | The resulting output is a combination of the two waveforms, mixed by the `MIX` input. 48 | -------------------------------------------------------------------------------- /manual/Oscar2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Oscar2.pdf -------------------------------------------------------------------------------- /manual/Pan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Pan Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Pan 13 | 14 | ![Pan Image](images/Pan.png "Pan") 15 | 16 | **PAN** allows for a single input to pan between two outputs. 17 | 18 | **PAN** takes a value from `IN` and allows a selection via CV and knob to 19 | control which output (or both) to send the value. Each `OUT` output provides 20 | one side of the panned output. 21 | -------------------------------------------------------------------------------- /manual/Pan.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Pan.pdf -------------------------------------------------------------------------------- /manual/Shift.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Shift Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Shift 13 | 14 | ![Shift Image](images/Shift.png "Shift") 15 | 16 | **SHIFT** takes a signal, and shifts it in either a positive or negative direction. 17 | One use for this is to take a waveform oscillator with values between `-5` and `5`, 18 | and turn it into a CV source with values from `0` and `10`. 19 | 20 | 21 | **SHIFT** has an input marked `IN`, an output marked `OUT`, and the ability to 22 | choose the amount to shift via CV and a knob. A choice can be made to either 23 | clip the values between `-5` and `5`, or to output the natural value. 24 | -------------------------------------------------------------------------------- /manual/Shift.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Shift.pdf -------------------------------------------------------------------------------- /manual/Tine.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tine Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Tine 13 | 14 | ![Tine Image](images/Tine.png "Tine") 15 | 16 | **TINE** takes a signal and a `MOD` or Modulator (or Carrier) and outputs two 17 | signals. 18 | 19 | The `POLARITY` switch changes between bipolar and unipolar for the signal input. 20 | 21 | `SPLIT` determines the voltage level at which to split the signal. When the 22 | modulator voltage is below `SPLIT` value, the signal is routed to the `LOW` 23 | output. When the modulator voltage is greater or equal to the `SPLIT` voltage, 24 | the `AUD` input is sent to the `HIGH` output. 25 | 26 | There are attenuators for both the `HIGH` and `LOW` outputs as well. 27 | -------------------------------------------------------------------------------- /manual/Tine.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Tine.pdf -------------------------------------------------------------------------------- /manual/Tsunami.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tsunami Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # Tsunami 13 | 14 | ![Tsunami Image](images/Tsunami.png "Tsunami") 15 | 16 | **Tsunami** is a wave multiplier. It takes an input via the `IN` jack and 17 | multiplies eight times using an offset set via the *Shift* parameter and CV. 18 | 19 | This offset wave is then added to the mix and the current value of the mix, 20 | from top to bottom, is output in each *OUT* jack. 21 | 22 | In addition, the *POLY OUT* output is a poly output with each channel set to the 23 | unmixed, shifted output. 24 | -------------------------------------------------------------------------------- /manual/Tsunami.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/Tsunami.pdf -------------------------------------------------------------------------------- /manual/X.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "X Manual" 3 | author: [SV Modular] 4 | date: "October, 2019" 5 | subject: "Charred Desert" 6 | keywords: [CharredDesert, VCVRack, Manual] 7 | lang: "en" 8 | titlepage: true 9 | logo: "images/SVModular.png" 10 | ... 11 | 12 | # X 13 | 14 | ![X Image](images/X.png "X") 15 | 16 | **X** is an audio exciter. It takes an input via the `IN` jack and "excites" 17 | that audio, sending it to the `OUT` jack. 18 | 19 | The `MIX` parameter controls the "wetness" and "dryness" of the signal. 20 | -------------------------------------------------------------------------------- /manual/X.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/X.pdf -------------------------------------------------------------------------------- /manual/images/CV-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/CV-full.png -------------------------------------------------------------------------------- /manual/images/CV-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/CV-small.png -------------------------------------------------------------------------------- /manual/images/CV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/CV.png -------------------------------------------------------------------------------- /manual/images/CVSeq-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/CVSeq-full.png -------------------------------------------------------------------------------- /manual/images/CVSeq-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/CVSeq-small.png -------------------------------------------------------------------------------- /manual/images/CVSeq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/CVSeq.png -------------------------------------------------------------------------------- /manual/images/Carbon-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Carbon-full.png -------------------------------------------------------------------------------- /manual/images/Carbon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Carbon-small.png -------------------------------------------------------------------------------- /manual/images/Carbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Carbon.png -------------------------------------------------------------------------------- /manual/images/DTMF-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/DTMF-full.png -------------------------------------------------------------------------------- /manual/images/DTMF-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/DTMF-small.png -------------------------------------------------------------------------------- /manual/images/DTMF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/DTMF.png -------------------------------------------------------------------------------- /manual/images/Eq-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Eq-full.png -------------------------------------------------------------------------------- /manual/images/Eq-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Eq-small.png -------------------------------------------------------------------------------- /manual/images/Eq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Eq.png -------------------------------------------------------------------------------- /manual/images/K-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/K-full.png -------------------------------------------------------------------------------- /manual/images/K-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/K-small.png -------------------------------------------------------------------------------- /manual/images/K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/K.png -------------------------------------------------------------------------------- /manual/images/M-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/M-full.png -------------------------------------------------------------------------------- /manual/images/M-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/M-small.png -------------------------------------------------------------------------------- /manual/images/M.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/M.png -------------------------------------------------------------------------------- /manual/images/Mixer-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Mixer-full.png -------------------------------------------------------------------------------- /manual/images/Mixer-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Mixer-small.png -------------------------------------------------------------------------------- /manual/images/Mixer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Mixer.png -------------------------------------------------------------------------------- /manual/images/MixerCV-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/MixerCV-full.png -------------------------------------------------------------------------------- /manual/images/MixerCV-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/MixerCV-small.png -------------------------------------------------------------------------------- /manual/images/MixerCV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/MixerCV.png -------------------------------------------------------------------------------- /manual/images/Noise-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Noise-full.png -------------------------------------------------------------------------------- /manual/images/Noise-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Noise-small.png -------------------------------------------------------------------------------- /manual/images/Noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Noise.png -------------------------------------------------------------------------------- /manual/images/Not-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Not-full.png -------------------------------------------------------------------------------- /manual/images/Not-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Not-small.png -------------------------------------------------------------------------------- /manual/images/Not.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Not.png -------------------------------------------------------------------------------- /manual/images/Oscar2-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Oscar2-full.png -------------------------------------------------------------------------------- /manual/images/Oscar2-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Oscar2-small.png -------------------------------------------------------------------------------- /manual/images/Oscar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Oscar2.png -------------------------------------------------------------------------------- /manual/images/Pan-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Pan-full.png -------------------------------------------------------------------------------- /manual/images/Pan-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Pan-small.png -------------------------------------------------------------------------------- /manual/images/Pan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Pan.png -------------------------------------------------------------------------------- /manual/images/SVModular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/SVModular.png -------------------------------------------------------------------------------- /manual/images/Shift-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Shift-full.png -------------------------------------------------------------------------------- /manual/images/Shift-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Shift-small.png -------------------------------------------------------------------------------- /manual/images/Shift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Shift.png -------------------------------------------------------------------------------- /manual/images/Tine-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Tine-full.png -------------------------------------------------------------------------------- /manual/images/Tine-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Tine-small.png -------------------------------------------------------------------------------- /manual/images/Tine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Tine.png -------------------------------------------------------------------------------- /manual/images/Tsunami-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Tsunami-full.png -------------------------------------------------------------------------------- /manual/images/Tsunami-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Tsunami-small.png -------------------------------------------------------------------------------- /manual/images/Tsunami.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/Tsunami.png -------------------------------------------------------------------------------- /manual/images/X-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/X-full.png -------------------------------------------------------------------------------- /manual/images/X-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/X-small.png -------------------------------------------------------------------------------- /manual/images/X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/manual/images/X.png -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "CharredDesert", 3 | "name": "CharredDesert", 4 | "version": "1.3.1", 5 | "license": "CC0-1.0", 6 | "author": "Jerry Sievert", 7 | "authorEmail": "contact@svmodular.com", 8 | "authorUrl": "https://svmodular.com/index.html", 9 | "pluginUrl": "https://svmodular.com/plugin/vcv/charred-desert.html", 10 | "manualUrl": "https://svmodular.com/plugin/vcv/charred-desert.html", 11 | "sourceUrl": "https://github.com/SVModular/CharredDesert", 12 | "brand": "SV Modular", 13 | "modules": [ 14 | { 15 | "slug": "DTMF", 16 | "name": "DTMF", 17 | "description": "DTMF Tone Generator", 18 | "tags": [ 19 | "Envelope generator" 20 | ], 21 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/DTMF.pdf" 22 | }, 23 | { 24 | "slug": "Noise", 25 | "name": "Noise", 26 | "description": "White and Pink Noise Generator", 27 | "tags": [ 28 | "Envelope generator", 29 | "Noise" 30 | ], 31 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Noise.pdf" 32 | }, 33 | { 34 | "slug": "CVSequencer", 35 | "name": "CV Sequencer", 36 | "description": "CV Sequencer", 37 | "tags": [ 38 | "Logic", 39 | "Sequencer" 40 | ], 41 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/CVSeq.pdf" 42 | }, 43 | { 44 | "slug": "Not", 45 | "name": "Not", 46 | "description": "CV and Voltage Inverter", 47 | "tags": [ 48 | "Logic" 49 | ], 50 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Not.pdf" 51 | }, 52 | { 53 | "slug": "Pan", 54 | "name": "Pan", 55 | "description": "Stereo Panner", 56 | "tags": [ 57 | "Panning" 58 | ], 59 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Pan.pdf" 60 | }, 61 | { 62 | "slug": "Shift", 63 | "name": "Shift", 64 | "description": "CV and Waveform Shifter/Attenuator", 65 | "tags": [ 66 | "Logic", 67 | "Attenuator" 68 | ], 69 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Shift.pdf" 70 | }, 71 | { 72 | "slug": "Oscar2", 73 | "name": "Oscar^2", 74 | "description": "Oscillator Madness", 75 | "tags": [ 76 | "VCO", 77 | "Waveshaper" 78 | ], 79 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Oscar2.pdf" 80 | }, 81 | { 82 | "slug": "Eq", 83 | "name": "Eq", 84 | "description": "CV Controlled Equalizer", 85 | "tags": [ 86 | "Equalizer" 87 | ], 88 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Eq.pdf" 89 | }, 90 | { 91 | "slug": "Carbon", 92 | "name": "Carbon", 93 | "description": "Moog-like Filter", 94 | "tags": [ 95 | "VCF" 96 | ], 97 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Carbon.pdf" 98 | }, 99 | { 100 | "slug": "Mixer", 101 | "name": "Mixer", 102 | "description": "8 Channel Stereo Mixer", 103 | "tags": [ 104 | "Mixer" 105 | ], 106 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Mixer.pdf" 107 | }, 108 | { 109 | "slug": "MixerCV", 110 | "name": "Mixer CV", 111 | "description": "CV Controlled 8 Channel Mixer", 112 | "tags": [ 113 | "Mixer" 114 | ], 115 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/MixerCV.pdf" 116 | }, 117 | { 118 | "slug": "CV", 119 | "name": "CV", 120 | "description": "Simple CV Manipulator", 121 | "tags": [ 122 | "Logic" 123 | ], 124 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/CV.pdf" 125 | }, 126 | { 127 | "slug": "M", 128 | "name": "M", 129 | "description": "Mini 2 Channel Mixer", 130 | "tags": [ 131 | "Mixer" 132 | ], 133 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/M.pdf" 134 | }, 135 | { 136 | "slug": "Tine", 137 | "name": "Tine", 138 | "description": "Voltage Based Splitter", 139 | "tags": [ 140 | "Attenuator", 141 | "Mixer" 142 | ], 143 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Tine.pdf" 144 | }, 145 | { 146 | "slug": "K", 147 | "name": "K", 148 | "description": "K is for Compressor", 149 | "tags": [ 150 | "Attenuator", 151 | "Compressor" 152 | ], 153 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/K.pdf" 154 | }, 155 | { 156 | "slug": "X", 157 | "name": "X", 158 | "description": "Audio Exciter", 159 | "tags": [ 160 | "Effect" 161 | ], 162 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/X.pdf" 163 | }, 164 | { 165 | "slug": "Tsunami", 166 | "name": "Tsunami", 167 | "description": "Tsunami Wave Multiplier", 168 | "tags": [ 169 | "Effect", 170 | "Delay", 171 | "Waveshaper" 172 | ], 173 | "manualUrl": "https://svmodular.s3-us-west-2.amazonaws.com/manual/charred-desert/Tsunami.pdf" 174 | } 175 | ] 176 | } 177 | -------------------------------------------------------------------------------- /res/CDSlider.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /res/CDSliderHandle.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /res/DTMF.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 25 | image/svg+xml 26 | 28 | 29 | 30 | 31 | 32 | 56 | 58 | 61 | 65 | 69 | 70 | 73 | 77 | 81 | 82 | 85 | 89 | 90 | 93 | 97 | 98 | 101 | 105 | 109 | 110 | 119 | 121 | 130 | 131 | 136 | 142 | 146 | 150 | 154 | 158 | 162 | 163 | 167 | 171 | 175 | 179 | 183 | 184 | 188 | 192 | 196 | 200 | 204 | 208 | 209 | 213 | 217 | 221 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /res/Knob.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /res/KnobSmall.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/Noise.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 25 | image/svg+xml 26 | 28 | 29 | 30 | 31 | 32 | 56 | 58 | 61 | 65 | 69 | 70 | 73 | 77 | 81 | 82 | 85 | 89 | 90 | 93 | 97 | 98 | 101 | 105 | 109 | 110 | 119 | 121 | 123 | 125 | 127 | 129 | 131 | 133 | 142 | 143 | 148 | 154 | 158 | 162 | 166 | 170 | 174 | 178 | 179 | 183 | 187 | 191 | 195 | 199 | 200 | 204 | 208 | 212 | 216 | 220 | 224 | 225 | 229 | 233 | 237 | 241 | 245 | 246 | 250 | 254 | 258 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /res/Not.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 25 | image/svg+xml 26 | 28 | 29 | 30 | 31 | 32 | 56 | 58 | 61 | 65 | 69 | 70 | 73 | 77 | 81 | 82 | 85 | 89 | 90 | 93 | 97 | 98 | 101 | 105 | 109 | 110 | 119 | 121 | 123 | 125 | 127 | 129 | 131 | 133 | 142 | 143 | 148 | 154 | 158 | 162 | 166 | 170 | 171 | 175 | 179 | 183 | 184 | 188 | 192 | 196 | 197 | 201 | 205 | 209 | 213 | 217 | 221 | 222 | 226 | 230 | 234 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /res/Port.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 29 | 30 | 32 | 36 | 37 | 39 | 43 | 50 | 51 | 52 | 59 | 64 | 65 | 67 | 73 | 74 | 76 | 80 | 81 | 83 | 89 | 90 | 92 | 96 | 97 | 99 | 103 | 110 | 111 | 112 | 119 | 124 | 125 | 127 | 133 | 134 | 136 | 140 | 141 | 142 | 165 | 167 | 168 | 170 | image/svg+xml 171 | 173 | 174 | 175 | 176 | 177 | 182 | 187 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /res/digit.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SVModular/CharredDesert/c8d43f7ef698b5c847764c07a446be65337c7d2e/res/digit.ttf -------------------------------------------------------------------------------- /src/CharredDesert.cpp: -------------------------------------------------------------------------------- 1 | #include "CharredDesert.hpp" 2 | 3 | // The plugin-wide instance of the Plugin class 4 | Plugin *pluginInstance; 5 | 6 | void init(rack::Plugin *p) { 7 | pluginInstance = p; 8 | 9 | p->addModel(modelDTMF); 10 | p->addModel(modelNoise); 11 | p->addModel(modelCVSeq); 12 | p->addModel(modelNot); 13 | p->addModel(modelPan); 14 | p->addModel(modelShift); 15 | p->addModel(modelOscar2); 16 | p->addModel(modelEq); 17 | p->addModel(modelCarbon); 18 | p->addModel(modelMixer); 19 | p->addModel(modelMixerCV); 20 | p->addModel(modelCV); 21 | p->addModel(modelM); 22 | p->addModel(modelTine); 23 | p->addModel(modelK); 24 | p->addModel(modelX); 25 | p->addModel(modelTsunami); 26 | } 27 | -------------------------------------------------------------------------------- /src/CharredDesert.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | extern Plugin *pluginInstance; 6 | 7 | struct Payload { 8 | float values[8]; 9 | }; 10 | 11 | 12 | //////////////////// 13 | // module widgets 14 | //////////////////// 15 | 16 | extern Model *modelDTMF; 17 | extern Model *modelNoise; 18 | extern Model *modelNot; 19 | extern Model *modelPan; 20 | extern Model *modelShift; 21 | extern Model *modelCVSeq; 22 | extern Model *modelOscar2; 23 | extern Model *modelEq; 24 | extern Model *modelCarbon; 25 | extern Model *modelMixer; 26 | extern Model *modelMixerCV; 27 | extern Model *modelCV; 28 | extern Model *modelM; 29 | extern Model *modelTine; 30 | extern Model *modelK; 31 | extern Model *modelX; 32 | extern Model *modelTsunami; 33 | -------------------------------------------------------------------------------- /src/LFO.cpp: -------------------------------------------------------------------------------- 1 | #include "LFO.hpp" 2 | 3 | #include 4 | #include 5 | 6 | LowFrequencyOscillator::LowFrequencyOscillator() { 7 | phase = 0.0; 8 | pw = 0.5; 9 | freq = 1.0; 10 | offset = false; 11 | invert = false; 12 | shift = 0.0; 13 | shiftVal = 0.0; 14 | random = 0.0f; 15 | skip = false; 16 | cv = new SynthDevKit::CV(1.7f); 17 | srand(time(NULL)); 18 | } 19 | 20 | void LowFrequencyOscillator::setPitch(float pitch) { 21 | pitch = fminf(pitch, 8.0); 22 | freq = powf(2.0, pitch); 23 | } 24 | 25 | void LowFrequencyOscillator::setFrequency(float frequency) { 26 | freq = frequency; 27 | } 28 | 29 | void LowFrequencyOscillator::setPulseWidth(float pw_) { 30 | const float pwMin = 0.01; 31 | pw = clamp(pw_, pwMin, 1.0f - pwMin); 32 | } 33 | 34 | void LowFrequencyOscillator::setShift(float shift_) { 35 | shift = clamp(shift_, 0.0f, freq); 36 | } 37 | 38 | void LowFrequencyOscillator::setRandom(float random_) { 39 | random = clamp(random_, 0.0f, 10.0f); 40 | } 41 | 42 | void LowFrequencyOscillator::setInvert(bool invert_) { 43 | invert = invert_; 44 | } 45 | 46 | void LowFrequencyOscillator::setReset(float reset) { 47 | cv->update(reset); 48 | if (cv->newTrigger()) { 49 | phase = 0.0; 50 | shiftVal = 0.0; 51 | } 52 | } 53 | 54 | void LowFrequencyOscillator::hardReset() { 55 | phase = 0.0; 56 | shiftVal = 0.0; 57 | } 58 | 59 | void LowFrequencyOscillator::step(float dt) { 60 | if (shiftVal <= shift) { 61 | shiftVal++; 62 | return; 63 | } 64 | 65 | float deltaPhase = fminf(freq * dt, 0.5); 66 | phase += deltaPhase; 67 | if (phase >= 1.0) { 68 | phase -= 1.0; 69 | if (random && random >= (rand() % 50)) { 70 | skip = true; 71 | } else { 72 | skip = false; 73 | } 74 | } 75 | } 76 | 77 | float LowFrequencyOscillator::sin() { 78 | if (skip) { 79 | return 0.0f; 80 | } 81 | 82 | if (offset) { 83 | return 1.0 - cosf(2 * M_PI * phase) * (invert ? -1.0 : 1.0); 84 | } else { 85 | return sinf(2 * M_PI * phase) * (invert ? -1.0 : 1.0); 86 | } 87 | } 88 | 89 | float LowFrequencyOscillator::tri(float x) { 90 | return 4.0 * fabsf(x - roundf(x)); 91 | } 92 | 93 | float LowFrequencyOscillator::tri() { 94 | if (skip) { 95 | return 0.0f; 96 | } 97 | 98 | if (offset) { 99 | return tri(invert ? phase - 0.5 : phase); 100 | } else { 101 | return -1.0 + tri(invert ? phase - 0.25 : phase - 0.75); 102 | } 103 | } 104 | 105 | float LowFrequencyOscillator::saw(float x) { 106 | return 2.0 * (x - roundf(x)); 107 | } 108 | 109 | float LowFrequencyOscillator::saw() { 110 | if (skip) { 111 | return 0.0f; 112 | } 113 | 114 | if (offset) { 115 | return invert ? 2.0 * (1.0 - phase) : 2.0 * phase; 116 | } else { 117 | return saw(phase) * (invert ? -1.0 : 1.0); 118 | } 119 | } 120 | 121 | float LowFrequencyOscillator::sqr() { 122 | if (skip) { 123 | return 0.0f; 124 | } 125 | 126 | float sqr = (phase < pw) ^ invert ? 1.0 : -1.0; 127 | return offset ? sqr + 1.0 : sqr; 128 | } 129 | 130 | float LowFrequencyOscillator::progress() { 131 | return phase; 132 | } 133 | -------------------------------------------------------------------------------- /src/LFO.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../deps/SynthDevKit/src/CV.hpp" 4 | #include 5 | 6 | #define clamp(val, min, max) (val < min ? min : (val > max ? max : val)) 7 | 8 | class LowFrequencyOscillator { 9 | private: 10 | float phase; 11 | float pw; 12 | float freq; 13 | float shift; 14 | float shiftVal; 15 | float random; 16 | bool skip; 17 | bool offset; 18 | bool invert; 19 | SynthDevKit::CV *cv; 20 | 21 | public: 22 | LowFrequencyOscillator(); 23 | void setPitch(float); 24 | void setFrequency(float); 25 | void setPulseWidth(float); 26 | void setShift(float); 27 | void setRandom(float); 28 | void setInvert(bool); 29 | void setReset(float); 30 | void hardReset(); 31 | void step(float); 32 | float sin(); 33 | float tri(float); 34 | float tri(); 35 | float saw(float); 36 | float saw(); 37 | float sqr(); 38 | float progress(); 39 | }; 40 | -------------------------------------------------------------------------------- /src/controller/Biquad.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Biquad.cpp 3 | // 4 | // Created by Nigel Redmon on 11/24/12 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2012 Nigel Redmon 7 | // 8 | // For a complete explanation of the Biquad code: 9 | // http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 10 | // 11 | // License: 12 | // 13 | // This source code is provided as is, without warranty. 14 | // You may copy and distribute verbatim copies of this document. 15 | // You may modify and use this source code to create binary code 16 | // for your own purposes, free or commercial. 17 | // 18 | 19 | #include "Biquad.h" 20 | #include 21 | 22 | Biquad::Biquad() { 23 | type = bq_type_lowpass; 24 | a0 = 1.0; 25 | a1 = a2 = b1 = b2 = 0.0; 26 | Fc = 0.50; 27 | Q = 0.707; 28 | peakGain = 0.0; 29 | z1 = z2 = 0.0; 30 | } 31 | 32 | Biquad::Biquad(int type, double Fc, double Q, double peakGainDB) { 33 | setBiquad(type, Fc, Q, peakGainDB); 34 | z1 = z2 = 0.0; 35 | } 36 | 37 | Biquad::~Biquad() { 38 | } 39 | 40 | void Biquad::setType(int type) { 41 | this->type = type; 42 | } 43 | 44 | void Biquad::setQ(double Q) { 45 | this->Q = Q; 46 | } 47 | 48 | void Biquad::setFc(double Fc) { 49 | this->Fc = Fc; 50 | } 51 | 52 | void Biquad::setPeakGain(double peakGainDB) { 53 | this->peakGain = peakGainDB; 54 | } 55 | 56 | void Biquad::setBiquad(int type, double Fc, double Q, double peakGainDB) { 57 | this->type = type; 58 | this->Q = Q; 59 | this->Fc = Fc; 60 | setPeakGain(peakGainDB); 61 | } 62 | 63 | void Biquad::calcBiquad(void) { 64 | double norm; 65 | double V = pow(10, fabs(peakGain) / 20.0); 66 | double K = tan(M_PI * Fc); 67 | switch (this->type) { 68 | case bq_type_lowpass: 69 | norm = 1 / (1 + K / Q + K * K); 70 | a0 = K * K * norm; 71 | a1 = 2 * a0; 72 | a2 = a0; 73 | b1 = 2 * (K * K - 1) * norm; 74 | b2 = (1 - K / Q + K * K) * norm; 75 | break; 76 | 77 | case bq_type_highpass: 78 | norm = 1 / (1 + K / Q + K * K); 79 | a0 = 1 * norm; 80 | a1 = -2 * a0; 81 | a2 = a0; 82 | b1 = 2 * (K * K - 1) * norm; 83 | b2 = (1 - K / Q + K * K) * norm; 84 | break; 85 | 86 | case bq_type_bandpass: 87 | norm = 1 / (1 + K / Q + K * K); 88 | a0 = K / Q * norm; 89 | a1 = 0; 90 | a2 = -a0; 91 | b1 = 2 * (K * K - 1) * norm; 92 | b2 = (1 - K / Q + K * K) * norm; 93 | break; 94 | 95 | case bq_type_notch: 96 | norm = 1 / (1 + K / Q + K * K); 97 | a0 = (1 + K * K) * norm; 98 | a1 = 2 * (K * K - 1) * norm; 99 | a2 = a0; 100 | b1 = a1; 101 | b2 = (1 - K / Q + K * K) * norm; 102 | break; 103 | 104 | case bq_type_peak: 105 | if (peakGain >= 0) { // boost 106 | norm = 1 / (1 + 1 / Q * K + K * K); 107 | a0 = (1 + V / Q * K + K * K) * norm; 108 | a1 = 2 * (K * K - 1) * norm; 109 | a2 = (1 - V / Q * K + K * K) * norm; 110 | b1 = a1; 111 | b2 = (1 - 1 / Q * K + K * K) * norm; 112 | } else { // cut 113 | norm = 1 / (1 + V / Q * K + K * K); 114 | a0 = (1 + 1 / Q * K + K * K) * norm; 115 | a1 = 2 * (K * K - 1) * norm; 116 | a2 = (1 - 1 / Q * K + K * K) * norm; 117 | b1 = a1; 118 | b2 = (1 - V / Q * K + K * K) * norm; 119 | } 120 | break; 121 | case bq_type_lowshelf: 122 | if (peakGain >= 0) { // boost 123 | norm = 1 / (1 + sqrt(2) * K + K * K); 124 | a0 = (1 + sqrt(2 * V) * K + V * K * K) * norm; 125 | a1 = 2 * (V * K * K - 1) * norm; 126 | a2 = (1 - sqrt(2 * V) * K + V * K * K) * norm; 127 | b1 = 2 * (K * K - 1) * norm; 128 | b2 = (1 - sqrt(2) * K + K * K) * norm; 129 | } else { // cut 130 | norm = 1 / (1 + sqrt(2 * V) * K + V * K * K); 131 | a0 = (1 + sqrt(2) * K + K * K) * norm; 132 | a1 = 2 * (K * K - 1) * norm; 133 | a2 = (1 - sqrt(2) * K + K * K) * norm; 134 | b1 = 2 * (V * K * K - 1) * norm; 135 | b2 = (1 - sqrt(2 * V) * K + V * K * K) * norm; 136 | } 137 | break; 138 | case bq_type_highshelf: 139 | if (peakGain >= 0) { // boost 140 | norm = 1 / (1 + sqrt(2) * K + K * K); 141 | a0 = (V + sqrt(2 * V) * K + K * K) * norm; 142 | a1 = 2 * (K * K - V) * norm; 143 | a2 = (V - sqrt(2 * V) * K + K * K) * norm; 144 | b1 = 2 * (K * K - 1) * norm; 145 | b2 = (1 - sqrt(2) * K + K * K) * norm; 146 | } else { // cut 147 | norm = 1 / (V + sqrt(2 * V) * K + K * K); 148 | a0 = (1 + sqrt(2) * K + K * K) * norm; 149 | a1 = 2 * (K * K - 1) * norm; 150 | a2 = (1 - sqrt(2) * K + K * K) * norm; 151 | b1 = 2 * (K * K - V) * norm; 152 | b2 = (V - sqrt(2 * V) * K + K * K) * norm; 153 | } 154 | break; 155 | } 156 | 157 | return; 158 | } 159 | -------------------------------------------------------------------------------- /src/controller/Biquad.h: -------------------------------------------------------------------------------- 1 | // 2 | // Biquad.h 3 | // 4 | // Created by Nigel Redmon on 11/24/12 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2012 Nigel Redmon 7 | // 8 | // For a complete explanation of the Biquad code: 9 | // http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ 10 | // 11 | // License: 12 | // 13 | // This source code is provided as is, without warranty. 14 | // You may copy and distribute verbatim copies of this document. 15 | // You may modify and use this source code to create binary code 16 | // for your own purposes, free or commercial. 17 | // 18 | 19 | // NOTE: Some local modifications were made that changes some behavior 20 | // from the above article - most notably: calcBiquad() was moved from 21 | // protected to public, and must be called manually on any changes before 22 | // process() is called. 23 | 24 | #ifndef Biquad_h 25 | #define Biquad_h 26 | 27 | enum { 28 | bq_type_lowpass = 0, 29 | bq_type_highpass, 30 | bq_type_bandpass, 31 | bq_type_notch, 32 | bq_type_peak, 33 | bq_type_lowshelf, 34 | bq_type_highshelf 35 | }; 36 | 37 | class Biquad { 38 | public: 39 | Biquad(); 40 | Biquad(int type, double Fc, double Q, double peakGainDB); 41 | ~Biquad(); 42 | void setType(int type); 43 | void setQ(double Q); 44 | void setFc(double Fc); 45 | void setPeakGain(double peakGainDB); 46 | void setBiquad(int type, double Fc, double Q, double peakGain); 47 | float process(float in); 48 | void calcBiquad(void); 49 | 50 | protected: 51 | int type; 52 | double a0, a1, a2, b1, b2; 53 | double Fc, Q, peakGain; 54 | double z1, z2; 55 | }; 56 | 57 | inline float Biquad::process(float in) { 58 | double out = in * a0 + z1; 59 | z1 = in * a1 + z2 - b1 * out; 60 | z2 = in * a2 - b2 * out; 61 | return out; 62 | } 63 | 64 | #endif // Biquad_h 65 | -------------------------------------------------------------------------------- /src/controller/CV.cpp: -------------------------------------------------------------------------------- 1 | #include "CV.hpp" 2 | #include 3 | 4 | CVModule::CVModule() { 5 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 6 | for (int i = 0; i < CV_COUNT; i++) { 7 | on[i] = false; 8 | cv[i] = new SynthDevKit::CV(0.5f); 9 | configParam(CVModule::SWITCH + i, 0.0f, 1.0f, 0.0f); 10 | configParam(CVModule::KNOB + i, 0.0f, 10.0f, 0.0f); 11 | } 12 | } 13 | 14 | void CVModule::process(const ProcessArgs &args) { 15 | for (int i = 0; i < CV_COUNT; i++) { 16 | cv[i]->update(params[SWITCH + i].getValue()); 17 | if (cv[i]->newTrigger()) { 18 | on[i] = !on[i]; 19 | } 20 | 21 | if (on[i]) { 22 | lights[OUT_LIGHT + i].value = 1.0f; 23 | outputs[OUT + i].setVoltage(params[KNOB + i].getValue()); 24 | } else { 25 | lights[OUT_LIGHT + i].value = 0.0f; 26 | outputs[OUT + i].setVoltage(0.0f); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/controller/CV.hpp: -------------------------------------------------------------------------------- 1 | #include "../CharredDesert.hpp" 2 | 3 | #include "../../deps/SynthDevKit/src/CV.hpp" 4 | 5 | #define CV_COUNT 2 6 | 7 | struct CVModule : Module { 8 | enum ParamIds { KNOB, SWITCH = CV_COUNT, NUM_PARAMS = CV_COUNT * 2 }; 9 | enum InputIds { NUM_INPUTS }; 10 | enum OutputIds { OUT, NUM_OUTPUTS = CV_COUNT }; 11 | enum LightIds { OUT_LIGHT, NUM_LIGHTS = CV_COUNT }; 12 | 13 | CVModule(); 14 | 15 | void process(const ProcessArgs &args) override; 16 | 17 | bool on[CV_COUNT]; 18 | 19 | SynthDevKit::CV *cv[CV_COUNT]; 20 | 21 | json_t *dataToJson() override { 22 | json_t *rootJ = json_object(); 23 | 24 | json_t *arr = json_array(); 25 | 26 | for (int i = 0; i < CV_COUNT; i++) { 27 | json_array_append(arr, json_boolean(on[i])); 28 | } 29 | 30 | json_object_set_new(rootJ, "switches", arr); 31 | 32 | return rootJ; 33 | } 34 | 35 | void dataFromJson(json_t *rootJ) override { 36 | json_t *switchJ = json_object_get(rootJ, "switches"); 37 | 38 | if (switchJ && json_is_array(switchJ)) { 39 | for (int i = 0; i < CV_COUNT; i++) { 40 | json_t *v = json_array_get(switchJ, i); 41 | if (v) { 42 | on[i] = json_boolean_value(v); 43 | } 44 | } 45 | } 46 | } 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /src/controller/CVSeq.cpp: -------------------------------------------------------------------------------- 1 | #include "CVSeq.hpp" 2 | 3 | #define ADD_CV(a, b) clamp(a.value + b.value, 0.0f, 10.0f) 4 | 5 | CVSeqModule::CVSeqModule() { 6 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 7 | cv = new SynthDevKit::CV(1.7f); 8 | currentStep = 0; 9 | configParam(CVSeqModule::KNOB1, 0.0f, 10.0f, 0.0f); 10 | configParam(CVSeqModule::KNOB2, 0.0f, 10.0f, 0.0f); 11 | configParam(CVSeqModule::KNOB3, 0.0f, 10.0f, 0.0f); 12 | configParam(CVSeqModule::KNOB4, 0.0f, 10.0f, 0.0f); 13 | } 14 | 15 | void CVSeqModule::process(const ProcessArgs &args) { 16 | float cv_in = inputs[CV_INPUT].getVoltage(); 17 | float current = 0.0f; 18 | 19 | cv->update(cv_in); 20 | 21 | if (cv->newTrigger()) { 22 | current = ADD_CV(inputs[currentStep], params[currentStep]); 23 | // current = params[currentStep].getValue(); 24 | outputs[CV_OUTPUT].setVoltage(current); 25 | for (int i = 0; i < 4; i++) { 26 | if (i == currentStep) { 27 | lights[i].value = 1.0; 28 | } else { 29 | lights[i].value = 0.0; 30 | } 31 | } 32 | currentStep++; 33 | 34 | if (currentStep == 4) { 35 | currentStep = 0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/controller/CVSeq.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../../deps/SynthDevKit/src/CV.hpp" 4 | #include "../CharredDesert.hpp" 5 | 6 | struct CVSeqModule : Module { 7 | enum ParamIds { KNOB1, KNOB2, KNOB3, KNOB4, NUM_PARAMS }; 8 | enum InputIds { 9 | KNOB1_INPUT, 10 | KNOB2_INPUT, 11 | KNOB3_INPUT, 12 | KNOB4_INPUT, 13 | CV_INPUT, 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { CV_OUTPUT, NUM_OUTPUTS }; 17 | enum LightIds { LED1, LED2, LED3, LED4, NUM_LIGHTS }; 18 | 19 | CVSeqModule(); 20 | void process(const ProcessArgs &args) override; 21 | 22 | SynthDevKit::CV *cv; 23 | uint8_t currentStep; 24 | }; 25 | -------------------------------------------------------------------------------- /src/controller/Carbon.cpp: -------------------------------------------------------------------------------- 1 | #include "Carbon.hpp" 2 | 3 | #include 4 | 5 | CarbonModule::CarbonModule() { 6 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 7 | configParam(CarbonModule::FREQ_PARAM, 20.0f, 6000.0f, (6000 - 20) / 2); 8 | configParam(CarbonModule::REZ_PARAM, 0.0f, 4.0f, 2.0f); 9 | frequency = 0.0f; 10 | filter.clear(); 11 | } 12 | 13 | void CarbonModule::process(const ProcessArgs &args) { 14 | // update the display no matter what 15 | frequency = clamp((inputs[FREQ_INPUT].isConnected() ? inputs[FREQ_INPUT].getVoltage() * 1000 : 0) + params[FREQ_PARAM].getValue(), 20.0f, 6000.0f); 16 | 17 | if (inputs[AUDIO_INPUT].isConnected() && outputs[AUDIO_OUTPUT].isConnected()) { 18 | float audio_in = inputs[AUDIO_INPUT].getVoltage() / 5.0f; 19 | float res = clamp((inputs[REZ_INPUT].isConnected() ? inputs[REZ_INPUT].getVoltage() / 10 : 0) + params[REZ_PARAM].getValue(), 0.1f, 4.0f); 20 | 21 | filter.setSamplerate(args.sampleRate); 22 | filter.setCoefficients(frequency, res); 23 | 24 | float out = 0.0f; 25 | 26 | filter.process(&audio_in, &out, 1); 27 | 28 | // filter can sometimes get unstable at high frequencies, if it does, reset 29 | if (std::isnan(out)) { 30 | out = 0.0f; 31 | filter.clear(); 32 | } 33 | outputs[AUDIO_OUTPUT].setVoltage(5.0f * out); 34 | } else { 35 | outputs[AUDIO_OUTPUT].setVoltage(0.0f); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/controller/Carbon.hpp: -------------------------------------------------------------------------------- 1 | #include "../CharredDesert.hpp" 2 | #include "filters.hpp" 3 | 4 | struct CarbonModule : Module { 5 | enum ParamIds { FREQ_PARAM, REZ_PARAM, NUM_PARAMS }; 6 | enum InputIds { FREQ_INPUT, REZ_INPUT, AUDIO_INPUT, NUM_INPUTS }; 7 | enum OutputIds { AUDIO_OUTPUT, NUM_OUTPUTS }; 8 | enum LightIds { NUM_LIGHTS }; 9 | 10 | CarbonModule(); 11 | void process(const ProcessArgs &args) override; 12 | MoogFilter filter; 13 | float frequency = 0.0f; 14 | }; 15 | -------------------------------------------------------------------------------- /src/controller/DTMF.cpp: -------------------------------------------------------------------------------- 1 | #include "DTMF.hpp" 2 | 3 | DTMFModule::DTMFModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | cv = new SynthDevKit::CV(1.7f); 6 | dtmf = new SynthDevKit::DTMF(44100); 7 | } 8 | 9 | char DTMFModule::getTone(float current) { 10 | for (int i = 0; i < 16; i++) { 11 | if ((notes[i] - 0.02) <= current && (notes[i] + 0.02) >= current) { 12 | return tones[i]; 13 | } 14 | } 15 | 16 | return ' '; 17 | } 18 | 19 | void DTMFModule::process(const ProcessArgs &args) { 20 | float cv_in = inputs[CV_INPUT].getVoltage(); 21 | float voct_in = inputs[VOCT_INPUT].getVoltage(); 22 | 23 | cv->update(cv_in); 24 | 25 | if (cv->newTrigger()) { 26 | dtmf->reset(); 27 | } 28 | 29 | if (cv->isHigh()) { 30 | char tone = getTone(voct_in); 31 | dtmf->setTone(tone); 32 | 33 | outputs[AUDIO_OUTPUT].setVoltage(dtmf->stepValue()); 34 | 35 | if (outputs[AUDIO_OUTPUT].value == 0) { 36 | lights[ON_LED].value = 0; 37 | } else { 38 | lights[ON_LED].value = 1; 39 | } 40 | } else { 41 | outputs[AUDIO_OUTPUT].setVoltage(0); 42 | lights[ON_LED].value = 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/controller/DTMF.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../../deps/SynthDevKit/src/CV.hpp" 4 | #include "../../deps/SynthDevKit/src/DTMF.hpp" 5 | #include "../CharredDesert.hpp" 6 | 7 | struct DTMFModule : Module { 8 | enum ParamIds { NUM_PARAMS }; 9 | enum InputIds { VOCT_INPUT, CV_INPUT, NUM_INPUTS }; 10 | enum OutputIds { AUDIO_OUTPUT, NUM_OUTPUTS }; 11 | enum LightIds { ON_LED, NUM_LIGHTS }; 12 | 13 | DTMFModule(); 14 | 15 | void process(const ProcessArgs &args) override; 16 | 17 | char getTone(float); 18 | 19 | SynthDevKit::CV *cv; 20 | SynthDevKit::DTMF *dtmf; 21 | float notes[16] = {0, 0.08, 0.17, 0.25, 0.33, 0.42, 0.5, 0.58, 22 | 0.67, 0.75, 0.83, 0.92, 1.0, 1.08, 1.17, 1.25}; 23 | char tones[16] = {'1', '2', '3', 'A', '4', '5', '6', 'B', 24 | '7', '8', '9', 'C', '*', '0', '#', 'D'}; 25 | }; 26 | -------------------------------------------------------------------------------- /src/controller/Eq.cpp: -------------------------------------------------------------------------------- 1 | #include "Eq.hpp" 2 | 3 | EqModule::EqModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | configParam(EqModule::FREQ_PARAM, 30.0f, 14000.0f, 7000.0f); 6 | configParam(EqModule::TYPE_PARAM, 0.0f, 6.0f, 0.0f); 7 | configParam(EqModule::Q_PARAM, 0.1f, 6.0f, 0.1f); 8 | sampleRate = APP->engine->getSampleRate(); 9 | filter = new Biquad(bq_type_lowpass, frequency / sampleRate, q, 6); 10 | filter->calcBiquad(); 11 | } 12 | 13 | float EqModule::paramValue (uint16_t param, uint16_t input, float low, float high) { 14 | float current = params[param].getValue(); 15 | 16 | if (inputs[input].isConnected()) { 17 | // high - low, divided by one tenth input voltage, plus the current value 18 | current += ((inputs[input].getVoltage() / 10) * (high - low)); 19 | } 20 | 21 | return clamp(current, low, high); 22 | } 23 | 24 | void EqModule::process(const ProcessArgs &args) { 25 | float audio_in = inputs[AUDIO_INPUT].getVoltage(); 26 | float freq_param = paramValue(FREQ_PARAM, FREQ_CV_INPUT, 30, 14000); 27 | uint8_t filter_type = (uint8_t)params[TYPE_PARAM].getValue(); 28 | float q_param = paramValue(Q_PARAM, Q_CV_INPUT, 0.1f, 6.0f); 29 | 30 | if (args.sampleRate != sampleRate || filter_type != filterType || 31 | freq_param != frequency || q_param != q) { 32 | // reset the tracking variables 33 | frequency = freq_param; 34 | q = q_param; 35 | filterType = filter_type; 36 | sampleRate = args.sampleRate; 37 | 38 | filter->setType(filterType); 39 | filter->setQ((double)q); 40 | filter->setFc(frequency / sampleRate); 41 | filter->calcBiquad(); 42 | } 43 | 44 | if (outputs[AUDIO_OUTPUT].isConnected()) { 45 | outputs[AUDIO_OUTPUT].setVoltage(5.0f * filter->process(audio_in / 5.0f)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/controller/Eq.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | #include "Biquad.h" 5 | 6 | struct EqModule : Module { 7 | enum ParamIds { FREQ_PARAM, TYPE_PARAM, Q_PARAM, NUM_PARAMS }; 8 | enum InputIds { AUDIO_INPUT, FREQ_CV_INPUT, Q_CV_INPUT, NUM_INPUTS }; 9 | enum OutputIds { AUDIO_OUTPUT, NUM_OUTPUTS }; 10 | enum LightIds { NUM_LIGHTS }; 11 | 12 | EqModule(); 13 | void process(const ProcessArgs &args) override; 14 | float paramValue (uint16_t, uint16_t, float, float); 15 | float frequency = 7000.0f; 16 | float sampleRate; 17 | uint8_t filterType = 0; 18 | float q = 0.1f; 19 | Biquad *filter; 20 | }; 21 | -------------------------------------------------------------------------------- /src/controller/K.cpp: -------------------------------------------------------------------------------- 1 | #include "K.hpp" 2 | 3 | #define VALUE(a) (a.isConnected() ? a.getVoltage() : 0.0f) 4 | 5 | KModule::KModule() { 6 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 7 | configParam(RATIO, 1.0f, 10.0f, 5.0f); 8 | configParam(THRESHOLD, 1.0f, 30.0f, 10.0f); 9 | configParam(ATTACK, 1.0, 100.0, 10.0); 10 | configParam(RELEASE, 1.0, 100.0, 10.0); 11 | } 12 | 13 | void KModule::process(const ProcessArgs &args) { 14 | float r = params[RATIO].getValue(); 15 | float t = params[THRESHOLD].getValue(); 16 | float at = params[ATTACK].getValue(); 17 | float rt = params[RELEASE].getValue(); 18 | 19 | if (sampleRate != args.sampleRate || ratio != r || threshold != t || attackTime != at || releaseTime != rt) { 20 | sampleRate = args.sampleRate; 21 | ratio = r; 22 | threshold = t; 23 | attackTime = at; 24 | releaseTime = rt; 25 | 26 | compressor.setCoefficients(attackTime, releaseTime, threshold, ratio, sampleRate); 27 | } 28 | 29 | float in = inputs[IN].getVoltage(); 30 | float out = compressor.process(in); 31 | lights[ACTIVE].value = (in == out) ? 0.0f : 1.0f; 32 | 33 | outputs[OUT].setVoltage(out); 34 | } 35 | -------------------------------------------------------------------------------- /src/controller/K.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | #include "../model/Compressor.hpp" 6 | 7 | struct KModule : Module { 8 | enum ParamIds { THRESHOLD, RATIO, ATTACK, RELEASE, NUM_PARAMS }; 9 | enum InputIds { IN, NUM_INPUTS }; 10 | enum OutputIds { OUT, NUM_OUTPUTS }; 11 | enum LightIds { ACTIVE, NUM_LIGHTS }; 12 | 13 | KModule(); 14 | 15 | void process(const ProcessArgs &args) override; 16 | Compressor compressor; 17 | float sampleRate = 0; 18 | float ratio = 0; 19 | float attackTime = 0; 20 | float releaseTime = 0; 21 | float threshold = 0; 22 | }; 23 | -------------------------------------------------------------------------------- /src/controller/M.cpp: -------------------------------------------------------------------------------- 1 | #include "M.hpp" 2 | 3 | #define VALUE(a) (a.isConnected() ? a.getVoltage() : 0.0f) 4 | 5 | MModule::MModule() { 6 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 7 | for (int i = 0; i < M_COUNT; i++) { 8 | configParam(MModule::KNOB + i, 0.0f, 1.0f, 0.5f); 9 | } 10 | } 11 | 12 | void MModule::process(const ProcessArgs &args) { 13 | for (int i = 0; i < M_COUNT; i++) { 14 | float mix = params[KNOB + i].getValue(); 15 | 16 | float out = (1 - mix) * VALUE(inputs[IN1 + i]) + mix * VALUE(inputs[IN2 + i]); 17 | 18 | outputs[OUT + i].setVoltage(out); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/controller/M.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | #include "../../deps/SynthDevKit/src/CV.hpp" 6 | 7 | #define M_COUNT 2 8 | 9 | struct MModule : Module { 10 | enum ParamIds { KNOB, NUM_PARAMS = M_COUNT }; 11 | enum InputIds { IN1, IN2 = M_COUNT, NUM_INPUTS = M_COUNT * 2 }; 12 | enum OutputIds { OUT, NUM_OUTPUTS = M_COUNT }; 13 | enum LightIds { NUM_LIGHTS }; 14 | 15 | MModule(); 16 | 17 | void process(const ProcessArgs &args) override; 18 | }; 19 | -------------------------------------------------------------------------------- /src/controller/Mixer.cpp: -------------------------------------------------------------------------------- 1 | #include "Mixer.hpp" 2 | 3 | MixerModule::MixerModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | for (int i = 0; i < MIXER_CHANNELS; i++) { 6 | channel_led_l[i] = 0.0f; 7 | channel_led_r[i] = 0.0f; 8 | mute[i] = false; 9 | solo[i] = false; 10 | 11 | mute_button[i] = new SynthDevKit::CV(0.5f); 12 | solo_button[i] = new SynthDevKit::CV(0.5f); 13 | 14 | configParam(MixerModule::VOLUME_SLIDER + i, 0.0f, 1.2f, 1.0f); 15 | configParam(MixerModule::PAN_PARAM + i, 0.0f, 1.0f, 0.5f); 16 | configParam(MixerModule::SOLO_PARAM + i, 0.0f, 1.0f, 0.0f); 17 | configParam(MixerModule::MUTE_PARAM + i, 0.0f, 1.0f, 0.0f); 18 | } 19 | 20 | configParam(MixerModule::VOLUME_L_MAIN, 0.0f, 1.2f, 1.0f); 21 | configParam(MixerModule::VOLUME_R_MAIN, 0.0f, 1.2f, 1.0f); 22 | configParam(MixerModule::MUTE_L_PARAM, 0.0f, 1.0f, 0.0f); 23 | configParam(MixerModule::MUTE_R_PARAM, 0.0f, 1.0f, 0.0f); 24 | 25 | master_led_l = 0.0f; 26 | master_led_r = 0.0f; 27 | master_mute_l = false; 28 | master_mute_r = false; 29 | 30 | mute_l = new SynthDevKit::CV(0.5f); 31 | mute_r = new SynthDevKit::CV(0.5f); 32 | } 33 | 34 | void MixerModule::process(const ProcessArgs &args) { 35 | bool has_solo = false; 36 | float output_l[MIXER_CHANNELS]; 37 | float output_r[MIXER_CHANNELS]; 38 | float master_l = 0.0f; 39 | float master_r = 0.0f; 40 | 41 | master_led_l = 0.0f; 42 | master_led_r = 0.0f; 43 | 44 | // check for solo buttons first and handle the button presses 45 | for (int i = 0; i < MIXER_CHANNELS; i++) { 46 | mute_button[i]->update(params[MUTE_PARAM + i].getValue()); 47 | solo_button[i]->update(params[SOLO_PARAM + i].getValue()); 48 | 49 | if (solo_button[i]->newTrigger()) { 50 | solo[i] = !solo[i]; 51 | } 52 | 53 | if (mute_button[i]->newTrigger()) { 54 | mute[i] = !mute[i]; 55 | } 56 | 57 | // if any are solo, there's a solo 58 | if (solo[i]) { 59 | has_solo = true; 60 | } 61 | } 62 | 63 | // iterate through the channels 64 | for (int i = 0; i < MIXER_CHANNELS; i++) { 65 | output_l[i] = 0.0f; 66 | output_r[i] = 0.0f; 67 | 68 | // if the input is not active, or the input is muted 69 | if (!inputs[INPUT + i].isConnected() || mute[i] || (has_solo && !solo[i])) { 70 | // set everything to 0 71 | output_l[i] = 0.0f; 72 | output_r[i] = 0.0f; 73 | } else { 74 | // do the math 75 | float input = inputs[INPUT + i].getVoltage(); 76 | 77 | // get the volume slider value 78 | float volume = params[VOLUME_SLIDER + i].getValue(); 79 | 80 | input *= volume; 81 | 82 | // figure out the left and right percentages 83 | float pan = params[PAN_PARAM + i].getValue(); 84 | 85 | output_l[i] = output_r[i] = input; 86 | 87 | // determine the left/right mixes 88 | if (pan < 0.5f) { 89 | output_r[i] = (2.0f * pan) * output_r[i]; 90 | } 91 | 92 | if (pan > 0.5f) { 93 | output_l[i] = (2.0f * (1.0f - pan)) * output_l[i]; 94 | } 95 | 96 | // add the output to master 97 | // check if there is a solo, and it's not this one 98 | if (has_solo) { 99 | if (solo[i]) { 100 | master_l += output_l[i]; 101 | master_r += output_r[i]; 102 | } 103 | } else { 104 | master_l += output_l[i]; 105 | master_r += output_r[i]; 106 | } 107 | } 108 | 109 | lights[SOLO_LIGHT + i].value = solo[i] ? 1.0f : 0.0f; 110 | lights[MUTE_LIGHT + i].value = mute[i] ? 1.0f : 0.0f; 111 | 112 | // the led's 113 | channel_led_l[i] = fabsf(output_l[i]); 114 | channel_led_r[i] = fabsf(output_r[i]); 115 | } 116 | 117 | // check if muted 118 | mute_l->update(params[MUTE_L_PARAM].getValue()); 119 | mute_r->update(params[MUTE_R_PARAM].getValue()); 120 | 121 | if (mute_l->newTrigger()) { 122 | master_mute_l = !master_mute_l; 123 | } 124 | 125 | if (mute_r->newTrigger()) { 126 | master_mute_r = !master_mute_r; 127 | } 128 | 129 | if (master_mute_l) { 130 | master_l = 0.0f; 131 | } else { 132 | // apply the left volume 133 | float volume = params[VOLUME_L_MAIN].getValue(); 134 | master_l = master_l * volume; 135 | } 136 | 137 | if (master_mute_r) { 138 | master_r = 0.0f; 139 | } else { 140 | // apply the left volume 141 | float volume = params[VOLUME_R_MAIN].getValue(); 142 | master_r = master_r * volume; 143 | } 144 | 145 | lights[MUTE_L_MAIN].value = master_mute_l ? 1.0f : 0.0f; 146 | lights[MUTE_R_MAIN].value = master_mute_r ? 1.0f : 0.0f; 147 | 148 | // apply master volume to the led's 149 | master_led_l = fabsf(master_l); 150 | master_led_r = fabsf(master_r); 151 | 152 | // and to the output 153 | outputs[MAIN_L_OUT].setVoltage(master_l); 154 | outputs[MAIN_R_OUT].setVoltage(master_r); 155 | } 156 | -------------------------------------------------------------------------------- /src/controller/Mixer.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | #include "../../deps/SynthDevKit/src/CV.hpp" 5 | 6 | #define MIXER_CHANNELS 8 7 | 8 | struct MixerModule : Module { 9 | enum ParamIds { 10 | VOLUME_SLIDER, 11 | PAN_PARAM = MIXER_CHANNELS, 12 | SOLO_PARAM = MIXER_CHANNELS * 2, 13 | MUTE_PARAM = MIXER_CHANNELS * 3, 14 | VOLUME_L_MAIN = MIXER_CHANNELS * 4, 15 | VOLUME_R_MAIN, 16 | MUTE_L_PARAM, 17 | MUTE_R_PARAM, 18 | NUM_PARAMS 19 | }; 20 | enum InputIds { 21 | INPUT, 22 | NUM_INPUTS = MIXER_CHANNELS 23 | }; 24 | enum OutputIds { 25 | MAIN_L_OUT, 26 | MAIN_R_OUT, 27 | NUM_OUTPUTS 28 | }; 29 | 30 | enum LightIds { 31 | SOLO_LIGHT, 32 | MUTE_LIGHT = MIXER_CHANNELS, 33 | MUTE_L_MAIN = MIXER_CHANNELS * 2, 34 | MUTE_R_MAIN, 35 | NUM_LIGHTS 36 | }; 37 | 38 | MixerModule(); 39 | void process(const ProcessArgs &args) override; 40 | 41 | float channel_led_l[MIXER_CHANNELS]; 42 | float channel_led_r[MIXER_CHANNELS]; 43 | 44 | float master_led_l = 0.0f; 45 | float master_led_r = 0.0f; 46 | 47 | bool mute[MIXER_CHANNELS]; 48 | bool master_mute_l; 49 | bool master_mute_r; 50 | 51 | bool solo[MIXER_CHANNELS]; 52 | 53 | SynthDevKit::CV *solo_button[MIXER_CHANNELS]; 54 | SynthDevKit::CV *mute_button[MIXER_CHANNELS]; 55 | SynthDevKit::CV *mute_l; 56 | SynthDevKit::CV *mute_r; 57 | 58 | json_t *dataToJson() override { 59 | json_t *rootJ = json_object(); 60 | 61 | json_t *m = json_array(); 62 | json_t *s = json_array(); 63 | 64 | for (int i = 0; i < MIXER_CHANNELS; i++) { 65 | json_array_append(m, json_boolean(mute[i])); 66 | json_array_append(s, json_boolean(solo[i])); 67 | } 68 | 69 | json_object_set_new(rootJ, "mute", m); 70 | json_object_set_new(rootJ, "solo", s); 71 | json_object_set_new(rootJ, "mute_l", json_boolean(master_mute_l)); 72 | json_object_set_new(rootJ, "mute_r", json_boolean(master_mute_r)); 73 | 74 | return rootJ; 75 | } 76 | 77 | void dataFromJson(json_t *rootJ) override { 78 | json_t *m = json_object_get(rootJ, "mute"); 79 | json_t *s = json_object_get(rootJ, "solo"); 80 | 81 | for (int i = 0; i < MIXER_CHANNELS; i++) { 82 | if (m && json_is_array(m)) { 83 | json_t *m1 = json_array_get(m, i); 84 | if (m1) { 85 | mute[i] = json_boolean_value(m1); 86 | } 87 | } 88 | 89 | if (s && json_is_array(s)) { 90 | json_t *s1 = json_array_get(s, i); 91 | if (s1) { 92 | solo[i] = json_boolean_value(s1); 93 | } 94 | } 95 | } 96 | 97 | json_t *v = json_object_get(rootJ, "mute_l"); 98 | if (v) { 99 | master_mute_l = json_boolean_value(v); 100 | } 101 | 102 | v = json_object_get(rootJ, "mute_r"); 103 | if (v) { 104 | master_mute_r = json_boolean_value(v); 105 | } 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /src/controller/MixerCV.cpp: -------------------------------------------------------------------------------- 1 | #include "MixerCV.hpp" 2 | 3 | MixerCVModule::MixerCVModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | for (int i = 0; i < MIXER_CHANNELS; i++) { 6 | channel_led_l[i] = 0.0f; 7 | channel_led_r[i] = 0.0f; 8 | mute[i] = false; 9 | solo[i] = false; 10 | 11 | mute_button[i] = new SynthDevKit::CV(0.5f); 12 | solo_button[i] = new SynthDevKit::CV(0.5f); 13 | channel_solo[i] = new SynthDevKit::CV(1.7f); 14 | channel_mute[i] = new SynthDevKit::CV(1.7f); 15 | 16 | configParam(MixerCVModule::VOLUME_SLIDER + i, 0.0f, 1.2f, 1.0f); 17 | configParam(MixerCVModule::PAN_PARAM + i, 0.0f, 1.0f, 0.5f); 18 | configParam(MixerCVModule::SOLO_PARAM + i, 0.0f, 1.0f, 0.0f); 19 | configParam(MixerCVModule::MUTE_PARAM + i, 0.0f, 1.0f, 0.0f); 20 | configParam(MixerCVModule::MIX_PARAM + i, 0.0f, 1.0f, 0.0f); 21 | } 22 | 23 | configParam(MixerCVModule::VOLUME_L_MAIN, 0.0f, 1.2f, 1.0f); 24 | configParam(MixerCVModule::VOLUME_R_MAIN, 0.0f, 1.2f, 1.0f); 25 | configParam(MixerCVModule::MUTE_L_PARAM, 0.0f, 1.0f, 0.0f); 26 | configParam(MixerCVModule::MUTE_R_PARAM, 0.0f, 1.0f, 0.0f); 27 | configParam(MixerCVModule::MAIN_L_MIX, 0.0f, 1.0f, 0.0f); 28 | configParam(MixerCVModule::MAIN_R_MIX, 0.0f, 1.0f, 0.0f); 29 | 30 | master_led_l = 0.0f; 31 | master_led_r = 0.0f; 32 | master_mute_l = false; 33 | master_mute_r = false; 34 | 35 | mute_l = new SynthDevKit::CV(1.7f); 36 | mute_r = new SynthDevKit::CV(1.7f); 37 | mute_l_param = new SynthDevKit::CV(0.5f); 38 | mute_r_param = new SynthDevKit::CV(0.5f); 39 | } 40 | 41 | void MixerCVModule::process(const ProcessArgs &args) { 42 | bool has_solo = false; 43 | float output_l[MIXER_CHANNELS]; 44 | float output_r[MIXER_CHANNELS]; 45 | float master_l = 0.0f; 46 | float master_r = 0.0f; 47 | 48 | master_led_l = 0.0f; 49 | master_led_r = 0.0f; 50 | 51 | // check for solo buttons first and handle the button presses and cv 52 | for (int i = 0; i < MIXER_CHANNELS; i++) { 53 | channel_solo[i]->update(inputs[SOLO_CV + i].getVoltage()); 54 | channel_mute[i]->update(inputs[MUTE_CV + i].getVoltage()); 55 | 56 | mute_button[i]->update(params[MUTE_PARAM + i].getValue()); 57 | solo_button[i]->update(params[SOLO_PARAM + i].getValue()); 58 | 59 | // buttons first 60 | if (solo_button[i]->newTrigger()) { 61 | solo[i] = !solo[i]; 62 | } 63 | 64 | if (mute_button[i]->newTrigger()) { 65 | mute[i] = !mute[i]; 66 | } 67 | 68 | // then cv 69 | if (channel_solo[i]->newTrigger()) { 70 | solo[i] = !solo[i]; 71 | } 72 | 73 | if (channel_mute[i]->newTrigger()) { 74 | mute[i] = !mute[i]; 75 | } 76 | 77 | // if any are solo, there's a solo 78 | if (solo[i]) { 79 | has_solo = true; 80 | } 81 | 82 | lights[SOLO_LIGHT + i].value = solo[i] ? 1.0f : 0.0f; 83 | lights[MUTE_LIGHT + i].value = mute[i] ? 1.0f : 0.0f; 84 | } 85 | 86 | // iterate through the channels 87 | for (int i = 0; i < MIXER_CHANNELS; i++) { 88 | output_l[i] = 0.0f; 89 | output_r[i] = 0.0f; 90 | 91 | // if the input is not active, or the input is muted 92 | if (mute[i] || (has_solo && !solo[i])) { 93 | // set everything to 0 94 | output_l[i] = 0.0f; 95 | output_r[i] = 0.0f; 96 | } else { 97 | // get the input 98 | float input = (inputs[INPUT + i].isConnected() ? inputs[INPUT + i].getVoltage() : 0.0f); 99 | 100 | // send the input if there's something listening 101 | if (outputs[SEND + i].isConnected()) { 102 | outputs[SEND + i].setVoltage(input); 103 | } 104 | 105 | // if there in recv, process it 106 | if (inputs[RECV + i].isConnected()) { 107 | // get the mix 108 | float mix = clamp((inputs[MIX_CV + i].isConnected() ? inputs[MIX_CV + i].getVoltage() / 10 : 0.0f) + params[MIX_PARAM + i].getValue(), 0.0f, 1.0f); 109 | 110 | // calculate the new "input" 111 | input = ((1 - mix) * input) + (mix * inputs[RECV + i].getVoltage()); 112 | } 113 | 114 | // get the volume slider value 115 | float volume = params[VOLUME_SLIDER + i].getValue(); 116 | 117 | // add any cv value 118 | volume = clamp((inputs[VOLUME_CV + i].isConnected() ? inputs[VOLUME_CV + i].getVoltage() / 10 : 0.0f) + volume, 0.0f, 1.2f); 119 | 120 | input *= volume; 121 | 122 | // figure out the left and right percentages 123 | float pan = params[PAN_PARAM + i].getValue(); 124 | 125 | // add any cv value 126 | pan = clamp((inputs[PAN_CV + i].isConnected() ? inputs[PAN_CV + i].getVoltage() : 0) / 10 + pan, 0.0f, 1.0f); 127 | 128 | output_l[i] = output_r[i] = input; 129 | 130 | // determine the left/right mixes 131 | if (pan < 0.5f) { 132 | output_r[i] = (2.0f * pan) * output_r[i]; 133 | } 134 | 135 | if (pan > 0.5f) { 136 | output_l[i] = (2.0f * (1.0f - pan)) * output_l[i]; 137 | } 138 | 139 | // add the output to master 140 | // check if there is a solo, and it's not this one 141 | if (has_solo) { 142 | if (solo[i]) { 143 | master_l += output_l[i]; 144 | master_r += output_r[i]; 145 | } 146 | } else { 147 | master_l += output_l[i]; 148 | master_r += output_r[i]; 149 | } 150 | } 151 | 152 | // the led's 153 | channel_led_l[i] = fabsf(output_l[i]); 154 | channel_led_r[i] = fabsf(output_r[i]); 155 | } 156 | 157 | // check if muted 158 | mute_l_param->update(params[MUTE_L_PARAM].getValue()); 159 | 160 | if (mute_l_param->newTrigger()) { 161 | master_mute_l = !master_mute_l; 162 | } 163 | 164 | if (inputs[MAIN_L_MUTE].isConnected()) { 165 | mute_l->update(inputs[MAIN_L_MUTE].getVoltage()); 166 | 167 | if (mute_l->newTrigger()) { 168 | master_mute_l = !master_mute_l; 169 | } 170 | } 171 | 172 | if (master_mute_l) { 173 | master_l = 0.0f; 174 | } else { 175 | // if there's something to send to, send 176 | if (outputs[MAIN_L_SEND].isConnected()) { 177 | outputs[MAIN_L_SEND].setVoltage(master_l); 178 | } 179 | 180 | // if there is something to receive, calculate the new value 181 | if (inputs[MAIN_L_RECV].isConnected()) { 182 | // get the mix 183 | float mix = clamp((inputs[MAIN_MIX_L_CV].isConnected() ? inputs[MAIN_MIX_L_CV].getVoltage() : 0.0f) + params[MAIN_L_MIX].getValue(), 0.0f, 1.0f); 184 | 185 | // calculate the new "input" 186 | master_l = ((1 - mix) * master_l) + (mix * inputs[MAIN_L_RECV].getVoltage()); 187 | } 188 | 189 | // apply the left volume 190 | float volume = params[VOLUME_L_MAIN].getValue(); 191 | master_l = master_l * volume; 192 | } 193 | 194 | mute_r_param->update(params[MUTE_R_PARAM].getValue()); 195 | 196 | if (mute_r_param->newTrigger()) { 197 | master_mute_r = !master_mute_r; 198 | } 199 | 200 | if (inputs[MAIN_R_MUTE].isConnected()) { 201 | mute_r->update(inputs[MAIN_R_MUTE].getVoltage()); 202 | 203 | if (mute_r->newTrigger()) { 204 | master_mute_r = !master_mute_r; 205 | } 206 | } 207 | 208 | if (master_mute_r) { 209 | master_r = 0.0f; 210 | } else { 211 | // if there's something to send to, send 212 | if (outputs[MAIN_R_SEND].isConnected()) { 213 | outputs[MAIN_R_SEND].setVoltage(master_l); 214 | } 215 | 216 | // if there is something to receive, calculate the new value 217 | if (inputs[MAIN_R_RECV].isConnected()) { 218 | // get the mix 219 | float mix = clamp((inputs[MAIN_MIX_R_CV].isConnected() ? inputs[MAIN_MIX_R_CV].getVoltage() : 0.0f) + params[MAIN_R_MIX].getValue(), 0.0f, 1.0f); 220 | 221 | // calculate the new "input" 222 | master_r = ((1 - mix) * master_r) + (mix * inputs[MAIN_R_RECV].getVoltage()); 223 | } 224 | 225 | // apply the left volume 226 | float volume = params[VOLUME_R_MAIN].getValue(); 227 | master_r = master_r * volume; 228 | } 229 | 230 | 231 | lights[MUTE_L_MAIN].value = master_mute_l ? 1.0f : 0.0f; 232 | lights[MUTE_R_MAIN].value = master_mute_r ? 1.0f : 0.0f; 233 | 234 | // apply master volume to the led's 235 | master_led_l = fabsf(master_l); 236 | master_led_r = fabsf(master_r); 237 | 238 | // and to the output 239 | outputs[MAIN_L_OUT].setVoltage(master_l); 240 | outputs[MAIN_R_OUT].setVoltage(master_r); 241 | } 242 | -------------------------------------------------------------------------------- /src/controller/MixerCV.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | #include "../../deps/SynthDevKit/src/CV.hpp" 5 | 6 | #define MIXER_CHANNELS 8 7 | 8 | struct MixerCVModule : Module { 9 | enum ParamIds { 10 | VOLUME_SLIDER, 11 | PAN_PARAM = MIXER_CHANNELS, 12 | SOLO_PARAM = MIXER_CHANNELS * 2, 13 | MUTE_PARAM = MIXER_CHANNELS * 3, 14 | MIX_PARAM = MIXER_CHANNELS * 4, 15 | VOLUME_L_MAIN = MIXER_CHANNELS * 5, 16 | VOLUME_R_MAIN, 17 | MUTE_L_PARAM, 18 | MUTE_R_PARAM, 19 | MAIN_L_MIX, 20 | MAIN_R_MIX, 21 | NUM_PARAMS 22 | }; 23 | enum InputIds { 24 | RECV, 25 | MIX_CV = MIXER_CHANNELS, 26 | MUTE_CV = MIXER_CHANNELS * 2, 27 | SOLO_CV = MIXER_CHANNELS * 3, 28 | PAN_CV = MIXER_CHANNELS * 4, 29 | VOLUME_CV = MIXER_CHANNELS * 5, 30 | INPUT = MIXER_CHANNELS * 6, 31 | MAIN_L_RECV = MIXER_CHANNELS * 7, 32 | MAIN_R_RECV, 33 | MAIN_L_MUTE, 34 | MAIN_R_MUTE, 35 | MAIN_MIX_L_CV, 36 | MAIN_MIX_R_CV, 37 | NUM_INPUTS 38 | }; 39 | enum OutputIds { 40 | SEND, 41 | MAIN_L_OUT = MIXER_CHANNELS, 42 | MAIN_R_OUT, 43 | MAIN_L_SEND, 44 | MAIN_R_SEND, 45 | NUM_OUTPUTS 46 | }; 47 | 48 | enum LightIds { 49 | SOLO_LIGHT, 50 | MUTE_LIGHT = MIXER_CHANNELS, 51 | MUTE_L_MAIN = MIXER_CHANNELS * 2, 52 | MUTE_R_MAIN, 53 | NUM_LIGHTS 54 | }; 55 | 56 | MixerCVModule(); 57 | void process(const ProcessArgs &args) override; 58 | 59 | float channel_led_l[MIXER_CHANNELS]; 60 | float channel_led_r[MIXER_CHANNELS]; 61 | 62 | float master_led_l = 0.0f; 63 | float master_led_r = 0.0f; 64 | 65 | bool mute[MIXER_CHANNELS]; 66 | bool master_mute_l; 67 | bool master_mute_r; 68 | 69 | bool solo[MIXER_CHANNELS]; 70 | 71 | SynthDevKit::CV *solo_button[MIXER_CHANNELS]; 72 | SynthDevKit::CV *mute_button[MIXER_CHANNELS]; 73 | SynthDevKit::CV *mute_l; 74 | SynthDevKit::CV *mute_r; 75 | SynthDevKit::CV *mute_l_param; 76 | SynthDevKit::CV *mute_r_param; 77 | 78 | SynthDevKit::CV *channel_mute[MIXER_CHANNELS]; 79 | SynthDevKit::CV *channel_solo[MIXER_CHANNELS]; 80 | 81 | json_t *dataToJson() override { 82 | json_t *rootJ = json_object(); 83 | 84 | json_t *m = json_array(); 85 | json_t *s = json_array(); 86 | 87 | for (int i = 0; i < MIXER_CHANNELS; i++) { 88 | json_array_append(m, json_boolean(mute[i])); 89 | json_array_append(s, json_boolean(solo[i])); 90 | } 91 | 92 | json_object_set_new(rootJ, "mute", m); 93 | json_object_set_new(rootJ, "solo", s); 94 | json_object_set_new(rootJ, "mute_l", json_boolean(master_mute_l)); 95 | json_object_set_new(rootJ, "mute_r", json_boolean(master_mute_r)); 96 | 97 | return rootJ; 98 | } 99 | 100 | void dataFromJson(json_t *rootJ) override { 101 | json_t *m = json_object_get(rootJ, "mute"); 102 | json_t *s = json_object_get(rootJ, "solo"); 103 | 104 | for (int i = 0; i < MIXER_CHANNELS; i++) { 105 | if (m && json_is_array(m)) { 106 | json_t *m1 = json_array_get(m, i); 107 | if (m1) { 108 | mute[i] = json_boolean_value(m1); 109 | } 110 | } 111 | 112 | if (s && json_is_array(s)) { 113 | json_t *s1 = json_array_get(s, i); 114 | if (s1) { 115 | solo[i] = json_boolean_value(s1); 116 | } 117 | } 118 | } 119 | 120 | json_t *v = json_object_get(rootJ, "mute_l"); 121 | if (v) { 122 | master_mute_l = json_boolean_value(v); 123 | } else { 124 | master_mute_l = false; 125 | } 126 | 127 | v = json_object_get(rootJ, "mute_r"); 128 | if (v) { 129 | master_mute_r = json_boolean_value(v); 130 | } else { 131 | master_mute_l = false; 132 | } 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /src/controller/Noise.cpp: -------------------------------------------------------------------------------- 1 | #include "Noise.hpp" 2 | 3 | NoiseModule::NoiseModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | wn = new SynthDevKit::WhiteNoise(0); 6 | pn = new SynthDevKit::PinkNoise(0); 7 | cv = new SynthDevKit::CV(1.7f); 8 | 9 | configParam(NoiseModule::NOISE_SWITCH, 0.0, 1.0, 1.0); 10 | } 11 | 12 | void NoiseModule::process(const ProcessArgs &args) { 13 | float cv_in = inputs[CV_INPUT].getVoltage(); 14 | 15 | cv->update(cv_in); 16 | 17 | if (cv->isHigh()) { 18 | if (params[NOISE_SWITCH].getValue()) { 19 | outputs[AUDIO_OUTPUT].setVoltage(wn->stepValue()); 20 | } else { 21 | outputs[AUDIO_OUTPUT].setVoltage(pn->stepValue()); 22 | } 23 | 24 | lights[ON_LED].value = 1; 25 | } else { 26 | outputs[AUDIO_OUTPUT].setVoltage(0); 27 | lights[ON_LED].value = 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/controller/Noise.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../../deps/SynthDevKit/src/CV.hpp" 4 | #include "../../deps/SynthDevKit/src/PinkNoise.hpp" 5 | #include "../../deps/SynthDevKit/src/WhiteNoise.hpp" 6 | #include "../CharredDesert.hpp" 7 | 8 | struct NoiseModule : Module { 9 | enum ParamIds { NOISE_SWITCH, NUM_PARAMS }; 10 | enum InputIds { CV_INPUT, NUM_INPUTS }; 11 | enum OutputIds { AUDIO_OUTPUT, NUM_OUTPUTS }; 12 | enum LightIds { ON_LED, NUM_LIGHTS }; 13 | 14 | NoiseModule(); 15 | 16 | void process(const ProcessArgs &args) override; 17 | 18 | SynthDevKit::WhiteNoise *wn; 19 | SynthDevKit::PinkNoise *pn; 20 | SynthDevKit::CV *cv; 21 | }; 22 | -------------------------------------------------------------------------------- /src/controller/Not.cpp: -------------------------------------------------------------------------------- 1 | #include "Not.hpp" 2 | 3 | NotModule::NotModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | configParam(NotModule::SWITCH, 0.0, 1.0, 1.0); 6 | } 7 | 8 | void NotModule::process(const ProcessArgs &args) { 9 | float in = inputs[INPUT].getVoltage(); 10 | 11 | if (params[SWITCH].getValue() == 0) { 12 | outputs[OUTPUT].setVoltage(-in); 13 | } else { 14 | if (in >= 1.7f) { 15 | outputs[OUTPUT].setVoltage(0); 16 | } else { 17 | outputs[OUTPUT].setVoltage(1.7f); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/controller/Not.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | struct NotModule : Module { 6 | enum ParamIds { SWITCH, NUM_PARAMS }; 7 | enum InputIds { INPUT, NUM_INPUTS }; 8 | enum OutputIds { OUTPUT, NUM_OUTPUTS }; 9 | enum LightIds { NUM_LIGHTS }; 10 | 11 | NotModule(); 12 | 13 | void process(const ProcessArgs &args) override; 14 | }; 15 | -------------------------------------------------------------------------------- /src/controller/Oscar2.cpp: -------------------------------------------------------------------------------- 1 | #include "Oscar2.hpp" 2 | 3 | static float calculateFrequency(float voltage) { 4 | return 261.626f * powf(2.0f, voltage); 5 | } 6 | 7 | static float calculateMix(float input, float param) { 8 | return clamp((input + param), 0.0f, 10.0f); 9 | } 10 | 11 | static float calculateShift(float frequency, float percent) { 12 | return (frequency * (percent / 100)); 13 | } 14 | 15 | static float valueForWave(LowFrequencyOscillator *osc, uint8_t wave) { 16 | if (wave == 0) { 17 | return osc->sin(); 18 | } else if (wave == 1) { 19 | return osc->tri(); 20 | } else if (wave == 2) { 21 | return osc->saw(); 22 | } else if (wave == 3) { 23 | return osc->sqr(); 24 | } else { 25 | return 0.0f; 26 | } 27 | } 28 | 29 | Oscar2Module::Oscar2Module() { 30 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 31 | osc1 = new LowFrequencyOscillator; 32 | osc2 = new LowFrequencyOscillator; 33 | 34 | configParam(Oscar2Module::SHAPE_PARAM1, 0.0f, 3.0f, 0.0f); 35 | configParam(Oscar2Module::SHIFT_PARAM1, 0.0f, 10.0f, 0.0f); 36 | configParam(Oscar2Module::OCTAVE_PARAM1, -4.0f, 4.0f, 0.0f); 37 | configParam(Oscar2Module::FINE_PARAM1, -1.0f, 1.0f, 0.0f); 38 | configParam(Oscar2Module::RANDOM_PARAM1, 0.0f, 5.0f, 0.0f); 39 | configParam(Oscar2Module::INVERT_PARAM1, 0.0f, 1.0f, 1.0f); 40 | configParam(Oscar2Module::SHAPE_PARAM2, 0.0f, 3.0f, 0.0f); 41 | configParam(Oscar2Module::SHIFT_PARAM2, 0.0f, 10.0f, 0.0f); 42 | configParam(Oscar2Module::OCTAVE_PARAM2, -4.0f, 4.0f, 0.0f); 43 | configParam(Oscar2Module::FINE_PARAM2, -1.0f, 1.0f, 0.0f); 44 | configParam(Oscar2Module::RANDOM_PARAM2, 0.0f, 5.0f, 0.0f); 45 | configParam(Oscar2Module::INVERT_PARAM2, 0.0f, 1.0f, 1.0f); 46 | configParam(Oscar2Module::MIX_PARAM, 0.0f, 10.0f, 5.0f); 47 | 48 | } 49 | 50 | void Oscar2Module::process(const ProcessArgs &args) { 51 | float freq = inputs[FREQ_INPUT].getVoltage(); 52 | 53 | osc1->setInvert(params[INVERT_PARAM1].getValue() ? false : true); 54 | osc2->setInvert(params[INVERT_PARAM2].getValue() ? false : true); 55 | 56 | float w1 = clamp(params[SHAPE_PARAM1].getValue() + inputs[SHAPE_INPUT1].getVoltage(), 57 | 0.0f, 3.0f); 58 | wave1 = (uint8_t)w1; 59 | 60 | float s1 = clamp(params[SHIFT_PARAM1].getValue() + inputs[SHIFT_INPUT1].getVoltage(), 61 | 0.0f, 10.0f) * 62 | 10; 63 | 64 | if (s1 != shift1) { 65 | osc1->setShift(calculateShift(calculateFrequency(freq), s1)); 66 | osc1->hardReset(); 67 | osc2->hardReset(); 68 | shift1 = s1; 69 | } 70 | 71 | float octave1 = clamp(params[OCTAVE_PARAM1].getValue() + inputs[OCTAVE_INPUT1].getVoltage(), -5.0f, 5.0f); 72 | float fine1 = params[FINE_PARAM1].getValue() + (inputs[FINE_INPUT1].getVoltage() / 2); 73 | 74 | float freq1 = clamp(freq + octave1 + fine1, -5.0f, 5.0f); 75 | osc1->setFrequency(calculateFrequency(freq1)); 76 | 77 | float rand1 = 78 | clamp(params[RANDOM_PARAM1].getValue() + (inputs[RANDOM_INPUT1].getVoltage() / 2), 79 | 0.0f, 5.0f); 80 | osc1->setRandom(rand1); 81 | 82 | float w2 = clamp(params[SHAPE_PARAM2].getValue() + inputs[SHAPE_INPUT2].getVoltage(), 83 | 0.0f, 3.0f); 84 | wave2 = (uint8_t)w2; 85 | 86 | float s2 = clamp(params[SHIFT_PARAM2].getValue() + inputs[SHIFT_INPUT2].getVoltage(), 87 | 0.0f, 10.0f) * 88 | 10; 89 | 90 | if (s2 != shift2) { 91 | osc2->setShift(calculateShift(calculateFrequency(freq), s1)); 92 | osc2->hardReset(); 93 | osc1->hardReset(); 94 | shift2 = s2; 95 | } 96 | 97 | float octave2 = params[OCTAVE_PARAM2].getValue() + inputs[OCTAVE_INPUT2].getVoltage(); 98 | float fine2 = params[FINE_PARAM2].getValue() + (inputs[FINE_INPUT2].getVoltage() / 2); 99 | 100 | float freq2 = clamp(freq + octave2 + fine2, -5.0f, 5.0f); 101 | osc2->setFrequency(calculateFrequency(freq2)); 102 | 103 | float rand2 = 104 | clamp(params[RANDOM_PARAM2].getValue() + (inputs[RANDOM_INPUT2].getVoltage() / 2), 105 | 0.0f, 5.0f); 106 | osc2->setRandom(rand2); 107 | 108 | osc1->step(args.sampleTime); 109 | osc2->step(args.sampleTime); 110 | 111 | float left = valueForWave(osc1, wave1); 112 | float right = valueForWave(osc2, wave2); 113 | 114 | float mixValue = calculateMix(inputs[MIX_INPUT].getVoltage(), params[MIX_PARAM].getValue()); 115 | float mix = (right * (mixValue / 10)) + (left * (1 - (mixValue / 10))); 116 | 117 | outputs[AUDIO_OUTPUT].setVoltage(5.0f * mix); 118 | } 119 | -------------------------------------------------------------------------------- /src/controller/Oscar2.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | #include "../../deps/SynthDevKit/src/CV.hpp" 6 | 7 | #include "../LFO.hpp" 8 | 9 | struct Oscar2Module : Module { 10 | enum ParamIds { 11 | SHAPE_PARAM1, 12 | SHAPE_PARAM2, 13 | SHIFT_PARAM1, 14 | SHIFT_PARAM2, 15 | OCTAVE_PARAM1, 16 | OCTAVE_PARAM2, 17 | FINE_PARAM1, 18 | FINE_PARAM2, 19 | RANDOM_PARAM1, 20 | RANDOM_PARAM2, 21 | MIX_PARAM, 22 | INVERT_PARAM1, 23 | INVERT_PARAM2, 24 | NUM_PARAMS 25 | }; 26 | enum InputIds { 27 | SHAPE_INPUT1, 28 | SHAPE_INPUT2, 29 | SHIFT_INPUT1, 30 | SHIFT_INPUT2, 31 | OCTAVE_INPUT1, 32 | OCTAVE_INPUT2, 33 | FINE_INPUT1, 34 | FINE_INPUT2, 35 | RANDOM_INPUT1, 36 | RANDOM_INPUT2, 37 | MIX_INPUT, 38 | FREQ_INPUT, 39 | NUM_INPUTS 40 | }; 41 | enum OutputIds { AUDIO_OUTPUT, NUM_OUTPUTS }; 42 | enum LightIds { NUM_LIGHTS }; 43 | 44 | Oscar2Module(); 45 | 46 | void process(const ProcessArgs &args) override; 47 | 48 | float shift1 = 0.0f; 49 | float shift2 = 0.0f; 50 | uint8_t wave1 = 0; 51 | uint8_t wave2 = 0; 52 | LowFrequencyOscillator *osc1; 53 | LowFrequencyOscillator *osc2; 54 | float value = 0.0f; 55 | }; 56 | -------------------------------------------------------------------------------- /src/controller/Pan.cpp: -------------------------------------------------------------------------------- 1 | #include "Pan.hpp" 2 | 3 | PanModule::PanModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | configParam(PanModule::PAN_PARAM, -5.0f, 5.0f, 0.0f); 6 | } 7 | 8 | #define ADD_CV(a, b) clamp(a.value + b.value, -5.0f, 5.0f) 9 | 10 | void PanModule::process(const ProcessArgs &args) { 11 | float audio_in = inputs[AUDIO_INPUT].getVoltage(); 12 | float pan_in = ADD_CV(inputs[PAN_INPUT], params[PAN_PARAM]); 13 | 14 | // figure out the percentages to apply 15 | float apply1 = (clamp(pan_in, -5.0f, 5.0f) + 5.0f) * 10; 16 | float apply2 = 100.0f - apply1; 17 | 18 | outputs[AUDIO_OUTPUT1].setVoltage(((audio_in * apply1) / 100.0f)); 19 | outputs[AUDIO_OUTPUT2].setVoltage(((audio_in * apply2) / 100.0f)); 20 | } 21 | -------------------------------------------------------------------------------- /src/controller/Pan.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | struct PanModule : Module { 6 | enum ParamIds { PAN_PARAM, NUM_PARAMS }; 7 | enum InputIds { AUDIO_INPUT, PAN_INPUT, NUM_INPUTS }; 8 | enum OutputIds { AUDIO_OUTPUT1, AUDIO_OUTPUT2, NUM_OUTPUTS }; 9 | enum LightIds { NUM_LIGHTS }; 10 | 11 | PanModule(); 12 | void process(const ProcessArgs &args) override; 13 | }; 14 | -------------------------------------------------------------------------------- /src/controller/Shift.cpp: -------------------------------------------------------------------------------- 1 | #include "Shift.hpp" 2 | 3 | ShiftModule::ShiftModule() { 4 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 5 | configParam(ShiftModule::SWITCH, 0.0f, 1.0f, 0.0f); 6 | configParam(ShiftModule::KNOB, -5.0f, 5.0f, 0.0f); 7 | } 8 | 9 | #define ADD_CV(a, b) (a.value + b.value) 10 | 11 | void ShiftModule::process(const ProcessArgs &args) { 12 | float in = inputs[INPUT].getVoltage(); 13 | 14 | float shift = ADD_CV(inputs[SHIFT], params[KNOB]); 15 | 16 | if (params[SWITCH].getValue()) { 17 | outputs[OUTPUT].setVoltage(clamp(in + shift, -5.0f, 5.0f)); 18 | } else { 19 | outputs[OUTPUT].setVoltage(in + shift); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/controller/Shift.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | struct ShiftModule : Module { 6 | enum ParamIds { SWITCH, KNOB, NUM_PARAMS }; 7 | enum InputIds { SHIFT, INPUT, NUM_INPUTS }; 8 | enum OutputIds { OUTPUT, NUM_OUTPUTS }; 9 | enum LightIds { NUM_LIGHTS }; 10 | 11 | ShiftModule(); 12 | void process(const ProcessArgs &args) override; 13 | }; 14 | -------------------------------------------------------------------------------- /src/controller/Tine.cpp: -------------------------------------------------------------------------------- 1 | #include "Tine.hpp" 2 | 3 | #define VALUE(a) (a.isConnected() ? a.getVoltage() : 0.0f) 4 | 5 | TineModule::TineModule() { 6 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 7 | configParam(POLARITY, 0, 1, 0); 8 | configParam(SPLIT, 0, 10, 5); 9 | configParam(LOWER_ATT, -10, 10, 0); 10 | configParam(UPPER_ATT, -10, 10, 0); 11 | } 12 | 13 | 14 | float TineModule::paramValue (uint16_t param, uint16_t input, float low, float high) { 15 | float current = params[param].getValue(); 16 | 17 | if (inputs[input].isConnected()) { 18 | // high - low, divided by one tenth input voltage, plus the current value 19 | current += ((inputs[input].getVoltage() / 10) * (high - low)); 20 | } 21 | 22 | return clamp(current, low, high); 23 | } 24 | 25 | 26 | void TineModule::process(const ProcessArgs &args) { 27 | outputs[LOWER_OUT].setVoltage(0); 28 | outputs[UPPER_OUT].setVoltage(0); 29 | lights[LOWER_LIGHT].value = 0; 30 | lights[UPPER_LIGHT].value = 0; 31 | 32 | if (inputs[MODIFIER_IN].isConnected()) { 33 | bool uni = params[POLARITY].getValue() ? true : false; 34 | 35 | float split = paramValue(SPLIT, SPLIT_CV, 0, 10); 36 | float lower = paramValue(LOWER_ATT, LOWER_ATT_CV, -10, 10); 37 | float upper = paramValue(UPPER_ATT, UPPER_ATT_CV, -10, 10); 38 | 39 | if (!uni) { 40 | split -= 5.0f; 41 | } 42 | 43 | float modifier = inputs[MODIFIER_IN].getVoltage(); 44 | float audio = inputs[AUDIO_IN].isConnected() ? inputs[AUDIO_IN].getVoltage() : modifier; 45 | 46 | if (modifier < split) { 47 | outputs[LOWER_OUT].setVoltage(audio + lower); 48 | lights[LOWER_LIGHT].value = 1; 49 | } else { 50 | outputs[UPPER_OUT].setVoltage(audio + upper); 51 | lights[UPPER_LIGHT].value = 1; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/controller/Tine.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../CharredDesert.hpp" 5 | 6 | struct TineModule : Module { 7 | enum ParamIds { 8 | POLARITY, 9 | SPLIT, 10 | LOWER_ATT, 11 | UPPER_ATT, 12 | NUM_PARAMS 13 | }; 14 | enum InputIds { 15 | AUDIO_IN, 16 | MODIFIER_IN, 17 | SPLIT_CV, 18 | LOWER_ATT_CV, 19 | UPPER_ATT_CV, 20 | NUM_INPUTS 21 | }; 22 | enum OutputIds { 23 | LOWER_OUT, 24 | UPPER_OUT, 25 | NUM_OUTPUTS 26 | }; 27 | enum LightIds { 28 | LOWER_LIGHT, 29 | UPPER_LIGHT, 30 | NUM_LIGHTS 31 | }; 32 | 33 | TineModule( ); 34 | float paramValue (uint16_t, uint16_t, float, float); 35 | 36 | void process(const ProcessArgs &args) override; 37 | 38 | std::string id; 39 | }; 40 | -------------------------------------------------------------------------------- /src/controller/Tsunami.cpp: -------------------------------------------------------------------------------- 1 | #include "Tsunami.hpp" 2 | 3 | 4 | TsunamiModule::TsunamiModule() { 5 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 6 | 7 | sampleRate = 44100; 8 | for (uint8_t i = 0; i < SHIFT_COUNT; i++) { 9 | configParam(TsunamiModule::LEVEL + i, 0.0f, 1.0f, 0.5f); 10 | configParam(TsunamiModule::SHIFT + i, 0.0f, 1.0f, float(i) / 10.0); 11 | 12 | delay[i].setMax(uint64_t(sampleRate / 10.0)); 13 | uint64_t nDelay = uint64_t((sampleRate / 10.0) * (float(i) / 10.0)); 14 | delay[i].setDelay(nDelay); 15 | } 16 | 17 | configParam(TsunamiModule::MASTER_LEVEL, 0.0f, 1.0f, 0.5f); 18 | 19 | } 20 | 21 | float TsunamiModule::paramValue (uint16_t param, uint16_t input, float low, float high) { 22 | float current = params[param].getValue(); 23 | 24 | if (inputs[input].isConnected()) { 25 | // high - low, divided by one tenth input voltage, plus the current value 26 | current += ((inputs[input].getVoltage() / 10) * (high - low)); 27 | } 28 | 29 | return clamp(current, low, high); 30 | } 31 | 32 | void TsunamiModule::process(const ProcessArgs &args) { 33 | if (sampleRate != args.sampleRate) { 34 | sampleRate = args.sampleRate; 35 | for (uint8_t i = 0; i < SHIFT_COUNT; i++) { 36 | delay[i].setMax(uint64_t(sampleRate / 10.0)); 37 | } 38 | } 39 | 40 | float in = inputs[MASTER_IN].getVoltage(); 41 | float master_level = params[MASTER_LEVEL].getValue(); 42 | in *= master_level; 43 | 44 | float mix = 0.0f; 45 | for (int i = 0; i < SHIFT_COUNT; i++) { 46 | float level = params[LEVEL + i].getValue(); 47 | float shift = paramValue(SHIFT + i, CV + i, 0.0, 1); 48 | uint64_t nDelay = uint64_t((sampleRate / 10.0) * shift); 49 | if (nDelay != delay[i].delay) { 50 | delay[i].setDelay(nDelay); 51 | } 52 | 53 | float out = delay[i].step(in); 54 | 55 | mix += (out * level); 56 | 57 | outputs[OUT + i].setVoltage(mix); 58 | outputs[POLY_OUT].setVoltage(out * level, i); 59 | } 60 | 61 | outputs[MASTER_OUT].setVoltage(mix); 62 | outputs[POLY_OUT].setChannels(SHIFT_COUNT); 63 | } 64 | -------------------------------------------------------------------------------- /src/controller/Tsunami.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | #include "../model/Delay.hpp" 6 | 7 | #define SHIFT_COUNT 8 8 | 9 | struct TsunamiModule : Module { 10 | enum ParamIds { LEVEL, SHIFT = LEVEL + SHIFT_COUNT, MASTER_LEVEL = SHIFT + SHIFT_COUNT, NUM_PARAMS }; 11 | enum InputIds { CV, IN = CV + SHIFT_COUNT, MASTER_IN = IN + SHIFT_COUNT, NUM_INPUTS }; 12 | enum OutputIds { OUT, POLY_OUT = OUT + SHIFT_COUNT, MASTER_OUT, NUM_OUTPUTS }; 13 | enum LightIds { NUM_LIGHTS }; 14 | 15 | TsunamiModule(); 16 | 17 | void process(const ProcessArgs &args) override; 18 | float paramValue (uint16_t, uint16_t, float, float); 19 | 20 | float sampleRate; 21 | 22 | Delay delay[SHIFT_COUNT]; 23 | }; 24 | -------------------------------------------------------------------------------- /src/controller/X.cpp: -------------------------------------------------------------------------------- 1 | #include "X.hpp" 2 | 3 | #include 4 | 5 | #define VALUE(a) (a.isConnected() ? a.getVoltage() : 0.0f) 6 | 7 | XModule::XModule() { 8 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 9 | for (int i = 0; i < X_COUNT; i++) { 10 | configParam(XModule::KNOB + i, 0.0f, 1.0f, 0.5f); 11 | } 12 | } 13 | 14 | float XModule::paramValue (uint16_t param, uint16_t input, float low, float high) { 15 | float current = params[param].getValue(); 16 | 17 | if (inputs[input].isConnected()) { 18 | // high - low, divided by one tenth input voltage, plus the current value 19 | current += ((inputs[input].getVoltage() / 10) * (high - low)); 20 | } 21 | 22 | return clamp(current, low, high); 23 | } 24 | 25 | 26 | void XModule::process(const ProcessArgs &args) { 27 | for (int i = 0; i < X_COUNT; i++) { 28 | float mix = paramValue(KNOB + i, MIX + i, 0, 1); 29 | float x = inputs[IN + i].getVoltage(); 30 | 31 | float out = (1 - mix) * VALUE(inputs[IN + i]) + mix * tanh(x); 32 | 33 | outputs[OUT + i].setVoltage(out); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/controller/X.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../CharredDesert.hpp" 4 | 5 | #include "../../deps/SynthDevKit/src/CV.hpp" 6 | 7 | #define X_COUNT 2 8 | 9 | struct XModule : Module { 10 | enum ParamIds { KNOB, NUM_PARAMS = X_COUNT }; 11 | enum InputIds { IN, MIX = X_COUNT, NUM_INPUTS = X_COUNT * 2 }; 12 | enum OutputIds { OUT, NUM_OUTPUTS = X_COUNT }; 13 | enum LightIds { NUM_LIGHTS }; 14 | 15 | XModule(); 16 | float paramValue (uint16_t, uint16_t, float, float); 17 | void process(const ProcessArgs &args) override; 18 | }; 19 | -------------------------------------------------------------------------------- /src/controller/filters.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "filters.hpp" 4 | 5 | void MoogFilter::clear() { 6 | f = 0; 7 | pc = 0; 8 | q = 0; 9 | bf0 = 0; 10 | bf1 = 0; 11 | bf2 = 0; 12 | bf3 = 0; 13 | bf4 = 0; 14 | t1 = 0; 15 | t2 = 0; 16 | } 17 | 18 | void MoogFilter::setCoefficients(float f_, float r_) { 19 | float frequency = f_ / (0.5 * sample_rate); 20 | float resonance = r_; 21 | 22 | if (frequency < 0) 23 | frequency = 0; 24 | if (frequency > 0.6) 25 | frequency = 0.6; 26 | 27 | q = 1.0f - frequency; 28 | pc = frequency + 0.8f * frequency * q; 29 | f = pc + pc - 1.0f; 30 | q = resonance * (1.0f + 0.5f * q * (1.0f - q + 5.6f * q * q)); 31 | } 32 | 33 | void MoogFilter::process(float *input, float *output, int samples) { 34 | for (uint16_t i = 0; i < samples; i++) { 35 | float in = input[i]; 36 | in -= q * bf4; // feedback 37 | t1 = bf1; 38 | bf1 = (in + bf0) * pc - bf1 * f; 39 | t2 = bf2; 40 | bf2 = (bf1 + t1) * pc - bf2 * f; 41 | t1 = bf3; 42 | bf3 = (bf2 + t2) * pc - bf3 * f; 43 | bf4 = (bf3 + t1) * pc - bf4 * f; 44 | bf4 = bf4 - bf4 * bf4 * bf4 * 0.166667f; // clipping 45 | bf0 = in; 46 | 47 | // Lowpass output: bf4 48 | // Highpass output: in - bf4; 49 | // Bandpass output: 3.0f * (bf3 - bf4); 50 | output[i] = bf4; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/controller/filters.hpp: -------------------------------------------------------------------------------- 1 | class MoogFilter { 2 | public: 3 | void clear(); 4 | void setType(int t) { 5 | type = t; 6 | } 7 | void setSamplerate(float r) { 8 | sample_rate = r; 9 | } 10 | void setCoefficients(float f, float r); 11 | void process(float *input, float *output, int samples); 12 | 13 | private: 14 | float f, pc, q; 15 | float bf0, bf1, bf2, bf3, bf4; 16 | float t1, t2; 17 | 18 | int type = 0; 19 | float sample_rate; 20 | }; 21 | -------------------------------------------------------------------------------- /src/model/Compressor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct EnvelopeDetection { 6 | EnvelopeDetection () { 7 | prev = 0; 8 | } 9 | 10 | void setCoefficients(float atttackTime, float releaseTime, float sampleRate) { 11 | this->sampleRate = sampleRate; 12 | this->attackTime = exp(-1 / ((sampleRate / 1000) * attackTime)); 13 | this->releaseTime = exp(-1 / ((sampleRate / 1000) * releaseTime)); 14 | } 15 | 16 | float process(float input) { 17 | input = fabs(input); 18 | 19 | if (input > prev) { 20 | prev *= attackTime; 21 | prev += (1 - attackTime) * input; 22 | } else{ 23 | prev *= releaseTime; 24 | prev += (1 - releaseTime) * input; 25 | } 26 | 27 | #if 0 28 | prev = simd::ifelse(input > prev, 29 | (prev * attackTime) + ((1 - attackTime) * input), 30 | (prev * releaseTime) + ((1 - releaseTime) * input)); 31 | #endif 32 | return prev; 33 | } 34 | 35 | float attackTime; 36 | float releaseTime; 37 | float sampleRate; 38 | float prev; 39 | }; 40 | 41 | 42 | struct Compressor { 43 | void setRatio(float ratio) { 44 | this->ratio = 1.0 / ratio; 45 | } 46 | void setThreshold(float threshold) { 47 | this->threshold = threshold; 48 | } 49 | 50 | void setCoefficients(float attackTime, float releaseTime, float threshold, float ratio, float sampleRate) { 51 | this->envelope.setCoefficients(attackTime, releaseTime, sampleRate); 52 | this->ratio = 1.0 / ratio; 53 | this->threshold = threshold; 54 | } 55 | 56 | float process(float input) { 57 | float level = envelope.process(input); 58 | 59 | if (level <= threshold) { 60 | return input; 61 | } else { 62 | //Calculate new level after compression then apply to input 63 | float compLevel = ratio * (level - threshold) + threshold; 64 | 65 | return input * compLevel / level; 66 | } 67 | } 68 | 69 | EnvelopeDetection envelope; 70 | float ratio; 71 | float threshold; 72 | }; 73 | -------------------------------------------------------------------------------- /src/model/Delay.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template struct Delay { 6 | Delay(uint64_t length = 65536) { 7 | buffer = new T[length]; 8 | delay = 0; 9 | inPoint = 0; 10 | outPoint = 0; 11 | this->length = length; 12 | 13 | clear(); 14 | } 15 | 16 | ~Delay( ) { 17 | delete buffer; 18 | } 19 | 20 | void setDelay(uint64_t delay) { 21 | if (inPoint >= delay) { 22 | outPoint = inPoint - delay; 23 | } else { 24 | outPoint = length + inPoint - delay; 25 | } 26 | 27 | this->delay = delay; 28 | } 29 | 30 | void setMax(uint64_t delay) { 31 | if (delay > length) { 32 | delete buffer; 33 | buffer = new T[delay]; 34 | 35 | for (uint64_t i = 0; i < delay; i++) { 36 | buffer[i] = 0; 37 | } 38 | } 39 | 40 | length = delay; 41 | } 42 | 43 | void clear( ) { 44 | for (uint64_t i = 0; i < length; i++) { 45 | buffer[i] = 0; 46 | } 47 | 48 | last = 0.0; 49 | } 50 | 51 | T nextOut( ) { 52 | return buffer[outPoint]; 53 | } 54 | 55 | T step(T in) { 56 | buffer[inPoint] = in; 57 | inPoint++; 58 | 59 | if (inPoint == length) { 60 | inPoint = 0; 61 | } 62 | 63 | last = buffer[outPoint]; 64 | outPoint++; 65 | 66 | if (outPoint == length) { 67 | outPoint = 0; 68 | } 69 | 70 | return last; 71 | } 72 | 73 | T *buffer; 74 | uint64_t length; 75 | uint64_t delay; 76 | uint64_t inPoint; 77 | uint64_t outPoint; 78 | T last; 79 | }; 80 | -------------------------------------------------------------------------------- /src/model/MessageBus.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class Message { 11 | public: 12 | std::string id; 13 | T value; 14 | }; 15 | 16 | template 17 | class MessageBus { 18 | public: 19 | MessageBus() { } 20 | 21 | void send(std::string id, Message *message) { 22 | std::lock_guard lock(memberMutex); 23 | 24 | if (members[id]) { 25 | if (memberData[id]) { 26 | Message *old = memberData[id]; 27 | memberData.erase(id); 28 | 29 | delete old; 30 | } 31 | 32 | memberData[id] = message; 33 | } else { 34 | fprintf(stderr, "Unknown member sent data to the MessageBus: %s\n", id.c_str()); 35 | } 36 | } 37 | 38 | std::vector *currentValues() { 39 | std::vector *data = new std::vector(); 40 | 41 | std::lock_guard lock(memberMutex); 42 | 43 | for (const auto &e : memberData) { 44 | Message *message = (Message *) e.second; 45 | data->push_back(message->value); 46 | } 47 | 48 | return data; 49 | } 50 | 51 | std::string registerMember() { 52 | std::lock_guard lock(memberMutex); 53 | 54 | std::string id = std::to_string(busId); 55 | busId++; 56 | 57 | members[id] = true; 58 | 59 | return id; 60 | } 61 | 62 | void deregisterMember(std::string id) { 63 | std::lock_guard lock(memberMutex); 64 | 65 | if (members[id]) { 66 | if (memberData[id]) { 67 | Message *old = memberData[id]; 68 | delete old; 69 | memberData.erase(id); 70 | } 71 | 72 | members.erase(id); 73 | } else { 74 | fprintf(stderr, "Unknown member tried to deregister: %s\n", id.c_str()); 75 | } 76 | } 77 | 78 | private: 79 | std::mutex memberMutex; 80 | std::unordered_map *> memberData; 81 | std::unordered_map members; 82 | // this would be better handled with a uuid 83 | uint64_t busId = 0; 84 | }; 85 | -------------------------------------------------------------------------------- /src/view/CV.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/CV.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct CVWidget : ModuleWidget { 6 | CVWidget(CVModule *module); 7 | }; 8 | 9 | CVWidget::CVWidget(CVModule *module) { 10 | setModule(module); 11 | box.size = Vec(2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CV.svg"))); 14 | 15 | 16 | for (int i = 0; i < CV_COUNT; i++) { 17 | addParam(createParam(Vec(4, 35 + (190 * i)), module, 18 | CVModule::SWITCH + i)); 19 | 20 | addChild(createLight>( 21 | Vec(5.2, 37 + (190 * i)), module, CVModule::OUT_LIGHT + i)); 22 | 23 | addParam(createParam(Vec(5, 85 + (190 * i)), module, CVModule::KNOB + i)); 24 | 25 | addOutput(createOutput(Vec(3, 140 + (190 * i)), module, 26 | CVModule::OUT + i)); 27 | } 28 | } 29 | 30 | Model *modelCV = createModel("CV"); 31 | -------------------------------------------------------------------------------- /src/view/CVSeq.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/CVSeq.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct CVSeqWidget : ModuleWidget { 6 | CVSeqWidget(CVSeqModule *module); 7 | }; 8 | 9 | CVSeqWidget::CVSeqWidget(CVSeqModule *module) { 10 | setModule(module); 11 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CVSeq.svg"))); 14 | 15 | 16 | addInput(createInput(Vec(4, 35), module, 17 | CVSeqModule::CV_INPUT)); 18 | addOutput(createOutput(Vec(32, 35), module, 19 | CVSeqModule::CV_OUTPUT)); 20 | 21 | addInput(createInput(Vec(4, 85), module, 22 | CVSeqModule::KNOB1_INPUT)); 23 | addParam(createParam( 24 | Vec(28.5, 79.5), module, CVSeqModule::KNOB1)); 25 | 26 | addInput(createInput(Vec(4, 135), module, 27 | CVSeqModule::KNOB2_INPUT)); 28 | addParam(createParam( 29 | Vec(28.5, 129.5), module, CVSeqModule::KNOB2)); 30 | 31 | addInput(createInput(Vec(4, 185), module, 32 | CVSeqModule::KNOB3_INPUT)); 33 | addParam(createParam( 34 | Vec(28.5, 179.5), module, CVSeqModule::KNOB3)); 35 | 36 | addInput(createInput(Vec(4, 235), module, 37 | CVSeqModule::KNOB4_INPUT)); 38 | addParam(createParam( 39 | Vec(28.5, 229.5), module, CVSeqModule::KNOB4)); 40 | 41 | addChild(createLight>( 42 | Vec(36, 109), module, CVSeqModule::LED1)); 43 | addChild(createLight>( 44 | Vec(36, 159), module, CVSeqModule::LED2)); 45 | addChild(createLight>( 46 | Vec(36, 209), module, CVSeqModule::LED3)); 47 | addChild(createLight>( 48 | Vec(36, 259), module, CVSeqModule::LED4)); 49 | } 50 | 51 | Model *modelCVSeq = createModel("CVSequencer"); 52 | -------------------------------------------------------------------------------- /src/view/Carbon.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Carbon.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct CarbonWidget : ModuleWidget { 6 | CarbonWidget(CarbonModule *module); 7 | }; 8 | 9 | CarbonWidget::CarbonWidget(CarbonModule *module) { 10 | setModule(module); 11 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Carbon.svg"))); 14 | 15 | 16 | { 17 | FrequencyDisplay *frequency = new FrequencyDisplay(); 18 | if (module) { 19 | frequency->value = &module->frequency; 20 | } 21 | frequency->box.pos = Vec(5.5, 46); 22 | frequency->box.size = Vec(40, 18); 23 | addChild(frequency); 24 | } 25 | 26 | addInput(createInput(Vec(17.5, 35), module, 27 | CarbonModule::AUDIO_INPUT)); 28 | 29 | addParam(createParam( 30 | Vec(28.5, 104.5), module, CarbonModule::FREQ_PARAM)); 31 | 32 | addInput(createInput(Vec(4, 110), module, 33 | CarbonModule::FREQ_INPUT)); 34 | 35 | addParam(createParam( 36 | Vec(28.5, 154.5), module, CarbonModule::REZ_PARAM)); 37 | 38 | addInput(createInput(Vec(4, 160), module, 39 | CarbonModule::REZ_INPUT)); 40 | 41 | addOutput(createOutput(Vec(17.5, 210), module, CarbonModule::AUDIO_OUTPUT)); 42 | } 43 | 44 | Model *modelCarbon = createModel("Carbon"); 45 | -------------------------------------------------------------------------------- /src/view/DTMF.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/DTMF.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct DTMFWidget : ModuleWidget { 6 | DTMFWidget(DTMFModule *module); 7 | }; 8 | 9 | DTMFWidget::DTMFWidget(DTMFModule *module) { 10 | setModule(module); 11 | box.size = Vec(3 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DTMF.svg"))); 14 | 15 | 16 | addInput(createInput(Vec(10, 35), module, 17 | DTMFModule::CV_INPUT)); 18 | addInput(createInput(Vec(10, 85), module, 19 | DTMFModule::VOCT_INPUT)); 20 | addOutput(createOutput(Vec(10, 135), module, 21 | DTMFModule::AUDIO_OUTPUT)); 22 | addChild(createLight>( 23 | Vec(18, 209), module, DTMFModule::ON_LED)); 24 | } 25 | 26 | Model *modelDTMF = createModel("DTMF"); 27 | -------------------------------------------------------------------------------- /src/view/Eq.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Eq.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct EqWidget : ModuleWidget { 6 | EqWidget(EqModule *module); 7 | }; 8 | 9 | EqWidget::EqWidget(EqModule *module) { 10 | setModule(module); 11 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Eq.svg"))); 14 | 15 | { 16 | FrequencyDisplay *frequency = new FrequencyDisplay(); 17 | if (module) { 18 | frequency->value = &module->frequency; 19 | } 20 | frequency->box.pos = Vec(5.5, 46); 21 | frequency->box.size = Vec(40, 18); 22 | addChild(frequency); 23 | } 24 | 25 | { 26 | EqTypeDisplay *type = new EqTypeDisplay(); 27 | if (module) { 28 | type->value = &module->filterType; 29 | } 30 | type->box.pos = Vec(5.5, 81); 31 | type->box.size = Vec(40, 18); 32 | addChild(type); 33 | } 34 | 35 | 36 | addParam(createParam( 37 | Vec(28.5, 104.5), module, EqModule::FREQ_PARAM)); 38 | 39 | addInput(createInput(Vec(4, 110), module, 40 | EqModule::FREQ_CV_INPUT)); 41 | addParam(createParam( 42 | Vec(17.5, 179.5), module, EqModule::TYPE_PARAM)); 43 | addParam(createParam(Vec(28.5, 229.5), module, 44 | EqModule::Q_PARAM)); 45 | 46 | addInput(createInput(Vec(4, 235), module, 47 | EqModule::Q_CV_INPUT)); 48 | 49 | 50 | addInput(createInput(Vec(0, 35), module, 51 | EqModule::AUDIO_INPUT)); 52 | addOutput(createOutput(Vec(35, 35), module, 53 | EqModule::AUDIO_OUTPUT)); 54 | } 55 | 56 | Model *modelEq = createModel("Eq"); 57 | -------------------------------------------------------------------------------- /src/view/K.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/K.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct KWidget : ModuleWidget { 6 | KWidget(KModule *module); 7 | }; 8 | 9 | KWidget::KWidget(KModule *module) { 10 | setModule(module); 11 | box.size = Vec(2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/K.svg"))); 14 | 15 | 16 | addParam(createParam(Vec(5, 47), module, KModule::THRESHOLD)); 17 | addParam(createParam(Vec(5, 87), module, KModule::RATIO)); 18 | addParam(createParam(Vec(5, 127), module, KModule::ATTACK)); 19 | addParam(createParam(Vec(5, 167), module, KModule::RELEASE)); 20 | 21 | addInput(createInput(Vec(3, 210), module, 22 | KModule::IN)); 23 | 24 | 25 | addOutput(createOutput(Vec(3, 250), module, 26 | KModule::OUT)); 27 | 28 | addChild(createLight>( 29 | Vec(10, 297), module, KModule::ACTIVE)); 30 | 31 | } 32 | 33 | Model *modelK = createModel("K"); 34 | -------------------------------------------------------------------------------- /src/view/M.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/M.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct MWidget : ModuleWidget { 6 | MWidget(MModule *module); 7 | }; 8 | 9 | MWidget::MWidget(MModule *module) { 10 | setModule(module); 11 | box.size = Vec(2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/M.svg"))); 14 | 15 | 16 | for (int i = 0; i < M_COUNT; i++) { 17 | addInput(createInput(Vec(3, 30 + (190 * i)), module, 18 | MModule::IN1 + i)); 19 | 20 | addInput(createInput(Vec(3, 65 + (190 * i)), module, 21 | MModule::IN2 + i)); 22 | 23 | addParam(createParam(Vec(5, 102 + (190 * i)), module, MModule::KNOB + i)); 24 | 25 | addOutput(createOutput(Vec(3, 140 + (190 * i)), module, 26 | MModule::OUT + i)); 27 | } 28 | } 29 | 30 | Model *modelM = createModel("M"); 31 | -------------------------------------------------------------------------------- /src/view/Mixer.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Mixer.hpp" 2 | 3 | #include 4 | 5 | 6 | #include "components.hpp" 7 | 8 | struct MixerWidget : ModuleWidget { 9 | MixerWidget(MixerModule *module); 10 | }; 11 | 12 | MixerWidget::MixerWidget(MixerModule *module) { 13 | setModule(module); 14 | box.size = Vec(28 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 15 | 16 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Mixer.svg"))); 17 | 18 | 19 | for (int i = 0; i < MIXER_CHANNELS; i++) { 20 | // input for each channel 21 | addInput( 22 | createInput(Vec(10 + (45 * i), 20), module, MixerModule::INPUT + i)); 23 | 24 | // left volume LED 25 | { 26 | CDLEDSmallDisplay *led = new CDLEDSmallDisplay(); 27 | if (module) { 28 | led->value = &module->channel_led_l[i]; 29 | } 30 | led->box.pos = Vec(11 + (i * 45), 59); 31 | led->box.size = Vec(8, 80); 32 | addChild(led); 33 | } 34 | 35 | // right volume LED 36 | { 37 | CDLEDSmallDisplay *led = new CDLEDSmallDisplay(); 38 | if (module) { 39 | led->value = &module->channel_led_r[i]; 40 | } 41 | led->box.pos = Vec(25.5 + (i * 45), 59); 42 | led->box.size = Vec(8, 80); 43 | addChild(led); 44 | } 45 | 46 | // volume slider 47 | addParam(createParam(Vec(10.65 + (i * 45), 145), module, 48 | MixerModule::VOLUME_SLIDER + i)); 49 | 50 | // pan knob 51 | addParam(createParam( 52 | Vec(10 + (i * 45), 265), module, MixerModule::PAN_PARAM + i)); 53 | 54 | // solo button 55 | addParam(createParam(Vec(11 + (i * 45), 310), module, 56 | MixerModule::SOLO_PARAM + i)); 57 | addChild(createLight>( 58 | Vec(13.2 + (i * 45), 312), module, MixerModule::SOLO_LIGHT + i)); 59 | 60 | // mute button 61 | addParam(createParam(Vec(11 + (i * 45), 343), module, 62 | MixerModule::MUTE_PARAM + i)); 63 | addChild(createLight>( 64 | Vec(13.2 + (i * 45), 345), module, MixerModule::MUTE_LIGHT + i)); 65 | 66 | } 67 | 68 | // main outputs 69 | addOutput(createOutput(Vec(364, 20), module, 70 | MixerModule::MAIN_L_OUT)); 71 | addOutput(createOutput(Vec(392, 20), module, 72 | MixerModule::MAIN_R_OUT)); 73 | 74 | // main LED 75 | // left master LED 76 | { 77 | CDLEDWideDisplay *led = new CDLEDWideDisplay(); 78 | if (module) { 79 | led->value = &module->master_led_l; 80 | } 81 | led->box.pos = Vec(367, 59); 82 | led->box.size = Vec(16, 110); 83 | addChild(led); 84 | } 85 | 86 | // right master LED 87 | { 88 | CDLEDWideDisplay *led = new CDLEDWideDisplay(); 89 | if (module) { 90 | led->value = &module->master_led_r; 91 | } 92 | led->box.pos = Vec(397, 59); 93 | led->box.size = Vec(16, 110); 94 | addChild(led); 95 | } 96 | 97 | // main volume 98 | addParam(createParam(Vec(363.35, 173), module, 99 | MixerModule::VOLUME_L_MAIN)); 100 | addParam(createParam(Vec(393.35, 173), module, 101 | MixerModule::VOLUME_R_MAIN)); 102 | 103 | // mute 104 | addParam(createParam(Vec(363, 287), module, 105 | MixerModule::MUTE_L_PARAM)); 106 | addChild(createLight>( 107 | Vec(365.2, 289), module, MixerModule::MUTE_L_MAIN)); 108 | 109 | addParam(createParam(Vec(393, 287), module, 110 | MixerModule::MUTE_R_PARAM)); 111 | addChild(createLight>( 112 | Vec(395.2, 289), module, MixerModule::MUTE_R_MAIN)); 113 | 114 | 115 | } 116 | 117 | Model *modelMixer = createModel("Mixer"); 118 | -------------------------------------------------------------------------------- /src/view/MixerCV.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/MixerCV.hpp" 2 | 3 | #include 4 | 5 | 6 | #include "components.hpp" 7 | 8 | struct MixerCVWidget : ModuleWidget { 9 | MixerCVWidget(MixerCVModule *module); 10 | }; 11 | 12 | MixerCVWidget::MixerCVWidget(MixerCVModule *module) { 13 | setModule(module); 14 | box.size = Vec(47 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 15 | 16 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/MixerCV.svg"))); 17 | 18 | 19 | for (int i = 0; i < MIXER_CHANNELS; i++) { 20 | // input for each channel 21 | addInput( 22 | createInput(Vec(10 + (45 * i), 20), module, MixerCVModule::INPUT + i)); 23 | 24 | // left volume LED 25 | { 26 | CDLEDSmallDisplay *led = new CDLEDSmallDisplay(); 27 | if (module) { 28 | led->value = &module->channel_led_l[i]; 29 | } 30 | led->box.pos = Vec(11 + (i * 45), 59); 31 | led->box.size = Vec(8, 80); 32 | addChild(led); 33 | } 34 | 35 | // right volume LED 36 | { 37 | CDLEDSmallDisplay *led = new CDLEDSmallDisplay(); 38 | if (module) { 39 | led->value = &module->channel_led_r[i]; 40 | } 41 | led->box.pos = Vec(25.5 + (i * 45), 59); 42 | led->box.size = Vec(8, 80); 43 | addChild(led); 44 | } 45 | 46 | // volume slider 47 | addParam(createParam(Vec(10.65 + (i * 45), 145), module, 48 | MixerCVModule::VOLUME_SLIDER + i)); 49 | 50 | // pan knob 51 | addParam(createParam( 52 | Vec(10 + (i * 45), 265), module, MixerCVModule::PAN_PARAM + i)); 53 | 54 | // solo button 55 | addParam(createParam(Vec(11 + (i * 45), 310), module, 56 | MixerCVModule::SOLO_PARAM + i)); 57 | addChild(createLight>( 58 | Vec(13.2 + (i * 45), 312), module, MixerCVModule::SOLO_LIGHT + i)); 59 | 60 | // mute button 61 | addParam(createParam(Vec(11 + (i * 45), 343), module, 62 | MixerCVModule::MUTE_PARAM + i)); 63 | addChild(createLight>( 64 | Vec(13.2 + (i * 45), 345), module, MixerCVModule::MUTE_LIGHT + i)); 65 | 66 | } 67 | 68 | // main outputs 69 | addOutput(createOutput(Vec(364, 20), module, 70 | MixerCVModule::MAIN_L_OUT)); 71 | addOutput(createOutput(Vec(392, 20), module, 72 | MixerCVModule::MAIN_R_OUT)); 73 | 74 | // main LED 75 | // left master LED 76 | { 77 | CDLEDWideDisplay *led = new CDLEDWideDisplay(); 78 | if (module) { 79 | led->value = &module->master_led_l; 80 | } 81 | led->box.pos = Vec(367, 59); 82 | led->box.size = Vec(16, 110); 83 | addChild(led); 84 | } 85 | 86 | // right master LED 87 | { 88 | CDLEDWideDisplay *led = new CDLEDWideDisplay(); 89 | if (module) { 90 | led->value = &module->master_led_r; 91 | } 92 | led->box.pos = Vec(397, 59); 93 | led->box.size = Vec(16, 110); 94 | addChild(led); 95 | } 96 | 97 | // main volume 98 | addParam(createParam(Vec(363.35, 173), module, 99 | MixerCVModule::VOLUME_L_MAIN)); 100 | addParam(createParam(Vec(393.35, 173), module, 101 | MixerCVModule::VOLUME_R_MAIN)); 102 | 103 | // mute 104 | addParam(createParam(Vec(363, 287), module, 105 | MixerCVModule::MUTE_L_PARAM)); 106 | addChild(createLight>( 107 | Vec(365.2, 289), module, MixerCVModule::MUTE_L_MAIN)); 108 | 109 | addParam(createParam(Vec(393, 287), module, 110 | MixerCVModule::MUTE_R_PARAM)); 111 | addChild(createLight>( 112 | Vec(395.2, 289), module, MixerCVModule::MUTE_R_MAIN)); 113 | 114 | 115 | for (int i = 0; i < MIXER_CHANNELS; i++) { 116 | // send output 117 | addOutput(createOutput(Vec(422 + (i * 30), 20), module, 118 | MixerCVModule::SEND + i)); 119 | 120 | // recv input 121 | addInput(createInput(Vec(422 + (i * 30), 66), module, 122 | MixerCVModule::RECV + i)); 123 | 124 | // mix 125 | addParam(createParam(Vec(425 + (i * 30), 112), module, 126 | MixerCVModule::MIX_PARAM + i)); 127 | 128 | addInput(createInput(Vec(422 + (i * 30), 158), module, 129 | MixerCVModule::MIX_CV + i)); 130 | 131 | // pan / volume cv 132 | addInput(createInput(Vec(422 + (i * 30), 204), module, 133 | MixerCVModule::VOLUME_CV + i)); 134 | addInput(createInput(Vec(422 + (i * 30), 250), module, 135 | MixerCVModule::PAN_CV + i)); 136 | 137 | // mute / solo cv 138 | addInput(createInput(Vec(422 + (i * 30), 296), module, 139 | MixerCVModule::SOLO_CV + i)); 140 | addInput(createInput(Vec(422 + (i * 30), 342), module, 141 | MixerCVModule::MUTE_CV + i)); 142 | 143 | } 144 | 145 | // main send l 146 | addOutput(createOutput(Vec(669.5, 20), module, 147 | MixerCVModule::MAIN_L_SEND)); 148 | 149 | // main recv l 150 | addInput(createInput(Vec(669.5, 50), module, 151 | MixerCVModule::MAIN_L_RECV)); 152 | 153 | // mix l 154 | addParam(createParam(Vec(670, 85), module, 155 | MixerCVModule::MAIN_L_MIX)); 156 | // mix cv l 157 | addInput(createInput(Vec(669.5, 122), module, 158 | MixerCVModule::MAIN_MIX_L_CV)); 159 | 160 | // mute l 161 | addInput(createInput(Vec(669.5, 154), module, 162 | MixerCVModule::MAIN_L_MUTE)); 163 | 164 | // main send r 165 | addOutput(createOutput(Vec(669.5, 210), module, 166 | MixerCVModule::MAIN_R_SEND)); 167 | 168 | // main recv r 169 | addInput(createInput(Vec(669.5, 240), module, 170 | MixerCVModule::MAIN_R_RECV)); 171 | 172 | // mix r 173 | addParam(createParam(Vec(670, 275), module, 174 | MixerCVModule::MAIN_R_MIX)); 175 | 176 | // main cv r 177 | addInput(createInput(Vec(669.5, 312), module, 178 | MixerCVModule::MAIN_MIX_R_CV)); 179 | 180 | // main mute r 181 | addInput(createInput(Vec(669.5, 344), module, 182 | MixerCVModule::MAIN_R_MUTE)); 183 | } 184 | 185 | Model *modelMixerCV = createModel("MixerCV"); 186 | -------------------------------------------------------------------------------- /src/view/Noise.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Noise.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct NoiseWidget : ModuleWidget { 6 | NoiseWidget(NoiseModule *module); 7 | }; 8 | 9 | NoiseWidget::NoiseWidget(NoiseModule *module) { 10 | setModule(module); 11 | box.size = Vec(3 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Noise.svg"))); 14 | 15 | addInput(createInput(Vec(10, 35), module, 16 | NoiseModule::CV_INPUT)); 17 | addParam(createParam(Vec(15, 95), module, 18 | NoiseModule::NOISE_SWITCH)); 19 | addOutput(createOutput(Vec(10, 135), module, 20 | NoiseModule::AUDIO_OUTPUT)); 21 | addChild(createLight>( 22 | Vec(18, 209), module, NoiseModule::ON_LED)); 23 | } 24 | 25 | Model *modelNoise = createModel("Noise"); 26 | -------------------------------------------------------------------------------- /src/view/Not.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Not.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct NotWidget : ModuleWidget { 6 | NotWidget(NotModule *module); 7 | }; 8 | 9 | NotWidget::NotWidget(NotModule *module) { 10 | setModule(module); 11 | box.size = Vec(3 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Not.svg"))); 14 | 15 | 16 | addInput( 17 | createInput(Vec(10, 35), module, NotModule::INPUT)); 18 | addParam(createParam(Vec(15, 95), module, NotModule::SWITCH)); 19 | addOutput(createOutput(Vec(10, 135), module, 20 | NotModule::OUTPUT)); 21 | } 22 | 23 | Model *modelNot = createModel("Not"); 24 | -------------------------------------------------------------------------------- /src/view/Oscar2.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Oscar2.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct Oscar2Widget : ModuleWidget { 6 | Oscar2Widget(Oscar2Module *module); 7 | }; 8 | 9 | Oscar2Widget::Oscar2Widget(Oscar2Module *module) { 10 | setModule(module); 11 | box.size = Vec(10 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Oscar2.svg"))); 14 | 15 | // wave selection - left 16 | { 17 | WaveSelect *waveSelect = new WaveSelect(); 18 | if (module) { 19 | waveSelect->value = &module->wave1; 20 | } 21 | waveSelect->box.pos = Vec(9.5, 33); 22 | waveSelect->box.size = Vec(10, 10); 23 | addChild(waveSelect); 24 | } 25 | 26 | // shape - left 27 | addInput(createInput(Vec(4, 85), module, 28 | Oscar2Module::SHAPE_INPUT1)); 29 | addParam(createParam( 30 | Vec(28.5, 79.5), module, Oscar2Module::SHAPE_PARAM1)); 31 | 32 | // shift - left 33 | addInput(createInput(Vec(4, 135), module, 34 | Oscar2Module::SHIFT_INPUT1)); 35 | addParam(createParam( 36 | Vec(28.5, 129.5), module, Oscar2Module::SHIFT_PARAM1)); 37 | 38 | // octave - left 39 | addInput(createInput(Vec(4, 185), module, 40 | Oscar2Module::OCTAVE_INPUT1)); 41 | addParam(createParam(Vec(28.5, 179.5), module, 42 | Oscar2Module::OCTAVE_PARAM1)); 43 | 44 | // fine - left 45 | addInput(createInput(Vec(4, 235), module, 46 | Oscar2Module::FINE_INPUT1)); 47 | addParam(createParam( 48 | Vec(28.5, 229.5), module, Oscar2Module::FINE_PARAM1)); 49 | 50 | // random - left 51 | addInput(createInput(Vec(4, 285), module, 52 | Oscar2Module::RANDOM_INPUT1)); 53 | addParam(createParam( 54 | Vec(28.5, 279.5), module, Oscar2Module::RANDOM_PARAM1)); 55 | 56 | // invert - left 57 | addParam(createParam( 58 | Vec(67, 108), module, Oscar2Module::INVERT_PARAM1)); 59 | 60 | // wave selection - right 61 | { 62 | WaveSelect *waveSelect = new WaveSelect(); 63 | if (module) { 64 | waveSelect->value = &module->wave2; 65 | } 66 | waveSelect->box.pos = Vec(55, 33); 67 | waveSelect->box.size = Vec(10, 10); 68 | addChild(waveSelect); 69 | } 70 | 71 | // shape - right 72 | addInput(createInput(Vec(94, 85), module, 73 | Oscar2Module::SHAPE_INPUT2)); 74 | addParam(createParam( 75 | Vec(118.5, 79.5), module, Oscar2Module::SHAPE_PARAM2)); 76 | 77 | // shift - right 78 | addInput(createInput(Vec(94, 135), module, 79 | Oscar2Module::SHIFT_INPUT2)); 80 | addParam(createParam(Vec(118.5, 129.5), module, 81 | Oscar2Module::SHIFT_PARAM2)); 82 | 83 | // octave - right 84 | addInput(createInput(Vec(94, 185), module, 85 | Oscar2Module::OCTAVE_INPUT2)); 86 | addParam(createParam(Vec(118.5, 179.5), module, 87 | Oscar2Module::OCTAVE_PARAM2)); 88 | 89 | // fine - right 90 | addInput(createInput(Vec(94, 235), module, 91 | Oscar2Module::FINE_INPUT2)); 92 | addParam(createParam( 93 | Vec(118.5, 229.5), module, Oscar2Module::FINE_PARAM2)); 94 | 95 | // random - right 96 | addInput(createInput(Vec(94, 285), module, 97 | Oscar2Module::RANDOM_INPUT2)); 98 | addParam(createParam(Vec(118.5, 279.5), module, 99 | Oscar2Module::RANDOM_PARAM2)); 100 | 101 | // invert - right 102 | addParam(createParam( 103 | Vec(67, 158), module, Oscar2Module::INVERT_PARAM2)); 104 | 105 | // mix 106 | addInput(createInput(Vec(62.5, 227), module, 107 | Oscar2Module::MIX_INPUT)); 108 | addParam(createParam( 109 | Vec(62.5, 252.5), module, Oscar2Module::MIX_PARAM)); 110 | 111 | // v/oct 112 | addInput(createInput(Vec(22.5, 330), module, 113 | Oscar2Module::FREQ_INPUT)); 114 | 115 | // mix out 116 | addOutput(createOutput(Vec(102.5, 330), module, 117 | Oscar2Module::AUDIO_OUTPUT)); 118 | } 119 | 120 | Model *modelOscar2 = createModel("Oscar2"); 121 | -------------------------------------------------------------------------------- /src/view/Pan.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Pan.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct PanWidget : ModuleWidget { 6 | PanWidget(PanModule *module); 7 | }; 8 | 9 | PanWidget::PanWidget(PanModule *module) { 10 | setModule(module); 11 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Pan.svg"))); 14 | 15 | 16 | addInput(createInput(Vec(17.5, 35), module, 17 | PanModule::AUDIO_INPUT)); 18 | addParam(createParam( 19 | Vec(28.5, 79.5), module, PanModule::PAN_PARAM)); 20 | addInput(createInput(Vec(4, 85), module, 21 | PanModule::PAN_INPUT)); 22 | addOutput(createOutput(Vec(17.5, 135), module, 23 | PanModule::AUDIO_OUTPUT1)); 24 | addOutput(createOutput(Vec(17.5, 185), module, 25 | PanModule::AUDIO_OUTPUT2)); 26 | } 27 | 28 | Model *modelPan = createModel("Pan"); 29 | -------------------------------------------------------------------------------- /src/view/Shift.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Shift.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct ShiftWidget : ModuleWidget { 6 | ShiftWidget(ShiftModule *module); 7 | }; 8 | 9 | ShiftWidget::ShiftWidget(ShiftModule *module) { 10 | setModule(module); 11 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Shift.svg"))); 14 | 15 | 16 | addInput(createInput(Vec(17.5, 35), module, 17 | ShiftModule::INPUT)); 18 | addParam(createParam(Vec(22.5, 95), module, ShiftModule::SWITCH)); 19 | 20 | addParam(createParam( 21 | Vec(28.5, 154.5), module, ShiftModule::KNOB)); 22 | addInput(createInput(Vec(4, 160), module, 23 | ShiftModule::SHIFT)); 24 | addOutput(createOutput(Vec(17.5, 211), module, 25 | ShiftModule::OUTPUT)); 26 | } 27 | 28 | Model *modelShift = createModel("Shift"); 29 | -------------------------------------------------------------------------------- /src/view/Tine.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Tine.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct TineWidget : ModuleWidget { 6 | TineWidget(TineModule *module); 7 | }; 8 | 9 | TineWidget::TineWidget(TineModule *module) { 10 | setModule(module); 11 | box.size = Vec(4 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Tine.svg"))); 14 | 15 | 16 | addInput(createInput(Vec(4, 35), module, 17 | TineModule::AUDIO_IN)); 18 | addInput(createInput(Vec(32, 35), module, 19 | TineModule::MODIFIER_IN)); 20 | 21 | addParam(createParam(Vec(23, 85), module, TineModule::POLARITY)); 22 | 23 | addInput(createInput(Vec(4, 135), module, 24 | TineModule::SPLIT_CV)); 25 | addParam(createParam( 26 | Vec(28.5, 129.5), module, TineModule::SPLIT)); 27 | 28 | addInput(createInput(Vec(4, 185), module, 29 | TineModule::LOWER_ATT_CV)); 30 | addParam(createParam( 31 | Vec(28.5, 179.5), module, TineModule::LOWER_ATT)); 32 | 33 | addInput(createInput(Vec(4, 235), module, 34 | TineModule::UPPER_ATT_CV)); 35 | addParam(createParam( 36 | Vec(28.5, 229.5), module, TineModule::UPPER_ATT)); 37 | 38 | addOutput(createOutput(Vec(4, 285), module, 39 | TineModule::LOWER_OUT)); 40 | addOutput(createOutput(Vec(32, 285), module, 41 | TineModule::UPPER_OUT)); 42 | 43 | addChild(createLight>( 44 | Vec(12, 177), module, TineModule::LOWER_LIGHT)); 45 | addChild(createLight>( 46 | Vec(12, 227), module, TineModule::UPPER_LIGHT)); 47 | } 48 | 49 | Model *modelTine = createModel("Tine"); 50 | -------------------------------------------------------------------------------- /src/view/Tsunami.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/Tsunami.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct TsunamiWidget : ModuleWidget { 6 | TsunamiWidget(TsunamiModule *module); 7 | }; 8 | 9 | TsunamiWidget::TsunamiWidget(TsunamiModule *module) { 10 | setModule(module); 11 | box.size = Vec(2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Tsunami.svg"))); 14 | 15 | 16 | for (int i = 0; i < SHIFT_COUNT; i++) { 17 | addInput(createInput(Vec(10, 30 + (35 * i)), module, 18 | TsunamiModule::CV + i)); 19 | 20 | addParam(createParam(Vec(57, 30 + (35 * i)), module, TsunamiModule::SHIFT + i)); 21 | 22 | addParam(createParam(Vec(102, 30 + (35 * i)), module, 23 | TsunamiModule::LEVEL + i)); 24 | 25 | addOutput(createOutput(Vec(145, 30 + (35 * i)), module, 26 | TsunamiModule::OUT + i)); 27 | } 28 | 29 | addInput(createInput(Vec(10, 310), module, TsunamiModule::MASTER_IN)); 30 | addParam(createParam(Vec(57, 310), module, TsunamiModule::MASTER_LEVEL)); 31 | addOutput(createOutput(Vec(100, 310), module, TsunamiModule::POLY_OUT)); 32 | addOutput(createOutput(Vec(145, 310), module, TsunamiModule::MASTER_OUT)); 33 | } 34 | 35 | Model *modelTsunami = createModel("Tsunami"); 36 | -------------------------------------------------------------------------------- /src/view/X.cpp: -------------------------------------------------------------------------------- 1 | #include "../controller/X.hpp" 2 | 3 | #include "components.hpp" 4 | 5 | struct XWidget : ModuleWidget { 6 | XWidget(XModule *module); 7 | }; 8 | 9 | XWidget::XWidget(XModule *module) { 10 | setModule(module); 11 | box.size = Vec(2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 12 | 13 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/X.svg"))); 14 | 15 | 16 | for (int i = 0; i < X_COUNT; i++) { 17 | addInput(createInput(Vec(3, 30 + (190 * i)), module, 18 | XModule::IN + i)); 19 | 20 | addOutput(createOutput(Vec(3, 140 + (190 * i)), module, 21 | XModule::OUT + i)); 22 | 23 | addParam(createParam(Vec(5, 102 + (190 * i)), module, XModule::KNOB + i)); 24 | 25 | addInput(createInput(Vec(3, 65 + (190 * i)), module, 26 | XModule::MIX + i)); 27 | } 28 | } 29 | 30 | Model *modelX = createModel("X"); 31 | --------------------------------------------------------------------------------