@@ -32,7 +32,7 @@ Tested in | |||
# Downloads | |||
The current release can be found in the [vst2_bin/](vst2_bin/) folder. | |||
Here's a snapshot of it: [veeseevstrack_0_6_1_win64_bin-19Aug2018.7z](dist/veeseevstrack_0_6_1_win64_bin-19Aug2018.7z) (64bit) | |||
Here's a snapshot of it: [veeseevstrack_0_6_1_win64_bin-21Aug2018.7z](dist/veeseevstrack_0_6_1_win64_bin-21Aug2018.7z) (64bit) | |||
Note: The effect plugin can used be as an instrument, too. You just have to send it MIDI events ! | |||
@@ -73,7 +73,7 @@ The binary distribution contains the following (17) dynamically loaded add-on mo | |||
- Template_shared.MyModule | |||
The following (575) add-on modules are statically linked with the VST plugin: | |||
The following (600) add-on modules are statically linked with the VST plugin: | |||
- 21kHz.D_Inf | |||
- 21kHz.PalmLoop | |||
- Alikins.IdleSwitch | |||
@@ -89,6 +89,16 @@ The following (575) add-on modules are statically linked with the VST plugin: | |||
- alto777_LFSR.cheapFX | |||
- alto777_LFSR.Divada | |||
- alto777_LFSR.YASeq3 | |||
- AmalgamatedHarmonics.Arpeggiator | |||
- AmalgamatedHarmonics.Arpeggiator2 | |||
- AmalgamatedHarmonics.Circle | |||
- AmalgamatedHarmonics.Imperfect | |||
- AmalgamatedHarmonics.Imperfect2 | |||
- AmalgamatedHarmonics.Progress | |||
- AmalgamatedHarmonics.Ruckus | |||
- AmalgamatedHarmonics.ScaleQuantizer | |||
- AmalgamatedHarmonics.ScaleQuantizer2 | |||
- AmalgamatedHarmonics.SLN | |||
- AS.ADSR | |||
- AS.AtNuVrTr | |||
- AS.BPMCalc | |||
@@ -314,6 +324,12 @@ The following (575) add-on modules are statically linked with the VST plugin: | |||
- Fundamental.VCMixer | |||
- Fundamental.VCO | |||
- Fundamental.VCO2 | |||
- Geodesics.BlackHoles | |||
- Geodesics.Pulsars | |||
- Geodesics.Branes | |||
- Geodesics.Ions | |||
- Geodesics.BlankLogo | |||
- Geodesics.BlankInfo | |||
- Gratrix.VCO_F1 | |||
- Gratrix.VCO_F2 | |||
- Gratrix.VCF_F1 | |||
@@ -424,6 +440,15 @@ The following (575) add-on modules are statically linked with the VST plugin: | |||
- mscHack.PingPong | |||
- mscHack.Osc_3Ch | |||
- mscHack.Compressor | |||
- mscHack.Alienz | |||
- mscHack.ASAF8 | |||
- mscHack.Dronez | |||
- mscHack.Mixer_9_3_4 | |||
- mscHack.Mixer_16_4_4 | |||
- mscHack.Mixer_24_4_4 | |||
- mscHack.Morze | |||
- mscHack.OSC_WaveMorph_3 | |||
- mscHack.Windz | |||
- mtsch_plugins.Sum | |||
- mtsch_plugins.Rationals | |||
- mtsch_plugins.TriggerPanic | |||
@@ -0,0 +1,130 @@ | |||
#include "plugin.hpp" | |||
#include "engine.hpp" | |||
#include "app.hpp" | |||
namespace rack { | |||
template <class TModule, class TModuleWidget, typename... Tags> | |||
Model *createModel(std::string author, std::string slug, std::string name, Tags... tags) { | |||
struct TModel : Model { | |||
Module *createModule() override { | |||
TModule *module = new TModule(); | |||
return module; | |||
} | |||
ModuleWidget *createModuleWidget() override { | |||
TModule *module = new TModule(); | |||
TModuleWidget *moduleWidget = new TModuleWidget(module); | |||
moduleWidget->model = this; | |||
return moduleWidget; | |||
} | |||
ModuleWidget *createModuleWidgetNull() override { | |||
TModuleWidget *moduleWidget = new TModuleWidget(NULL); | |||
moduleWidget->model = this; | |||
return moduleWidget; | |||
} | |||
}; | |||
Model *model = new TModel(); | |||
model->author = author; | |||
model->slug = slug; | |||
model->name = name; | |||
model->tags = {tags...}; | |||
return model; | |||
} | |||
template <class TWidget> | |||
TWidget *createWidget(Vec pos) { | |||
TWidget *w = new TWidget(); | |||
w->box.pos = pos; | |||
return w; | |||
} | |||
/** Deprecated. Use createWidget<TScrew>() instead */ | |||
template <class TScrew> | |||
DEPRECATED TScrew *createScrew(Vec pos) { | |||
return createWidget<TScrew>(pos); | |||
} | |||
template <class TParamWidget> | |||
TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
TParamWidget *param = new TParamWidget(); | |||
param->box.pos = pos; | |||
param->module = module; | |||
param->paramId = paramId; | |||
param->setLimits(minValue, maxValue); | |||
param->setDefaultValue(defaultValue); | |||
return param; | |||
} | |||
template <class TParamWidget> | |||
TParamWidget *createParamCentered(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
TParamWidget *param = new TParamWidget(); | |||
param->box.pos = pos.minus(param->box.size.div(2)); | |||
param->module = module; | |||
param->paramId = paramId; | |||
param->setLimits(minValue, maxValue); | |||
param->setDefaultValue(defaultValue); | |||
return param; | |||
} | |||
template <class TPort> | |||
TPort *createInput(Vec pos, Module *module, int inputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos; | |||
port->module = module; | |||
port->type = Port::INPUT; | |||
port->portId = inputId; | |||
return port; | |||
} | |||
template <class TPort> | |||
TPort *createInputCentered(Vec pos, Module *module, int inputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos.minus(port->box.size.div(2)); | |||
port->module = module; | |||
port->type = Port::INPUT; | |||
port->portId = inputId; | |||
return port; | |||
} | |||
template <class TPort> | |||
TPort *createOutput(Vec pos, Module *module, int outputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos; | |||
port->module = module; | |||
port->type = Port::OUTPUT; | |||
port->portId = outputId; | |||
return port; | |||
} | |||
template <class TPort> | |||
TPort *createOutputCentered(Vec pos, Module *module, int outputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos.minus(port->box.size.div(2)); | |||
port->module = module; | |||
port->type = Port::OUTPUT; | |||
port->portId = outputId; | |||
return port; | |||
} | |||
template <class TModuleLightWidget> | |||
TModuleLightWidget *createLight(Vec pos, Module *module, int firstLightId) { | |||
TModuleLightWidget *light = new TModuleLightWidget(); | |||
light->box.pos = pos; | |||
light->module = module; | |||
light->firstLightId = firstLightId; | |||
return light; | |||
} | |||
template <class TModuleLightWidget> | |||
TModuleLightWidget *createLightCentered(Vec pos, Module *module, int firstLightId) { | |||
TModuleLightWidget *light = new TModuleLightWidget(); | |||
light->box.pos = pos.minus(light->box.size.div(2)); | |||
light->module = module; | |||
light->firstLightId = firstLightId; | |||
return light; | |||
} | |||
} // namespace rack |
@@ -9,88 +9,4 @@ | |||
#include "app.hpp" | |||
#include "ui.hpp" | |||
#include "componentlibrary.hpp" | |||
namespace rack { | |||
//////////////////// | |||
// helpers | |||
//////////////////// | |||
/** Deprecated, use Model::create<TModule, TModuleWidget>(...) instead */ | |||
template <class TModuleWidget, typename... Tags> | |||
DEPRECATED Model *createModel(std::string author, std::string slug, std::string name, Tags... tags) { | |||
struct TModel : Model { | |||
ModuleWidget *createModuleWidget() override { | |||
ModuleWidget *moduleWidget = new TModuleWidget(); | |||
moduleWidget->model = this; | |||
return moduleWidget; | |||
} | |||
}; | |||
Model *model = new TModel(); | |||
model->author = author; | |||
model->slug = slug; | |||
model->name = name; | |||
model->tags = {tags...}; | |||
return model; | |||
} | |||
/** Deprecated, use Widget::create<TScrew>() instead */ | |||
template <class TScrew> | |||
DEPRECATED TScrew *createScrew(Vec pos) { | |||
TScrew *screw = new TScrew(); | |||
screw->box.pos = pos; | |||
return screw; | |||
} | |||
/** Deprecated, use ParamWidget::create<TParamWidget>() instead */ | |||
template <class TParamWidget> | |||
DEPRECATED TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
TParamWidget *param = new TParamWidget(); | |||
param->box.pos = pos; | |||
param->module = module; | |||
param->paramId = paramId; | |||
param->setLimits(minValue, maxValue); | |||
param->setDefaultValue(defaultValue); | |||
return param; | |||
} | |||
/** Deprecated, use Port::create<TPort>(..., Port::INPUT, ...) instead */ | |||
template <class TPort> | |||
DEPRECATED TPort *createInput(Vec pos, Module *module, int inputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos; | |||
port->module = module; | |||
port->type = Port::INPUT; | |||
port->portId = inputId; | |||
return port; | |||
} | |||
/** Deprecated, use Port::create<TPort>(..., Port::OUTPUT, ...) instead */ | |||
template <class TPort> | |||
DEPRECATED TPort *createOutput(Vec pos, Module *module, int outputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos; | |||
port->module = module; | |||
port->type = Port::OUTPUT; | |||
port->portId = outputId; | |||
return port; | |||
} | |||
/** Deprecated, use ModuleLightWidget::create<TModuleLightWidget>() instead */ | |||
template<class TModuleLightWidget> | |||
DEPRECATED TModuleLightWidget *createLight(Vec pos, Module *module, int firstLightId) { | |||
TModuleLightWidget *light = new TModuleLightWidget(); | |||
light->box.pos = pos; | |||
light->module = module; | |||
light->firstLightId = firstLightId; | |||
return light; | |||
} | |||
// Rack instance, stores global states | |||
struct Rack { | |||
}; | |||
} // namespace rack | |||
#include "helpers.hpp" |
@@ -0,0 +1,29 @@ | |||
BSD 3-Clause License | |||
Copyright (c) 2017, John Hoar | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are met: | |||
* Redistributions of source code must retain the above copyright notice, this | |||
list of conditions and the following disclaimer. | |||
* Redistributions in binary form must reproduce the above copyright notice, | |||
this list of conditions and the following disclaimer in the documentation | |||
and/or other materials provided with the distribution. | |||
* Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived from | |||
this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -0,0 +1,27 @@ | |||
# Must follow the format in the Naming section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||
SLUG = AmalgamatedHarmonics | |||
# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||
VERSION = 0.6.1 | |||
# FLAGS will be passed to both the C and C++ compiler | |||
FLAGS += | |||
CFLAGS += | |||
CXXFLAGS += | |||
# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. | |||
# Static libraries are fine. | |||
LDFLAGS += | |||
# Add .cpp and .c files to the build | |||
SOURCES += $(wildcard src/*.cpp) | |||
# Add files to the ZIP package when running `make dist` | |||
# The compiled plugin is automatically added. | |||
DISTRIBUTABLES += $(wildcard LICENSE*) res | |||
# If RACK_DIR is not defined when calling the Makefile, default to two levels above | |||
RACK_DIR ?= ../.. | |||
# Include the VCV Rack plugin Makefile framework | |||
include $(RACK_DIR)/plugin.mk |
@@ -0,0 +1,14 @@ | |||
Welcome to the Amalgamated Harmonics; your one-stop shop for barely usable modules for [VCVRack](www.vcvrack.com). | |||
 | |||
* [Scale Quantizer](https://github.com/jhoar/AmalgamatedHarmonics/wiki/Scale-Quantizer), a scale-aware quantizer with multiple input and outputs. | |||
* [Arpeggiator](https://github.com/jhoar/AmalgamatedHarmonics/wiki/Arpeggiator), a multi-input arpeggiator. | |||
* [Progress](https://github.com/jhoar/AmalgamatedHarmonics/wiki/Progress), a chord sequencer. | |||
* [Fifths and Fourths](https://github.com/jhoar/AmalgamatedHarmonics/wiki/54), an implementation of the Circle of Fifths intended to work with Progress and Scale Quantizer. | |||
* [Imperfect](https://github.com/jhoar/AmalgamatedHarmonics/wiki/Imperfect), a trigger-to-gate and clock divider module. | |||
* [Ruckus](https://github.com/jhoar/AmalgamatedHarmonics/wiki/Ruckus), a trigger sequencer based on summed clock-dividers, inspired by the excellent Trigger Riot from Tiptop Audio. | |||
* [SLN](https://github.com/jhoar/AmalgamatedHarmonics/wiki/SLN), Slew-Limited Noise - a MODULE MASHUP of the Befaco Slew Limiter, Audible Instruments Utilities and Bogaudio Noise modules. | |||
The latest release is [0.6.1](https://github.com/jhoar/AmalgamatedHarmonics/releases/tag/v0.6.1). It is available through the [Plugin Manager](https://vcvrack.com/plugins.html). You can contact us at amalgamatedharmonics@outlook.com. We do not post on Facebook. | |||
@@ -0,0 +1,14 @@ | |||
ALL_OBJ= \ | |||
src/AH.o \ | |||
src/Arpeggiator.o \ | |||
src/ArpeggiatorMkII.o \ | |||
src/Circle.o \ | |||
src/Core.o \ | |||
src/Imperfect.o \ | |||
src/Imperfect2.o \ | |||
src/Progress.o \ | |||
src/Ruckus.o \ | |||
src/ScaleQuantizer.o \ | |||
src/ScaleQuantizerMkII.o \ | |||
src/SLN.o \ | |||
src/UI.o |
@@ -0,0 +1,7 @@ | |||
SLUG=AmalgamatedHarmonics | |||
include ../../../build_plugin_pre.mk | |||
include make.objects | |||
include ../../../build_plugin_post.mk |
@@ -0,0 +1,81 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="17.999601" | |||
height="18" | |||
viewBox="0 0 4.7623944 4.7624998" | |||
version="1.1" | |||
id="svg7595" | |||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||
sodipodi:docname="AHButton.svg"> | |||
<defs | |||
id="defs7589"> | |||
<clipPath | |||
id="clip444"> | |||
<path | |||
d="m 1201.582,338.19922 h 18 v 18 h -18 z m 0,0" | |||
id="path24586" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
</defs> | |||
<sodipodi:namedview | |||
id="base" | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:zoom="7.9195959" | |||
inkscape:cx="-67.764383" | |||
inkscape:cy="0.82092183" | |||
inkscape:document-units="mm" | |||
inkscape:current-layer="layer1" | |||
showgrid="false" | |||
fit-margin-top="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-bottom="0" | |||
inkscape:window-width="2560" | |||
inkscape:window-height="1396" | |||
inkscape:window-x="0" | |||
inkscape:window-y="18" | |||
inkscape:window-maximized="0" | |||
units="px" /> | |||
<metadata | |||
id="metadata7592"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<g | |||
inkscape:label="Layer 1" | |||
inkscape:groupmode="layer" | |||
id="layer1" | |||
transform="translate(-46.755712,-67.833036)"> | |||
<g | |||
transform="matrix(0.26458333,0,0,0.26458333,-271.16296,-21.64884)" | |||
clip-path="url(#clip444)" | |||
id="g31387" | |||
style="clip-rule:nonzero;fill:#d4af37;fill-opacity:1"> | |||
<path | |||
style="fill:#d4af37;fill-opacity:1;fill-rule:nonzero;stroke:none" | |||
d="m 1202.125,344.12109 c -1.6953,4.66797 0.7148,9.83594 5.3789,11.53516 4.6719,1.69531 9.8359,-0.71094 11.5352,-5.37891 1.6992,-4.67187 -0.7071,-9.83203 -5.3789,-11.53515 -4.668,-1.69532 -9.8282,0.70703 -11.5352,5.3789" | |||
id="path31385" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
</g> | |||
</svg> |
@@ -0,0 +1,101 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="23.005993" | |||
height="23.005983" | |||
viewBox="0 0 6.087002 6.0869995" | |||
version="1.1" | |||
id="svg16908" | |||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||
sodipodi:docname="AHKnob1.svg"> | |||
<defs | |||
id="defs16902"> | |||
<clipPath | |||
clipPathUnits="userSpaceOnUse" | |||
id="clipPath6367"> | |||
<path | |||
d="M 0,3193 H 2089 V 0 H 0 Z" | |||
id="path6365" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
</defs> | |||
<sodipodi:namedview | |||
id="base" | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:zoom="12.833327" | |||
inkscape:cx="2.9610326" | |||
inkscape:cy="18.000787" | |||
inkscape:document-units="mm" | |||
inkscape:current-layer="layer1" | |||
showgrid="false" | |||
fit-margin-top="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-bottom="0" | |||
inkscape:window-width="1503" | |||
inkscape:window-height="1003" | |||
inkscape:window-x="766" | |||
inkscape:window-y="55" | |||
inkscape:window-maximized="0" | |||
units="px" /> | |||
<metadata | |||
id="metadata16905"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title></dc:title> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<g | |||
inkscape:label="Layer 1" | |||
inkscape:groupmode="layer" | |||
id="layer1" | |||
transform="translate(-230.09584,-126.67083)"> | |||
<g | |||
style="fill:#000000;fill-opacity:1;stroke-width:1.33335185" | |||
id="g6443" | |||
transform="matrix(0.16908321,0,0,-0.16907394,236.18284,129.71437)"> | |||
<path | |||
d="m 0,0 c 0,-9.941 -8.06,-18 -18,-18 -9.941,0 -18,8.059 -18,18 0,9.941 8.059,18 18,18 C -8.06,18 0,9.941 0,0" | |||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.33335185" | |||
id="path6445" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
style="fill:#d4af37;fill-opacity:1;stroke-width:1.33335185" | |||
id="g6447" | |||
transform="matrix(0.16908321,0,0,-0.16907394,236.1656,129.39275)"> | |||
<path | |||
d="m 0,0 c -0.79,1.279 -2.12,2.537 -2.86,4.327 -0.74,1.784 -0.691,3.609 -1.033,5.067 -0.805,0.997 -1.711,1.902 -2.709,2.708 -1.459,0.341 -3.282,0.293 -5.068,1.033 -1.79,0.742 -3.048,2.069 -4.325,2.86 -0.625,0.066 -1.26,0.104 -1.902,0.104 -0.644,0 -1.279,-0.038 -1.903,-0.104 -1.279,-0.791 -2.537,-2.118 -4.327,-2.86 -1.784,-0.74 -3.607,-0.692 -5.067,-1.033 -0.997,-0.806 -1.904,-1.711 -2.708,-2.708 -0.342,-1.458 -0.294,-3.283 -1.035,-5.067 -0.74,-1.79 -2.069,-3.048 -2.858,-4.327 -0.066,-0.625 -0.103,-1.26 -0.103,-1.902 0,-0.643 0.037,-1.278 0.103,-1.905 0.789,-1.277 2.118,-2.535 2.858,-4.325 0.741,-1.784 0.693,-3.609 1.035,-5.066 0.804,-0.998 1.711,-1.904 2.708,-2.709 1.46,-0.342 3.283,-0.293 5.067,-1.032 1.79,-0.743 3.048,-2.071 4.327,-2.861 0.624,-0.065 1.259,-0.103 1.903,-0.103 0.642,0 1.277,0.038 1.902,0.103 1.277,0.79 2.535,2.118 4.325,2.861 1.786,0.739 3.609,0.69 5.068,1.032 0.998,0.805 1.904,1.711 2.709,2.709 0.342,1.457 0.293,3.282 1.033,5.066 0.74,1.79 2.07,3.048 2.86,4.325 0.065,0.627 0.102,1.262 0.102,1.905 C 0.102,-1.26 0.065,-0.625 0,0" | |||
style="fill:#d4af37;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.33335185" | |||
id="path6449" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
<g | |||
style="fill:#000000;fill-opacity:1;stroke-width:1.33335185" | |||
id="g6451" | |||
transform="matrix(0.16908321,0,0,-0.16907394,233.24057,126.68261)"> | |||
<path | |||
d="M 0,0 C -0.195,0.045 -0.393,0.069 -0.598,0.069 -0.804,0.069 -1.002,0.045 -1.196,0 V -18.157 H 0 Z" | |||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.33335185" | |||
id="path6453" | |||
inkscape:connector-curvature="0" /> | |||
</g> | |||
</g> | |||
</svg> |
@@ -0,0 +1,433 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="6.2990241mm" | |||
height="6.3003879mm" | |||
viewBox="0 0 6.2990242 6.3003887" | |||
version="1.1" | |||
id="svg15246" | |||
sodipodi:docname="Trimpot.svg" | |||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"> | |||
<defs | |||
id="defs15240"> | |||
<clipPath | |||
id="clip89"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="18" | |||
height="19" | |||
id="rect4864" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip90"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 0.898438,0.128906 h 16.25 v 17.882813 h -16.25 z m 0,0" | |||
id="path4861" /> | |||
</clipPath> | |||
<mask | |||
id="mask44"> | |||
<g | |||
style="filter:url(#alpha)" | |||
id="g4858" | |||
transform="matrix(0.26458333,0,0,0.26458333,89.358789,128.57765)"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="3052.8701" | |||
height="3351.5" | |||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||
id="rect4856" /> | |||
</g> | |||
</mask> | |||
<filter | |||
id="alpha" | |||
filterUnits="objectBoundingBox" | |||
x="0" | |||
y="0" | |||
width="1" | |||
height="1"> | |||
<feColorMatrix | |||
type="matrix" | |||
in="SourceGraphic" | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
id="feColorMatrix4149" /> | |||
</filter> | |||
<clipPath | |||
id="clipPath17821"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="18" | |||
height="19" | |||
id="rect17819" /> | |||
</clipPath> | |||
<clipPath | |||
id="clipPath17825"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 0.898438,0.128906 h 16.25 v 17.882813 h -16.25 z m 0,0" | |||
id="path17823" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip87"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="24" | |||
height="26" | |||
id="rect4848" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip88"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 0.683594,0.921875 h 22.679687 v 24.9375 H 0.683594 Z m 0,0" | |||
id="path4845" /> | |||
</clipPath> | |||
<mask | |||
id="mask43"> | |||
<g | |||
style="filter:url(#alpha)" | |||
id="g4842" | |||
transform="matrix(0.26458333,0,0,0.26458333,89.358789,128.57765)"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="3052.8701" | |||
height="3351.5" | |||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||
id="rect4840" /> | |||
</g> | |||
</mask> | |||
<filter | |||
id="filter17836" | |||
filterUnits="objectBoundingBox" | |||
x="0" | |||
y="0" | |||
width="1" | |||
height="1"> | |||
<feColorMatrix | |||
type="matrix" | |||
in="SourceGraphic" | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
id="feColorMatrix17834" /> | |||
</filter> | |||
<clipPath | |||
id="clipPath17840"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="24" | |||
height="26" | |||
id="rect17838" /> | |||
</clipPath> | |||
<clipPath | |||
id="clipPath17844"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="m 0.683594,0.921875 h 22.679687 v 24.9375 H 0.683594 Z m 0,0" | |||
id="path17842" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip95"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="18" | |||
height="18" | |||
id="rect4912" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip96"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.140625,0.140625 H 17.199219 V 17.199219 H 0.140625 Z m 0,0" | |||
id="path4909" /> | |||
</clipPath> | |||
<mask | |||
id="mask47"> | |||
<g | |||
style="filter:url(#alpha-3)" | |||
id="g4906" | |||
transform="matrix(0.26458333,0,0,0.26458333,88.611154,119.19859)"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="3052.8701" | |||
height="3351.5" | |||
style="fill:#000000;fill-opacity:0.33000201;stroke:none" | |||
id="rect4904" /> | |||
</g> | |||
</mask> | |||
<filter | |||
id="alpha-3" | |||
filterUnits="objectBoundingBox" | |||
x="0" | |||
y="0" | |||
width="1" | |||
height="1"> | |||
<feColorMatrix | |||
type="matrix" | |||
in="SourceGraphic" | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
id="feColorMatrix4149-6" /> | |||
</filter> | |||
<clipPath | |||
id="clipPath18541"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="18" | |||
height="18" | |||
id="rect18539" /> | |||
</clipPath> | |||
<clipPath | |||
id="clipPath18545"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.140625,0.140625 H 17.199219 V 17.199219 H 0.140625 Z m 0,0" | |||
id="path18543" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip93"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="22" | |||
height="24" | |||
id="rect4896" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip94"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.0390625,0.0390625 H 21.300781 V 23.421875 H 0.0390625 Z m 0,0" | |||
id="path4893" /> | |||
</clipPath> | |||
<mask | |||
id="mask46"> | |||
<g | |||
style="filter:url(#alpha-3)" | |||
id="g4890" | |||
transform="matrix(0.26458333,0,0,0.26458333,88.611154,119.19859)"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="3052.8701" | |||
height="3351.5" | |||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||
id="rect4888" /> | |||
</g> | |||
</mask> | |||
<filter | |||
id="filter18556" | |||
filterUnits="objectBoundingBox" | |||
x="0" | |||
y="0" | |||
width="1" | |||
height="1"> | |||
<feColorMatrix | |||
type="matrix" | |||
in="SourceGraphic" | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
id="feColorMatrix18554" /> | |||
</filter> | |||
<clipPath | |||
id="clipPath18560"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="22" | |||
height="24" | |||
id="rect18558" /> | |||
</clipPath> | |||
<clipPath | |||
id="clipPath18564"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.0390625,0.0390625 H 21.300781 V 23.421875 H 0.0390625 Z m 0,0" | |||
id="path18562" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip91"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="29" | |||
height="32" | |||
id="rect4880" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip92"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.507812,0.5 H 28.855469 V 31.679688 H 0.507812 Z m 0,0" | |||
id="path4877" /> | |||
</clipPath> | |||
<mask | |||
id="mask45"> | |||
<g | |||
style="filter:url(#alpha-3)" | |||
id="g4874" | |||
transform="matrix(0.26458333,0,0,0.26458333,88.611154,119.19859)"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="3052.8701" | |||
height="3351.5" | |||
style="fill:#000000;fill-opacity:0.14999402;stroke:none" | |||
id="rect4872" /> | |||
</g> | |||
</mask> | |||
<filter | |||
id="filter18575" | |||
filterUnits="objectBoundingBox" | |||
x="0" | |||
y="0" | |||
width="1" | |||
height="1"> | |||
<feColorMatrix | |||
type="matrix" | |||
in="SourceGraphic" | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
id="feColorMatrix18573" /> | |||
</filter> | |||
<clipPath | |||
id="clipPath18579"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="29" | |||
height="32" | |||
id="rect18577" /> | |||
</clipPath> | |||
<clipPath | |||
id="clipPath18583"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.507812,0.5 H 28.855469 V 31.679688 H 0.507812 Z m 0,0" | |||
id="path18581" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip202"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="18" | |||
height="18" | |||
id="rect5795" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip203"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.855469,0.140625 H 17.914062 V 17.199219 H 0.855469 Z m 0,0" | |||
id="path5792" /> | |||
</clipPath> | |||
<mask | |||
id="mask104"> | |||
<g | |||
style="filter:url(#alpha-7)" | |||
id="g5789" | |||
transform="matrix(0.26458333,0,0,0.26458333,74.416306,97.613551)"> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="3052.8701" | |||
height="3351.5" | |||
style="fill:#000000;fill-opacity:0.33000201;stroke:none" | |||
id="rect5787" /> | |||
</g> | |||
</mask> | |||
<filter | |||
id="alpha-7" | |||
filterUnits="objectBoundingBox" | |||
x="0" | |||
y="0" | |||
width="1" | |||
height="1"> | |||
<feColorMatrix | |||
type="matrix" | |||
in="SourceGraphic" | |||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" | |||
id="feColorMatrix4149-5" /> | |||
</filter> | |||
<clipPath | |||
id="clipPath18765"> | |||
<rect | |||
y="0" | |||
x="0" | |||
width="18" | |||
height="18" | |||
id="rect18763" /> | |||
</clipPath> | |||
<clipPath | |||
id="clipPath18769"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
d="M 0.855469,0.140625 H 17.914062 V 17.199219 H 0.855469 Z m 0,0" | |||
id="path18767" /> | |||
</clipPath> | |||
</defs> | |||
<sodipodi:namedview | |||
id="base" | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:zoom="5.6" | |||
inkscape:cx="26.106375" | |||
inkscape:cy="37.463453" | |||
inkscape:document-units="mm" | |||
inkscape:current-layer="layer1" | |||
showgrid="false" | |||
inkscape:window-width="1274" | |||
inkscape:window-height="856" | |||
inkscape:window-x="154" | |||
inkscape:window-y="0" | |||
inkscape:window-maximized="0" | |||
units="px" | |||
fit-margin-top="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-bottom="0" /> | |||
<metadata | |||
id="metadata15243"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<g | |||
inkscape:label="Layer 1" | |||
inkscape:groupmode="layer" | |||
id="layer1" | |||
transform="translate(-46.318588,-97.647662)"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path7219" | |||
d="m 46.318588,100.79786 c 0,1.74046 1.409735,3.15019 3.1502,3.15019 1.739088,0 3.148824,-1.40973 3.148824,-3.15019 0,-1.739087 -1.409736,-3.150198 -3.148824,-3.150198 -1.740465,0 -3.1502,1.411111 -3.1502,3.150198" | |||
style="fill:#d4af37;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path7221" | |||
d="m 49.672764,97.658686 c -0.06752,-0.0055 -0.135079,-0.01101 -0.203976,-0.01101 -0.0689,0 -0.137795,0.0055 -0.205317,0.01101 v 3.139174 h 0.409293 z m 0,0" | |||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,95 @@ | |||
Copyright (c) 2017, keshikan (http://www.keshikan.net), | |||
with Reserved Font Name "DSEG". | |||
This Font Software is licensed under the SIL Open Font License, Version 1.1. | |||
This license is copied below, and is also available with a FAQ at: | |||
http://scripts.sil.org/OFL | |||
----------------------------------------------------------- | |||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |||
----------------------------------------------------------- | |||
PREAMBLE | |||
The goals of the Open Font License (OFL) are to stimulate worldwide | |||
development of collaborative font projects, to support the font creation | |||
efforts of academic and linguistic communities, and to provide a free and | |||
open framework in which fonts may be shared and improved in partnership | |||
with others. | |||
The OFL allows the licensed fonts to be used, studied, modified and | |||
redistributed freely as long as they are not sold by themselves. The | |||
fonts, including any derivative works, can be bundled, embedded, | |||
redistributed and/or sold with any software provided that any reserved | |||
names are not used by derivative works. The fonts and derivatives, | |||
however, cannot be released under any other type of license. The | |||
requirement for fonts to remain under this license does not apply | |||
to any document created using the fonts or their derivatives. | |||
DEFINITIONS | |||
"Font Software" refers to the set of files released by the Copyright | |||
Holder(s) under this license and clearly marked as such. This may | |||
include source files, build scripts and documentation. | |||
"Reserved Font Name" refers to any names specified as such after the | |||
copyright statement(s). | |||
"Original Version" refers to the collection of Font Software components as | |||
distributed by the Copyright Holder(s). | |||
"Modified Version" refers to any derivative made by adding to, deleting, | |||
or substituting -- in part or in whole -- any of the components of the | |||
Original Version, by changing formats or by porting the Font Software to a | |||
new environment. | |||
"Author" refers to any designer, engineer, programmer, technical | |||
writer or other person who contributed to the Font Software. | |||
PERMISSION & CONDITIONS | |||
Permission is hereby granted, free of charge, to any person obtaining | |||
a copy of the Font Software, to use, study, copy, merge, embed, modify, | |||
redistribute, and sell modified and unmodified copies of the Font | |||
Software, subject to the following conditions: | |||
1) Neither the Font Software nor any of its individual components, | |||
in Original or Modified Versions, may be sold by itself. | |||
2) Original or Modified Versions of the Font Software may be bundled, | |||
redistributed and/or sold with any software, provided that each copy | |||
contains the above copyright notice and this license. These can be | |||
included either as stand-alone text files, human-readable headers or | |||
in the appropriate machine-readable metadata fields within text or | |||
binary files as long as those fields can be easily viewed by the user. | |||
3) No Modified Version of the Font Software may use the Reserved Font | |||
Name(s) unless explicit written permission is granted by the corresponding | |||
Copyright Holder. This restriction only applies to the primary font name as | |||
presented to the users. | |||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |||
Software shall not be used to promote, endorse or advertise any | |||
Modified Version, except to acknowledge the contribution(s) of the | |||
Copyright Holder(s) and the Author(s) or with their explicit written | |||
permission. | |||
5) The Font Software, modified or unmodified, in part or in whole, | |||
must be distributed entirely under this license, and must not be | |||
distributed under any other license. The requirement for fonts to | |||
remain under this license does not apply to any document created | |||
using the Font Software. | |||
TERMINATION | |||
This license becomes null and void if any of the above conditions are | |||
not met. | |||
DISCLAIMER | |||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |||
OTHER DEALINGS IN THE FONT SOFTWARE. |
@@ -0,0 +1,30 @@ | |||
#include "AH.hpp" | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Arpeggiator); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Arpeggiator2); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Circle); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Imperfect); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Imperfect2); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Progress); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, Ruckus); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, ScaleQuantizer); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, ScaleQuantizer2); | |||
RACK_PLUGIN_MODEL_DECLARE(AmalgamatedHarmonics, SLN); | |||
RACK_PLUGIN_INIT(AmalgamatedHarmonics) { | |||
RACK_PLUGIN_INIT_ID(); | |||
RACK_PLUGIN_INIT_WEBSITE("https://github.com/jhoar/AmalgamatedHarmonics"); | |||
RACK_PLUGIN_INIT_MANUAL("https://github.com/jhoar/AmalgamatedHarmonics/wiki"); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Arpeggiator); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Arpeggiator2); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Circle); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Imperfect); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Imperfect2); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Progress); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, Ruckus); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, ScaleQuantizer); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, ScaleQuantizer2); | |||
RACK_PLUGIN_MODEL_ADD(AmalgamatedHarmonics, SLN); | |||
} |
@@ -0,0 +1,7 @@ | |||
#pragma once | |||
#include "rack.hpp" | |||
using namespace rack; | |||
#define plugin "AmalgamatedHarmonics" |
@@ -0,0 +1,580 @@ | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include "componentlibrary.hpp" | |||
#include "dsp/digital.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct Sequence { | |||
int pDir = 0; | |||
int sDir = 0; | |||
int nStep = 0; | |||
int nDist = 0; | |||
int stepI = 0; | |||
int cycleI = 0; | |||
int stepsRemaining = 0; | |||
int cycleRemaining = 0; | |||
int currDist = 0; | |||
void advanceSequence() { | |||
stepI++; | |||
stepsRemaining--; | |||
} | |||
void advanceCycle() { | |||
cycleI++; | |||
cycleRemaining--; | |||
} | |||
void initSequence(int inputStep, int inputDist, int inputPDir, int inputSDir, bool locked) { | |||
if (!locked) { | |||
nStep = inputStep; | |||
nDist = inputDist; | |||
pDir = inputPDir; | |||
sDir = inputSDir; | |||
} | |||
stepsRemaining = nStep; | |||
stepI = 0; | |||
// At the beginning of the sequence (i.e. dist = 0) | |||
// currDist is the distance from the base note of the sequence, nDist controls the size of the increment | |||
currDist = 0; | |||
} | |||
void setCycle(int n) { | |||
cycleRemaining = n; | |||
cycleI = 0; | |||
} | |||
bool isCycleFinished() { | |||
return (cycleRemaining == 0); | |||
} | |||
bool isSequenceFinished() { | |||
return (stepsRemaining == 0); | |||
} | |||
bool isSequenceStarted() { | |||
return stepI; | |||
} | |||
}; | |||
struct Arpeggiator : AHModule { | |||
const static int MAX_STEPS = 16; | |||
const static int MAX_DIST = 12; //Octave | |||
const static int NUM_PITCHES = 6; | |||
enum ParamIds { | |||
STEP_PARAM, | |||
DIST_PARAM, | |||
PDIR_PARAM, | |||
SDIR_PARAM, | |||
LOCK_PARAM, | |||
TRIGGER_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
CLOCK_INPUT, | |||
STEP_INPUT, | |||
DIST_INPUT, | |||
TRIG_INPUT, | |||
PITCH_INPUT, | |||
NUM_INPUTS = PITCH_INPUT + NUM_PITCHES | |||
}; | |||
enum OutputIds { | |||
OUT_OUTPUT, | |||
GATE_OUTPUT, | |||
EOC_OUTPUT, | |||
EOS_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
LOCK_LIGHT, | |||
NUM_LIGHTS | |||
}; | |||
Arpeggiator() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { } | |||
void step() override; | |||
SchmittTrigger clockTrigger; // for clock | |||
SchmittTrigger trigTrigger; // for step trigger | |||
SchmittTrigger lockTrigger; | |||
SchmittTrigger buttonTrigger; | |||
PulseGenerator triggerPulse; | |||
PulseGenerator gatePulse; | |||
PulseGenerator eosPulse; | |||
PulseGenerator eocPulse; | |||
float pitches[NUM_PITCHES]; | |||
float inputPitches[NUM_PITCHES]; | |||
bool pitchStatus[NUM_PITCHES]; | |||
float pitchValue[NUM_PITCHES]; | |||
int inputPDir; | |||
int inputSDir; | |||
int inputStep = 0; | |||
int inputDist = 0; | |||
bool locked = false; | |||
float outVolts; | |||
bool isRunning = false; | |||
bool freeRunning = false; | |||
Sequence seq; | |||
int newSequence = 0; | |||
int newCycle = 0; | |||
const static int LAUNCH = 1; | |||
const static int COUNTDOWN = 3; | |||
int nValidPitches = 0; | |||
int poll = 5000; | |||
}; | |||
void Arpeggiator::step() { | |||
stepX++; | |||
// Wait a few steps for the inputs to flow through Rack | |||
if (stepX < 10) { | |||
return; | |||
} | |||
// Get the clock rate and semi-tone | |||
float semiTone = 1.0 / 12.0; | |||
// Get inputs from Rack | |||
float clockInput = inputs[CLOCK_INPUT].value; | |||
float trigInput = inputs[TRIG_INPUT].value; | |||
float trigActive = inputs[TRIG_INPUT].active; | |||
float lockInput = params[LOCK_PARAM].value; | |||
float buttonInput = params[TRIGGER_PARAM].value; | |||
float iPDir = params[PDIR_PARAM].value; | |||
float iSDir = params[SDIR_PARAM].value; | |||
float iStep; | |||
if (inputs[STEP_INPUT].active) { | |||
iStep = inputs[STEP_INPUT].value; | |||
inputStep = round(rescale(iStep, -10.0f, 10.0f, 0.0f, MAX_STEPS)); | |||
} else { | |||
iStep = params[STEP_PARAM].value; | |||
inputStep = iStep; | |||
} | |||
float iDist; | |||
if (inputs[DIST_INPUT].active) { | |||
iDist = inputs[DIST_INPUT].value; | |||
inputDist = round(rescale(iDist, -10.0f, 10.0f, 0.0f, MAX_DIST)); | |||
} else { | |||
iDist = params[DIST_PARAM].value; | |||
inputDist = iDist; | |||
} | |||
for (int p = 0; p < NUM_PITCHES; p++) { | |||
int index = PITCH_INPUT + p; | |||
pitchStatus[p] = inputs[index].active; | |||
pitchValue[p] = inputs[index].value; | |||
} | |||
// Process inputs | |||
bool clockStatus = clockTrigger.process(clockInput); | |||
bool triggerStatus = trigTrigger.process(trigInput); | |||
bool lockStatus = lockTrigger.process(lockInput); | |||
bool buttonStatus = buttonTrigger.process(buttonInput); | |||
inputPDir = iPDir; | |||
inputSDir = iSDir; | |||
int nValidPitches = 0; | |||
for (int p = 0; p < NUM_PITCHES; p++) { | |||
if (pitchStatus[p]) { //Plugged in | |||
inputPitches[nValidPitches] = pitchValue[p]; | |||
nValidPitches++; | |||
} | |||
} | |||
// Check that we even have anything plugged in | |||
if (nValidPitches == 0) { | |||
return; // No inputs, no music | |||
} | |||
// Has the trigger input been fired | |||
if (triggerStatus) { | |||
triggerPulse.trigger(5e-5); | |||
if (debugEnabled()) { std::cout << stepX << " Triggered" << std::endl; } | |||
} | |||
// Update the trigger pulse and determine if it is still high | |||
bool triggerHigh = triggerPulse.process(delta); | |||
if (debugEnabled()) { | |||
if (triggerHigh) { | |||
std::cout << stepX << " Trigger is high" << std::endl; | |||
} | |||
} | |||
// Update lock | |||
if (lockStatus) { | |||
if (debugEnabled()) { std::cout << "Toggling lock: " << locked << std::endl; } | |||
locked = !locked; | |||
} | |||
if (newSequence) { | |||
newSequence--; | |||
if (debugEnabled()) { std::cout << stepX << " Countdown newSequence " << newSequence << std::endl; } | |||
} | |||
if (newCycle) { | |||
newCycle--; | |||
if (debugEnabled()) { std::cout << stepX << " Countdown newCycle " << newCycle << std::endl; } | |||
} | |||
// OK so the problem here might be that the clock gate is still high right after the trigger gate fired on the previous step | |||
// So we need to wait a while for the clock gate to go low | |||
// Has the clock input been fired | |||
bool isClocked = false; | |||
if (clockStatus && !triggerHigh) { | |||
if (debugEnabled()) { std::cout << stepX << " Clocked" << std::endl; } | |||
isClocked = true; | |||
} | |||
// Has the trigger input been fired, either on the input or button | |||
if (triggerStatus || buttonStatus) { | |||
newSequence = COUNTDOWN; | |||
newCycle = COUNTDOWN; | |||
if (debugEnabled()) { std::cout << stepX << " Triggered" << std::endl; } | |||
} | |||
// So this is where the free-running could be triggered | |||
if (isClocked && !isRunning) { // Must have a clock and not be already running | |||
if (!trigActive) { // If nothing plugged into the TRIG input | |||
if (debugEnabled()) { std::cout << stepX << " Free running sequence; starting" << std::endl; } | |||
freeRunning = true; // We're free-running | |||
newSequence = COUNTDOWN; | |||
newCycle = LAUNCH; | |||
} else { | |||
if (debugEnabled()) { std::cout << stepX << " Triggered sequence; wait for trigger" << std::endl; } | |||
freeRunning = false; | |||
} | |||
} | |||
// Detect cable being plugged in when free-running, stop free-running | |||
if (freeRunning && trigActive && isRunning) { | |||
if (debugEnabled()) { std::cout << stepX << " TRIG input re-connected" << std::endl; } | |||
freeRunning = false; | |||
} | |||
// Reached the end of the cycle | |||
if (isRunning && isClocked && seq.isCycleFinished()) { | |||
// Completed 1 step | |||
seq.advanceSequence(); | |||
// Pulse the EOC gate | |||
eocPulse.trigger(5e-3); | |||
if (debugEnabled()) { std::cout << stepX << " Finished Cycle S: " << seq.stepI << | |||
" C: " << seq.cycleI << | |||
" sRemain: " << seq.stepsRemaining << | |||
" cRemain: " << seq.cycleRemaining << std::endl; | |||
} | |||
// Reached the end of the sequence | |||
if (isRunning && seq.isSequenceFinished()) { | |||
// Free running, so start new seqeuence & cycle | |||
if (freeRunning) { | |||
newCycle = COUNTDOWN; | |||
newSequence = COUNTDOWN; | |||
} | |||
isRunning = false; | |||
// Pulse the EOS gate | |||
eosPulse.trigger(5e-3); | |||
if (debugEnabled()) { std::cout << stepX << " Finished sequence S: " << seq.stepI << | |||
" C: " << seq.cycleI << | |||
" sRemain: " << seq.stepsRemaining << | |||
" cRemain: " << seq.cycleRemaining << | |||
" flag:" << isRunning << std::endl; | |||
} | |||
} else { | |||
newCycle = LAUNCH; | |||
if (debugEnabled()) { std::cout << stepX << " Flagging new cycle" << std::endl; } | |||
} | |||
} | |||
// If we have been triggered, start a new sequence | |||
if (newSequence == LAUNCH) { | |||
// At the first step of the sequence | |||
if (debugEnabled()) { std::cout << stepX << " New Sequence" << std::endl; } | |||
if (!locked) { | |||
if (debugEnabled()) { std::cout << stepX << " Update sequence inputs" << std::endl; } | |||
} | |||
// So this is where we tweak the sequence parameters | |||
seq.initSequence(inputStep, inputDist, inputPDir, inputSDir, locked); | |||
// We're running now | |||
isRunning = true; | |||
} | |||
// Starting a new cycle | |||
if (newCycle == LAUNCH) { | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Defining cycle: nStep: " << seq.nStep << | |||
" nDist: " << seq.nDist << | |||
" pDir: " << seq.pDir << | |||
" sDir: " << seq.sDir << | |||
" nValidPitches: " << nValidPitches << | |||
" seqLen: " << seq.nStep * nValidPitches; | |||
for (int i = 0; i < nValidPitches; i++) { | |||
std::cout << " P" << i << " V: " << inputPitches[i]; | |||
} | |||
std::cout << std::endl; | |||
} | |||
/// Reset the cycle counters | |||
seq.setCycle(nValidPitches); | |||
if (debugEnabled()) { std::cout << stepX << " New cycle" << std::endl; } | |||
// Deal with RND setting, when sDir == 1, force it up or down | |||
if (seq.sDir == 1) { | |||
if (rand() % 2 == 0) { | |||
seq.sDir = 0; | |||
} else { | |||
seq.sDir = 2; | |||
} | |||
} | |||
// Only starting moving after the first cycle | |||
if (seq.isSequenceStarted()) { | |||
switch (seq.sDir) { | |||
case 0: seq.currDist--; break; | |||
case 2: seq.currDist++; break; | |||
default: ; | |||
} | |||
} | |||
if (!locked) {// Pitches are locked, and so is the order. This keeps randomly generated arps fixed when locked | |||
// pitches[i] are offset from the input values according to the dist setting. Here we calculate the offsets | |||
for (int i = 0; i < nValidPitches; i++) { | |||
int target; | |||
// Read the pitches according to direction, but we should do this for the sequence? | |||
switch (seq.pDir) { | |||
case 0: target = nValidPitches - i - 1; break; // DOWN | |||
case 1: target = rand() % nValidPitches; break; // RANDOM | |||
case 2: target = i; break; // UP | |||
default: target = i; break; // For random case, read randomly from array, so order does not matter | |||
} | |||
// How many semi-tones do we need to shift | |||
float dV = semiTone * seq.nDist * seq.currDist; | |||
pitches[i] = clamp(inputPitches[target] + dV, -10.0, 10.0); | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Pitch: " << i << " stepI: " << seq.stepI << | |||
" dV:" << dV << | |||
" target: " << target << | |||
" in: " << inputPitches[target] << | |||
" out: " << pitches[target] << std::endl; | |||
} | |||
} | |||
} | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Output pitches: "; | |||
for (int i = 0; i < nValidPitches; i++) { | |||
std::cout << " P" << i << " V: " << pitches[i]; | |||
} | |||
std::cout << std::endl; | |||
} | |||
} | |||
// Advance the sequence | |||
// Are we starting a sequence or are running and have been clocked; if so advance the sequence | |||
// Only advance from the clock | |||
if (isRunning && (isClocked || newCycle == LAUNCH)) { | |||
if (debugEnabled()) { std::cout << stepX << " Advance Cycle S: " << seq.stepI << | |||
" C: " << seq.cycleI << | |||
" sRemain: " << seq.stepsRemaining << | |||
" cRemain: " << seq.cycleRemaining << std::endl; | |||
} | |||
// Finally set the out voltage | |||
outVolts = pitches[seq.cycleI]; | |||
if (debugEnabled()) { std::cout << stepX << " Output V = " << outVolts << std::endl; } | |||
// Update counters | |||
seq.advanceCycle(); | |||
// Pulse the output gate | |||
gatePulse.trigger(5e-4); | |||
} | |||
// Set the value | |||
lights[LOCK_LIGHT].value = locked ? 1.0 : 0.0; | |||
outputs[OUT_OUTPUT].value = outVolts; | |||
bool gPulse = gatePulse.process(delta); | |||
bool sPulse = eosPulse.process(delta); | |||
bool cPulse = eocPulse.process(delta); | |||
outputs[GATE_OUTPUT].value = gPulse ? 10.0 : 0.0; | |||
outputs[EOS_OUTPUT].value = sPulse ? 10.0 : 0.0; | |||
outputs[EOC_OUTPUT].value = cPulse ? 10.0 : 0.0; | |||
} | |||
struct ArpeggiatorDisplay : TransparentWidget { | |||
Arpeggiator *module; | |||
int frame = 0; | |||
std::shared_ptr<Font> font; | |||
ArpeggiatorDisplay() { | |||
font = Font::load(assetPlugin(plugin, "res/Roboto-Light.ttf")); | |||
} | |||
void draw(NVGcontext *vg) override { | |||
Vec pos = Vec(0, 20); | |||
nvgFontSize(vg, 20); | |||
nvgFontFaceId(vg, font->handle); | |||
nvgTextLetterSpacing(vg, -1); | |||
nvgFillColor(vg, nvgRGBA(212, 175, 55, 0xff)); | |||
char text[128]; | |||
snprintf(text, sizeof(text), "STEP: %d [%d]", module->seq.nStep, module->inputStep); | |||
nvgText(vg, pos.x + 10, pos.y + 5, text, NULL); | |||
snprintf(text, sizeof(text), "DIST: %d [%d]", module->seq.nDist, module->inputDist); | |||
nvgText(vg, pos.x + 10, pos.y + 25, text, NULL); | |||
if (module->seq.sDir == 0) { | |||
snprintf(text, sizeof(text), "SEQ: DSC"); | |||
} else { | |||
snprintf(text, sizeof(text), "SEQ: ASC"); | |||
} | |||
nvgText(vg, pos.x + 10, pos.y + 45, text, NULL); | |||
switch(module->seq.pDir) { | |||
case 0: snprintf(text, sizeof(text), "ARP: R-L"); break; | |||
case 1: snprintf(text, sizeof(text), "ARP: RND"); break; | |||
case 2: snprintf(text, sizeof(text), "ARP: L-R"); break; | |||
default: snprintf(text, sizeof(text), "ARP: ERR"); break; | |||
} | |||
nvgText(vg, pos.x + 10, pos.y + 65, text, NULL); | |||
std::string inputs ("IN: "); | |||
for (int p = 0; p < Arpeggiator::NUM_PITCHES; p++) { | |||
if (module->pitchStatus[p] && module->pitchValue[p] > -9.999) { //Plugged in or approx -10.0 | |||
inputs = inputs + std::to_string(p + 1); | |||
} | |||
} | |||
nvgText(vg, pos.x + 10, pos.y + 85, inputs.c_str(), NULL); | |||
} | |||
}; | |||
struct ArpeggiatorWidget : ModuleWidget { | |||
ArpeggiatorWidget(Arpeggiator *module); | |||
}; | |||
ArpeggiatorWidget::ArpeggiatorWidget(Arpeggiator *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(240, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/Arpeggiator.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
{ | |||
ArpeggiatorDisplay *display = new ArpeggiatorDisplay(); | |||
display->module = module; | |||
display->box.pos = Vec(10, 95); | |||
display->box.size = Vec(100, 140); | |||
addChild(display); | |||
} | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 0, false, false), Port::OUTPUT, module, Arpeggiator::OUT_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, 0, false, false), Port::OUTPUT, module, Arpeggiator::GATE_OUTPUT)); | |||
addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, 2, 0, false, false), module, Arpeggiator::LOCK_PARAM, 0.0, 1.0, 0.0)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 2, 0, false, false), module, Arpeggiator::LOCK_LIGHT)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, 0, false, false), Port::OUTPUT, module, Arpeggiator::EOC_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 0, false, false), Port::OUTPUT, module, Arpeggiator::EOS_OUTPUT)); | |||
addParam(ParamWidget::create<BefacoPush>(Vec(127, 155), module, Arpeggiator::TRIGGER_PARAM, 0.0, 1.0, 0.0)); | |||
for (int i = 0; i < Arpeggiator::NUM_PITCHES; i++) { | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, i, 5, true, false), Port::INPUT, module, Arpeggiator::PITCH_INPUT + i)); | |||
} | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 4, true, false), Port::INPUT, module, Arpeggiator::STEP_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 1, 4, true, false), module, Arpeggiator::STEP_PARAM, 1.0, 16.0, 1.0)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 4, true, false), Port::INPUT, module, Arpeggiator::DIST_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 3, 4, true, false), module, Arpeggiator::DIST_PARAM, 0.0, 12.0, 0.0)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 4, true, false), Port::INPUT, module, Arpeggiator::TRIG_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 4, true, false), Port::INPUT, module, Arpeggiator::CLOCK_INPUT)); | |||
addParam(ParamWidget::create<BefacoSwitch>(Vec(178.5, 112.0), module, Arpeggiator::SDIR_PARAM, 0, 2, 0)); | |||
addParam(ParamWidget::create<BefacoSwitch>(Vec(178.5, 187.0), module, Arpeggiator::PDIR_PARAM, 0, 2, 0)); | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Arpeggiator) { | |||
Model *modelArpeggiator = Model::create<Arpeggiator, ArpeggiatorWidget>( "Amalgamated Harmonics", "Arpeggiator", "Arpeggiator (deprecated)", ARPEGGIATOR_TAG); | |||
return modelArpeggiator; | |||
} |
@@ -0,0 +1,306 @@ | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include "dsp/digital.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct Circle : AHModule { | |||
const static int NUM_PITCHES = 6; | |||
enum ParamIds { | |||
KEY_PARAM, | |||
MODE_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
ROTL_INPUT, | |||
ROTR_INPUT, | |||
KEY_INPUT, | |||
MODE_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
KEY_OUTPUT, | |||
MODE_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(MODE_LIGHT,7), | |||
ENUMS(BKEY_LIGHT,12), | |||
ENUMS(CKEY_LIGHT,12), | |||
NUM_LIGHTS | |||
}; | |||
enum Scaling { | |||
CHROMATIC, | |||
FIFTHS | |||
}; | |||
Scaling voltScale = FIFTHS; | |||
Circle() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||
void step() override; | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// gateMode | |||
json_t *scaleModeJ = json_integer((int) voltScale); | |||
json_object_set_new(rootJ, "scale", scaleModeJ); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
// gateMode | |||
json_t *scaleModeJ = json_object_get(rootJ, "scale"); | |||
if (scaleModeJ) { | |||
voltScale = (Scaling)json_integer_value(scaleModeJ); | |||
} | |||
} | |||
SchmittTrigger rotLTrigger; | |||
SchmittTrigger rotRTrigger; | |||
PulseGenerator stepPulse; | |||
float outVolts[NUM_PITCHES]; | |||
int baseKeyIndex = 0; | |||
int curKeyIndex = 0; | |||
int curMode = 0; | |||
int poll = 50000; | |||
}; | |||
void Circle::step() { | |||
AHModule::step(); | |||
// Get inputs from Rack | |||
float rotLInput = inputs[ROTL_INPUT].value; | |||
float rotRInput = inputs[ROTR_INPUT].value; | |||
int newKeyIndex = 0; | |||
int deg; | |||
if (inputs[KEY_INPUT].active) { | |||
float fRoot = inputs[KEY_INPUT].value; | |||
if (voltScale == FIFTHS) { | |||
newKeyIndex = CoreUtil().getKeyFromVolts(fRoot); | |||
} else { | |||
CoreUtil().getPitchFromVolts(fRoot, Core::NOTE_C, Core::SCALE_CHROMATIC, &newKeyIndex, °); | |||
} | |||
} else { | |||
newKeyIndex = params[KEY_PARAM].value; | |||
} | |||
int newMode = 0; | |||
if (inputs[MODE_INPUT].active) { | |||
float fMode = inputs[MODE_INPUT].value; | |||
newMode = round(rescale(fabs(fMode), 0.0f, 10.0f, 0.0f, 6.0f)); | |||
} else { | |||
newMode = params[MODE_PARAM].value; | |||
} | |||
curMode = newMode; | |||
// Process inputs | |||
bool rotLStatus = rotLTrigger.process(rotLInput); | |||
bool rotRStatus = rotRTrigger.process(rotRInput); | |||
if (rotLStatus) { | |||
if (debugEnabled()) { std::cout << stepX << " Rotate left: " << curKeyIndex; } | |||
if (voltScale == FIFTHS) { | |||
if (curKeyIndex == 0) { | |||
curKeyIndex = 11; | |||
} else { | |||
curKeyIndex--; | |||
} | |||
} else { | |||
curKeyIndex = curKeyIndex + 5; | |||
if (curKeyIndex > 11) { | |||
curKeyIndex = curKeyIndex - 12; | |||
} | |||
} | |||
if (debugEnabled()) { std::cout << " -> " << curKeyIndex << std::endl; } | |||
} | |||
if (rotRStatus) { | |||
if (debugEnabled()) { std::cout << stepX << " Rotate right: " << curKeyIndex; } | |||
if (voltScale == FIFTHS) { | |||
if (curKeyIndex == 11) { | |||
curKeyIndex = 0; | |||
} else { | |||
curKeyIndex++; | |||
} | |||
} else { | |||
curKeyIndex = curKeyIndex - 5; | |||
if (curKeyIndex < 0) { | |||
curKeyIndex = curKeyIndex + 12; | |||
} | |||
} | |||
if (debugEnabled()) { std::cout << " -> " << curKeyIndex << std::endl; } | |||
} | |||
if (rotLStatus && rotRStatus) { | |||
if (debugEnabled()) { std::cout << stepX << " Reset " << curKeyIndex << std::endl; } | |||
curKeyIndex = baseKeyIndex; | |||
} | |||
if (newKeyIndex != baseKeyIndex) { | |||
if (debugEnabled()) { std::cout << stepX << " New base: " << newKeyIndex << std::endl;} | |||
baseKeyIndex = newKeyIndex; | |||
curKeyIndex = newKeyIndex; | |||
} | |||
int curKey; | |||
int baseKey; | |||
if (voltScale == FIFTHS) { | |||
curKey = CoreUtil().CIRCLE_FIFTHS[curKeyIndex]; | |||
baseKey = CoreUtil().CIRCLE_FIFTHS[baseKeyIndex]; | |||
} else { | |||
curKey = curKeyIndex; | |||
baseKey = baseKeyIndex; | |||
} | |||
float keyVolts = CoreUtil().getVoltsFromKey(curKey); | |||
float modeVolts = CoreUtil().getVoltsFromMode(curMode); | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[CKEY_LIGHT + i].value = 0.0; | |||
lights[BKEY_LIGHT + i].value = 0.0; | |||
} | |||
lights[CKEY_LIGHT + curKey].value = 1.0; | |||
lights[BKEY_LIGHT + baseKey].value = 1.0; | |||
for (int i = 0; i < Core::NUM_MODES; i++) { | |||
lights[MODE_LIGHT + i].value = 0.0; | |||
} | |||
lights[MODE_LIGHT + curMode].value = 1.0; | |||
outputs[KEY_OUTPUT].value = keyVolts; | |||
outputs[MODE_OUTPUT].value = modeVolts; | |||
} | |||
struct CircleWidget : ModuleWidget { | |||
CircleWidget(Circle *module); | |||
Menu *createContextMenu() override; | |||
}; | |||
CircleWidget::CircleWidget(Circle *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(240, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/Circle.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 0, true, false), Port::INPUT, module, Circle::ROTL_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 0, true, false), Port::INPUT, module, Circle::ROTR_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 5, true, false), Port::INPUT, module, Circle::KEY_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 1, 5, true, false), module, Circle::KEY_PARAM, 0.0, 11.0, 0.0)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 5, true, false), Port::INPUT, module, Circle::MODE_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 3, 5, true, false), module, Circle::MODE_PARAM, 0.0, 6.0, 0.0)); | |||
float div = (M_PI * 2) / 12.0; | |||
for (int i = 0; i < 12; i++) { | |||
float cosDiv = cos(div * i); | |||
float sinDiv = sin(div * i); | |||
float xPos = sinDiv * 52.5; | |||
float yPos = cosDiv * 52.5; | |||
float xxPos = sinDiv * 60.0; | |||
float yyPos = cosDiv * 60.0; | |||
// ui.calculateKeyboard(i, xSpace, xOffset, 230.0, &xPos, &yPos, &scale); | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xxPos + 116.5, 149.5 - yyPos), module, Circle::CKEY_LIGHT + CoreUtil().CIRCLE_FIFTHS[i])); | |||
// ui.calculateKeyboard(i, xSpace, xOffset + 72.0, 165.0, &xPos, &yPos, &scale); | |||
addChild(ModuleLightWidget::create<SmallLight<RedLight>>(Vec(xPos + 116.5, 149.5 - yPos), module, Circle::BKEY_LIGHT + CoreUtil().CIRCLE_FIFTHS[i])); | |||
} | |||
float xOffset = 18.0; | |||
for (int i = 0; i < 7; i++) { | |||
float xPos = 2 * xOffset + i * 18.2; | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos, 280.0), module, Circle::MODE_LIGHT + i)); | |||
} | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 5, true, false), Port::OUTPUT, module, Circle::KEY_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 5, true, false), Port::OUTPUT, module, Circle::MODE_OUTPUT)); | |||
} | |||
struct CircleScalingItem : MenuItem { | |||
Circle *circle; | |||
Circle::Scaling scalingMode; | |||
void onAction(EventAction &e) override { | |||
circle->voltScale = scalingMode; | |||
} | |||
void step() override { | |||
rightText = (circle->voltScale == scalingMode) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *CircleWidget::createContextMenu() { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
Circle *circle = dynamic_cast<Circle*>(module); | |||
assert(circle); | |||
MenuLabel *modeLabel = new MenuLabel(); | |||
modeLabel->text = "Root Volt Scaling"; | |||
menu->addChild(modeLabel); | |||
CircleScalingItem *fifthsItem = new CircleScalingItem(); | |||
fifthsItem->text = "Fifths"; | |||
fifthsItem->circle = circle; | |||
fifthsItem->scalingMode = Circle::FIFTHS; | |||
menu->addChild(fifthsItem); | |||
CircleScalingItem *chromaticItem = new CircleScalingItem(); | |||
chromaticItem->text = "Chromatic (V/OCT)"; | |||
chromaticItem->circle = circle; | |||
chromaticItem->scalingMode = Circle::CHROMATIC; | |||
menu->addChild(chromaticItem); | |||
return menu; | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Circle) { | |||
Model *modelCircle = Model::create<Circle, CircleWidget>( "Amalgamated Harmonics", "Circle", "Fifths and Fourths", SEQUENCER_TAG); | |||
return modelCircle; | |||
} | |||
// ♯♠|
@@ -0,0 +1,205 @@ | |||
#include "Core.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
float Core::getPitchFromVolts(float inVolts, float inRoot, float inScale, int *outRoot, int *outScale, int *outNote, int *outDegree) { | |||
// get the root note and scale | |||
int currRoot = getKeyFromVolts(inRoot); | |||
int currScale = getScaleFromVolts(inScale); | |||
if (debug && stepX % poll == 0) { | |||
std::cout << "QUANT " << stepX << " Root in: " << inRoot << " Root out: " << currRoot<< " Scale in: " << inScale << " Scale out: " << currScale << std::endl; | |||
} | |||
float outVolts = getPitchFromVolts(inVolts, currRoot, currScale, outNote, outDegree); | |||
*outRoot = currRoot; | |||
*outScale = currScale; | |||
return outVolts; | |||
} | |||
float Core::getPitchFromVolts(float inVolts, int currRoot, int currScale, int *outNote, int *outDegree) { | |||
int *curScaleArr; | |||
int notesInScale = 0; | |||
switch (currScale){ | |||
case SCALE_CHROMATIC: curScaleArr = ASCALE_CHROMATIC; notesInScale=LENGTHOF(ASCALE_CHROMATIC); break; | |||
case SCALE_IONIAN: curScaleArr = ASCALE_IONIAN; notesInScale=LENGTHOF(ASCALE_IONIAN); break; | |||
case SCALE_DORIAN: curScaleArr = ASCALE_DORIAN; notesInScale=LENGTHOF(ASCALE_DORIAN); break; | |||
case SCALE_PHRYGIAN: curScaleArr = ASCALE_PHRYGIAN; notesInScale=LENGTHOF(ASCALE_PHRYGIAN); break; | |||
case SCALE_LYDIAN: curScaleArr = ASCALE_LYDIAN; notesInScale=LENGTHOF(ASCALE_LYDIAN); break; | |||
case SCALE_MIXOLYDIAN: curScaleArr = ASCALE_MIXOLYDIAN; notesInScale=LENGTHOF(ASCALE_MIXOLYDIAN); break; | |||
case SCALE_AEOLIAN: curScaleArr = ASCALE_AEOLIAN; notesInScale=LENGTHOF(ASCALE_AEOLIAN); break; | |||
case SCALE_LOCRIAN: curScaleArr = ASCALE_LOCRIAN; notesInScale=LENGTHOF(ASCALE_LOCRIAN); break; | |||
case SCALE_MAJOR_PENTA: curScaleArr = ASCALE_MAJOR_PENTA; notesInScale=LENGTHOF(ASCALE_MAJOR_PENTA); break; | |||
case SCALE_MINOR_PENTA: curScaleArr = ASCALE_MINOR_PENTA; notesInScale=LENGTHOF(ASCALE_MINOR_PENTA); break; | |||
case SCALE_HARMONIC_MINOR: curScaleArr = ASCALE_HARMONIC_MINOR; notesInScale=LENGTHOF(ASCALE_HARMONIC_MINOR); break; | |||
case SCALE_BLUES: curScaleArr = ASCALE_BLUES; notesInScale=LENGTHOF(ASCALE_BLUES); break; | |||
default: curScaleArr = ASCALE_CHROMATIC; notesInScale=LENGTHOF(ASCALE_CHROMATIC); | |||
} | |||
// get the octave | |||
int octave = floor(inVolts); | |||
float closestVal = 10.0; | |||
float closestDist = 10.0; | |||
int noteFound = 0; | |||
if (debug && stepX % poll == 0) { | |||
std::cout << "QUANT Octave: " << octave << " Scale: " << scaleNames[currScale] << " Root: " << noteNames[currRoot] << std::endl; | |||
} | |||
float octaveOffset = 0; | |||
if (currRoot != 0) { | |||
octaveOffset = (12 - currRoot) / 12.0; | |||
} | |||
if (debug && stepX % poll == 0) { | |||
std::cout << "QUANT Octave: " << octave << " currRoot: " << currRoot << " -> Offset: " << octaveOffset << " inVolts: " << inVolts << std::endl; | |||
} | |||
float fOctave = (float)octave - octaveOffset; | |||
int scaleIndex = 0; | |||
int searchOctave = 0; | |||
do { | |||
int degree = curScaleArr[scaleIndex]; // 0 - 11! | |||
float fVoltsAboveOctave = searchOctave + degree / 12.0; | |||
float fScaleNoteInVolts = fOctave + fVoltsAboveOctave; | |||
float distAway = fabs(inVolts - fScaleNoteInVolts); | |||
if (debug && stepX % poll == 0) { | |||
std::cout << "QUANT input: " << inVolts | |||
<< " index: " << scaleIndex | |||
<< " root: " << currRoot | |||
<< " octave: " << fOctave | |||
<< " degree: " << degree | |||
<< " V above O: " << fVoltsAboveOctave | |||
<< " note in V: " << fScaleNoteInVolts | |||
<< " distance: " << distAway | |||
<< std::endl; | |||
} | |||
// Assume that the list of notes is ordered, so there is an single inflection point at the minimum value | |||
if (distAway >= closestDist){ | |||
break; | |||
} else { | |||
// Let's remember this | |||
closestVal = fScaleNoteInVolts; | |||
closestDist = distAway; | |||
} | |||
scaleIndex++; | |||
if (scaleIndex == notesInScale - 1) { | |||
scaleIndex = 0; | |||
searchOctave++; | |||
} | |||
} while (true); | |||
if(scaleIndex == 0) { | |||
noteFound = notesInScale - 2; // NIS is a count, not index | |||
} else { | |||
noteFound = scaleIndex - 1; | |||
} | |||
if (debug && stepX % poll == 0) { | |||
std::cout << "QUANT NIS: " << notesInScale << " scaleIndex: " << scaleIndex << " NF: " << noteFound << std::endl; | |||
} | |||
int currNote = (currRoot + curScaleArr[noteFound]) % 12; // So this is the nth note of the scale; | |||
// case in point, V=0, Scale = F#m returns the 6th note, which should be C# | |||
if (debug && stepX % poll == 0) { | |||
// Dump the note and degree, mod the size in case where we have wrapped round | |||
std::cout << "QUANT Found index in scale: " << noteFound << ", currNote: " << currNote; | |||
std::cout << " This is scale note: " << curScaleArr[noteFound] << " (Interval: " << intervalNames[curScaleArr[noteFound]] << ")"; | |||
std::cout << ": " << inVolts << " -> " << closestVal << std::endl; | |||
} | |||
*outNote = currNote; | |||
*outDegree = curScaleArr[noteFound]; | |||
return closestVal; | |||
} | |||
void Core::getRootFromMode(int inMode, int inRoot, int inTonic, int *currRoot, int *quality) { | |||
*quality = ModeQuality[inMode][inTonic]; | |||
int positionRelativeToStartOfScale = tonicIndex[inMode + inTonic]; | |||
int positionStartOfScale = scaleIndex[inMode]; | |||
// FIXME should be mapped into the Circle of Fifths?? | |||
*currRoot = inRoot + noteIndex[positionStartOfScale + positionRelativeToStartOfScale]; | |||
if (*currRoot < 0) { | |||
*currRoot += 12; | |||
} | |||
if (*currRoot > 11) { | |||
*currRoot -= 12; | |||
} | |||
// Quantizer q; | |||
// | |||
// std::cout << "Mode: " << inMode | |||
// << " Root: " << q.noteNames[inRoot] | |||
// << " Tonic: " << q.tonicNames[inTonic] | |||
// << " Scale Pos: " << positionStartOfScale | |||
// << " Rel Pos: " << positionRelativeToStartOfScale | |||
// << " Note Index: " << positionStartOfScale + positionRelativeToStartOfScale | |||
// << " Note: " << noteIndex[positionStartOfScale + positionRelativeToStartOfScale] | |||
// << " Offset: " << ModeOffset[inMode][inTonic] | |||
// << " Output: " << *currRoot | |||
// << " " << q.noteNames[*currRoot] | |||
// << std::endl; | |||
} | |||
Core & CoreUtil() { | |||
static Core core; | |||
return core; | |||
} | |||
// http://c-faq.com/lib/gaussian.html | |||
double Core::gaussrand() { | |||
static double U, V; | |||
static int phase = 0; | |||
double Z; | |||
if(phase == 0) { | |||
U = (rand() + 1.) / (RAND_MAX + 2.); | |||
V = rand() / (RAND_MAX + 1.); | |||
Z = sqrt(-2 * log(U)) * sin(2 * M_PI * V); | |||
} else | |||
Z = sqrt(-2 * log(U)) * cos(2 * M_PI * V); | |||
phase = 1 - phase; | |||
return Z; | |||
} | |||
int Core::ipow(int base, int exp) { | |||
int result = 1; | |||
while (exp) | |||
{ | |||
if (exp & 1) | |||
result *= base; | |||
exp >>= 1; | |||
base *= base; | |||
} | |||
return result; | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics |
@@ -0,0 +1,527 @@ | |||
#pragma once | |||
#include <iostream> | |||
#include "dsp/digital.hpp" | |||
#include "AH.hpp" | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct AHPulseGenerator { | |||
float time = 0.f; | |||
float pulseTime = 0.f; | |||
bool ishigh() { | |||
return time < pulseTime; | |||
} | |||
bool process(float deltaTime) { | |||
time += deltaTime; | |||
return time < pulseTime; | |||
} | |||
bool trigger(float pulseTime) { | |||
// Keep the previous pulseTime if the existing pulse would be held longer than the currently requested one. | |||
if (time + pulseTime >= this->pulseTime) { | |||
time = 0.f; | |||
this->pulseTime = pulseTime; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
}; | |||
struct BpmCalculator { | |||
float timer = 0.0f; | |||
int misses = 0; | |||
float seconds = 0; | |||
SchmittTrigger gateTrigger; | |||
inline bool checkBeat(int mult) { | |||
return ( ((timer - mult * seconds) * (timer - mult * seconds) / (seconds * seconds) < 0.2f ) && misses < 4); | |||
} | |||
float calculateBPM(float delta, float input) { | |||
if (gateTrigger.process(input) ) { | |||
if (timer > 0) { | |||
float new_seconds; | |||
bool found = false; | |||
for(int mult = 1; !found && mult < 20; mult++ ) { | |||
if (checkBeat(mult)) { | |||
new_seconds = timer / mult; | |||
if (mult == 1) { | |||
misses = 0; | |||
} else { | |||
misses++; | |||
} | |||
found = true; | |||
}; | |||
}; | |||
if (!found) { | |||
// std::cerr << "default. misses = " << misses << "\n"; | |||
new_seconds = timer; | |||
misses = 0; | |||
} | |||
float a = 0.5f; // params[SMOOTH_PARAM].value; | |||
seconds = ((1.0f - a) * seconds + a * new_seconds); | |||
timer -= seconds; | |||
} | |||
}; | |||
timer += delta; | |||
if (seconds < 2.0e-05) { | |||
return 0.0f; | |||
} else { | |||
return 60.0f / seconds; | |||
} | |||
}; | |||
}; | |||
struct ChordDef { | |||
int number; | |||
std::string quality; | |||
int root[6]; | |||
int first[6]; | |||
int second[6]; | |||
}; | |||
struct Core { | |||
static constexpr float TRIGGER = 5e-4f; | |||
static constexpr float SEMITONE = 1.0f / 12.0f; | |||
// Reference, midi note to scale | |||
// 0 1 | |||
// 1 b2 (#1) | |||
// 2 2 | |||
// 3 b3 (#2) | |||
// 4 3 | |||
// 5 4 | |||
// 6 b5 (#4) | |||
// 7 5 | |||
// 8 b6 (#5) | |||
// 9 6 | |||
// 10 b7 (#6) | |||
// 11 7 | |||
// http://www.grantmuller.com/MidiReference/doc/midiReference/ScaleReference.html | |||
// Although their definition of the Blues scale is wrong | |||
// Added the octave note to ensure that the last note is correctly processed | |||
int ASCALE_CHROMATIC [13]= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // All of the notes | |||
int ASCALE_IONIAN [8] = {0, 2, 4, 5, 7, 9, 11, 12}; // 1,2,3,4,5,6,7 | |||
int ASCALE_DORIAN [8] = {0, 2, 3, 5, 7, 9, 10, 12}; // 1,2,b3,4,5,6,b7 | |||
int ASCALE_PHRYGIAN [8] = {0, 1, 3, 5, 7, 8, 10, 12}; // 1,b2,b3,4,5,b6,b7 | |||
int ASCALE_LYDIAN [8] = {0, 2, 4, 6, 7, 9, 10, 12}; // 1,2,3,#4,5,6,7 | |||
int ASCALE_MIXOLYDIAN [8] = {0, 2, 4, 5, 7, 9, 10, 12}; // 1,2,3,4,5,6,b7 | |||
int ASCALE_AEOLIAN [8] = {0, 2, 3, 5, 7, 8, 10, 12}; // 1,2,b3,4,5,b6,b7 | |||
int ASCALE_LOCRIAN [8] = {0, 1, 3, 5, 6, 8, 10, 12}; // 1,b2,b3,4,b5,b6,b7 | |||
int ASCALE_MAJOR_PENTA [6] = {0, 2, 4, 7, 9, 12}; // 1,2,3,5,6 | |||
int ASCALE_MINOR_PENTA [6] = {0, 3, 5, 7, 10, 12}; // 1,b3,4,5,b7 | |||
int ASCALE_HARMONIC_MINOR [8] = {0, 2, 3, 5, 7, 8, 11, 12}; // 1,2,b3,4,5,b6,7 | |||
int ASCALE_BLUES [7] = {0, 3, 5, 6, 7, 10, 12}; // 1,b3,4,b5,5,b7 | |||
enum Notes { | |||
NOTE_C = 0, | |||
NOTE_D_FLAT, // C Sharp | |||
NOTE_D, | |||
NOTE_E_FLAT, // D Sharp | |||
NOTE_E, | |||
NOTE_F, | |||
NOTE_G_FLAT, //F Sharp | |||
NOTE_G, | |||
NOTE_A_FLAT, // G Sharp | |||
NOTE_A, | |||
NOTE_B_FLAT, // A Sharp | |||
NOTE_B, | |||
NUM_NOTES | |||
}; | |||
int CIRCLE_FIFTHS [12] = { | |||
NOTE_C, | |||
NOTE_G, | |||
NOTE_D, | |||
NOTE_A, | |||
NOTE_E, | |||
NOTE_B, | |||
NOTE_G_FLAT, | |||
NOTE_D_FLAT, | |||
NOTE_A_FLAT, | |||
NOTE_E_FLAT, | |||
NOTE_B_FLAT, | |||
NOTE_F | |||
}; | |||
std::string noteNames[12] = { | |||
"C", | |||
"C#/Db", | |||
"D", | |||
"D#/Eb", | |||
"E", | |||
"F", | |||
"F#/Gb", | |||
"G", | |||
"G#/Ab", | |||
"A", | |||
"A#/Bb", | |||
"B", | |||
}; | |||
enum Scales { | |||
SCALE_CHROMATIC = 0, | |||
SCALE_IONIAN, | |||
SCALE_DORIAN, | |||
SCALE_PHRYGIAN, | |||
SCALE_LYDIAN, | |||
SCALE_MIXOLYDIAN, | |||
SCALE_AEOLIAN, | |||
SCALE_LOCRIAN, | |||
SCALE_MAJOR_PENTA, | |||
SCALE_MINOR_PENTA, | |||
SCALE_HARMONIC_MINOR, | |||
SCALE_BLUES, | |||
NUM_SCALES | |||
}; | |||
std::string scaleNames[12] = { | |||
"Chromatic", | |||
"Ionian (Major)", | |||
"Dorian", | |||
"Phrygian", | |||
"Lydian", | |||
"Mixolydian", | |||
"Aeolian (Natural Minor)", | |||
"Locrian", | |||
"Major Pentatonic", | |||
"Minor Pentatonic", | |||
"Harmonic Minor", | |||
"Blues" | |||
}; | |||
std::string intervalNames[13] { | |||
"1", | |||
"b2", | |||
"2", | |||
"b3", | |||
"3", | |||
"4", | |||
"b5", | |||
"5", | |||
"b6", | |||
"6", | |||
"b7", | |||
"7", | |||
"O" | |||
}; | |||
enum Modes { | |||
MODE_IONIAN = 0, | |||
MODE_DORIAN, | |||
MODE_PHRYGIAN, | |||
MODE_LYDIAN, | |||
MODE_MIXOLYDIAN, | |||
MODE_AEOLIAN, | |||
MODE_LOCRIAN, | |||
NUM_MODES | |||
}; | |||
std::string modeNames[7] { | |||
"Ionian (Major)", | |||
"Dorian", | |||
"Phrygian", | |||
"Lydian", | |||
"Mixolydian", | |||
"Aeolian (Natural Minor)", | |||
"Locrian" | |||
}; | |||
enum DEGREES { | |||
DEGREE_I = 0, | |||
DEGREE_II, | |||
DEGREE_III, | |||
DEGREE_IV, | |||
DEGREE_V, | |||
DEGREE_VI, | |||
DEGREE_VII, | |||
NUM_DEGREES | |||
}; | |||
std::string degreeNames[21] { // Degree * 3 + Quality | |||
"I", | |||
"i", | |||
"i°", | |||
"II", | |||
"ii", | |||
"ii°", | |||
"III", | |||
"iii", | |||
"iii°", | |||
"IV", | |||
"iv", | |||
"iv°", | |||
"V", | |||
"v", | |||
"v°", | |||
"VI", | |||
"vi", | |||
"vi°", | |||
"VII", | |||
"vii", | |||
"vii°" | |||
}; | |||
enum Inversion { | |||
ROOT, | |||
FIRST_INV, | |||
SECOND_INV, | |||
NUM_INV | |||
}; | |||
std::string inversionNames[5] { | |||
"", | |||
"(1)", | |||
"(2)" | |||
}; | |||
enum Quality { | |||
MAJ = 0, | |||
MIN, | |||
DIM, | |||
NUM_QUALITY | |||
}; | |||
std::string qualityNames[3] { | |||
"Maj", | |||
"Min", | |||
"Dim" | |||
}; | |||
double gaussrand(); | |||
int ipow(int base, int exp); | |||
bool debug = false; | |||
int poll = 100000; | |||
int stepX = 0; // debugging | |||
/* | |||
* Convert a V/OCT voltage to a quantized pitch, key and scale, and calculate various information about the quantised note. | |||
*/ | |||
float getPitchFromVolts(float inVolts, int inRoot, int inScale, int *outNote, int *outDegree); | |||
float getPitchFromVolts(float inVolts, float inRoot, float inScale, int *outRoot, int *outScale, int *outNote, int *outDegree); | |||
/* | |||
* Convert a root note (relative to C, C=0) and positive semi-tone offset from that root to a voltage (1V/OCT, 0V = C4 (or 3??)) | |||
*/ | |||
float getVoltsFromPitch(int inNote, int inRoot){ | |||
return (inRoot + inNote) * SEMITONE; | |||
} | |||
float getVoltsFromScale(int scale) { | |||
return rescale(scale, 0.0f, NUM_SCALES - 1, 0.0f, 10.0f); | |||
} | |||
int getScaleFromVolts(float volts) { | |||
return round(rescale(fabs(volts), 0.0f, 10.0f, 0.0f, NUM_SCALES - 1)); | |||
} | |||
int getModeFromVolts(float volts) { | |||
int mode = round(rescale(fabs(volts), 0.0f, 10.0f, 0.0f, NUM_SCALES - 1)); | |||
return clamp(mode - 1, 0, 6); | |||
} | |||
float getVoltsFromMode(int mode) { | |||
// Mode 0 = IONIAN, MODE 6 = LOCRIAN -> Scale 1 - 7 | |||
return rescale(mode + 1, 0.0f, NUM_SCALES - 1, 0.0f, 10.0f); | |||
} | |||
float getVoltsFromKey(int key) { | |||
return rescale(key, 0.0f, NUM_NOTES - 1, 0.0f, 10.0f); | |||
} | |||
int getKeyFromVolts(float volts) { | |||
return round(rescale(fabs(volts), 0.0f, 10.0f, 0.0f, NUM_NOTES - 1)); | |||
} | |||
void getRootFromMode(int inMode, int inRoot, int inTonic, int *currRoot, int *quality); | |||
const static int NUM_CHORDS = 99; | |||
ChordDef ChordTable[NUM_CHORDS] { | |||
{ 0 ,"None",{ -24 , -24 , -24 , -24 , -24 , -24 },{ -24 , -24 , -24 , -24 , -24 , -24 },{ -24 , -24 , -24 , -24 , -24 , -24 }}, | |||
{ 1 ,"",{ 0 , 4 , 7 , -24 , -20 , -17 },{ 12 , 4 , 7 , -12 , -20 , -17 },{ 12 , 16 , 7 , -12 , -8 , -17 }}, | |||
{ 2 ,"M#5",{ 0 , 4 , 8 , -24 , -20 , -16 },{ 12 , 4 , 8 , -12 , -20 , -16 },{ 12 , 16 , 8 , -12 , -8 , -16 }}, | |||
{ 3 ,"M#5add9",{ 0 , 4 , 8 , 14 , -24 , -20 },{ 12 , 4 , 8 , 14 , -24 , -20 },{ 12 , 16 , 8 , 14 , -12 , -20 }}, | |||
{ 4 ,"M13",{ 0 , 4 , 7 , 11 , 14 , 21 },{ 12 , 4 , 7 , 11 , 14 , 21 },{ 12 , 16 , 7 , 11 , 14 , 21 }}, | |||
{ 5 ,"M6",{ 0 , 4 , 7 , 21 , -24 , -20 },{ 12 , 4 , 7 , 21 , -24 , -20 },{ 12 , 16 , 7 , 21 , -12 , -20 }}, | |||
{ 6 ,"M6#11",{ 0 , 4 , 7 , 9 , 18 , -24 },{ 12 , 4 , 7 , 9 , 18 , -24 },{ 12 , 16 , 7 , 9 , 18 , -24 }}, | |||
{ 7 ,"M6/9",{ 0 , 4 , 7 , 9 , 14 , -24 },{ 12 , 4 , 7 , 9 , 14 , -24 },{ 12 , 16 , 7 , 9 , 14 , -24 }}, | |||
{ 8 ,"M6/9#11",{ 0 , 4 , 7 , 9 , 14 , 18 },{ 12 , 4 , 7 , 9 , 14 , 18 },{ 12 , 16 , 7 , 9 , 14 , 18 }}, | |||
{ 9 ,"M7#11",{ 0 , 4 , 7 , 11 , 18 , -24 },{ 12 , 4 , 7 , 11 , 18 , -24 },{ 12 , 16 , 7 , 11 , 18 , -24 }}, | |||
{ 10 ,"M7#5",{ 0 , 4 , 8 , 11 , -24 , -20 },{ 12 , 4 , 8 , 11 , -24 , -20 },{ 12 , 16 , 8 , 11 , -12 , -20 }}, | |||
{ 11 ,"M7#5sus4",{ 0 , 5 , 8 , 11 , -24 , -19 },{ 12 , 5 , 8 , 11 , -24 , -19 },{ 12 , 17 , 8 , 11 , -12 , -19 }}, | |||
{ 12 ,"M7#9#11",{ 0 , 4 , 7 , 11 , 15 , 18 },{ 12 , 4 , 7 , 11 , 15 , 18 },{ 12 , 16 , 7 , 11 , 15 , 18 }}, | |||
{ 13 ,"M7add13",{ 0 , 4 , 7 , 9 , 11 , 14 },{ 12 , 4 , 7 , 9 , 11 , 14 },{ 12 , 16 , 7 , 9 , 11 , 14 }}, | |||
{ 14 ,"M7b5",{ 0 , 4 , 6 , 11 , -24 , -20 },{ 12 , 4 , 6 , 11 , -24 , -20 },{ 12 , 16 , 6 , 11 , -12 , -20 }}, | |||
{ 15 ,"M7b6",{ 0 , 4 , 8 , 11 , -24 , -20 },{ 12 , 4 , 8 , 11 , -24 , -20 },{ 12 , 16 , 8 , 11 , -12 , -20 }}, | |||
{ 16 ,"M7b9",{ 0 , 4 , 7 , 11 , 13 , -20 },{ 12 , 4 , 7 , 11 , 13 , -20 },{ 12 , 16 , 7 , 11 , 13 , -20 }}, | |||
{ 17 ,"M7sus4",{ 0 , 5 , 7 , 11 , -24 , -19 },{ 12 , 5 , 7 , 11 , -24 , -19 },{ 12 , 17 , 7 , 11 , -12 , -19 }}, | |||
{ 18 ,"M9",{ 0 , 4 , 7 , 11 , 14 , -24 },{ 12 , 4 , 7 , 11 , 14 , -24 },{ 12 , 16 , 7 , 11 , 14 , -24 }}, | |||
{ 19 ,"M9#11",{ 0 , 4 , 7 , 11 , 14 , 18 },{ 12 , 4 , 7 , 11 , 14 , 18 },{ 12 , 16 , 7 , 11 , 14 , 18 }}, | |||
{ 20 ,"M9#5",{ 0 , 4 , 8 , 11 , 14 , -24 },{ 12 , 4 , 8 , 11 , 14 , -24 },{ 12 , 16 , 8 , 11 , 14 , -24 }}, | |||
{ 21 ,"M9#5sus4",{ 0 , 5 , 8 , 11 , 14 , -24 },{ 12 , 5 , 8 , 11 , 14 , -24 },{ 12 , 17 , 8 , 11 , 14 , -24 }}, | |||
{ 22 ,"M9b5",{ 0 , 4 , 6 , 11 , 14 , -24 },{ 12 , 4 , 6 , 11 , 14 , -24 },{ 12 , 16 , 6 , 11 , 14 , -24 }}, | |||
{ 23 ,"M9sus4",{ 0 , 5 , 7 , 11 , 14 , -24 },{ 12 , 5 , 7 , 11 , 14 , -24 },{ 12 , 17 , 7 , 11 , 14 , -24 }}, | |||
{ 24 ,"Madd9",{ 0 , 4 , 7 , 14 , -24 , -20 },{ 12 , 4 , 7 , 14 , -24 , -20 },{ 12 , 16 , 7 , 14 , -12 , -20 }}, | |||
{ 25 ,"Maj7",{ 0 , 4 , 7 , 11 , -24 , -20 },{ 12 , 4 , 7 , 11 , -24 , -20 },{ 12 , 16 , 7 , 11 , -12 , -20 }}, | |||
{ 26 ,"Mb5",{ 0 , 4 , 6 , -24 , -20 , -18 },{ 12 , 4 , 6 , -12 , -20 , -18 },{ 12 , 16 , 6 , -12 , -8 , -18 }}, | |||
{ 27 ,"Mb6",{ 0 , 4 , 20 , -24 , -20 , -4 },{ 12 , 4 , 20 , -12 , -20 , -4 },{ 12 , 16 , 20 , -12 , -8 , -4 }}, | |||
{ 28 ,"Msus2",{ 0 , 2 , 7 , -24 , -22 , -17 },{ 12 , 2 , 7 , -12 , -22 , -17 },{ 12 , 14 , 7 , -12 , -10 , -17 }}, | |||
{ 29 ,"Msus4",{ 0 , 5 , 7 , -24 , -19 , -17 },{ 12 , 5 , 7 , -12 , -19 , -17 },{ 12 , 17 , 7 , -12 , -7 , -17 }}, | |||
{ 30 ,"Maddb9",{ 0 , 4 , 7 , 13 , -24 , -20 },{ 12 , 4 , 7 , 13 , -24 , -20 },{ 12 , 16 , 7 , 13 , -12 , -20 }}, | |||
{ 31 ,"7",{ 0 , 4 , 7 , 10 , -24 , -20 },{ 12 , 4 , 7 , 10 , -24 , -20 },{ 12 , 16 , 7 , 10 , -12 , -20 }}, | |||
{ 32 ,"9",{ 0 , 4 , 7 , 10 , 14 , -24 },{ 12 , 4 , 7 , 10 , 14 , -24 },{ 12 , 16 , 7 , 10 , 14 , -24 }}, | |||
{ 33 ,"11",{ 0 , 7 , 10 , 14 , 17 , -24 },{ 12 , 7 , 10 , 14 , 17 , -24 },{ 12 , 19 , 10 , 14 , 17 , -24 }}, | |||
{ 34 ,"13",{ 0 , 4 , 7 , 10 , 14 , 21 },{ 12 , 4 , 7 , 10 , 14 , 21 },{ 12 , 16 , 7 , 10 , 14 , 21 }}, | |||
{ 35 ,"11b9",{ 0 , 7 , 10 , 13 , 17 , -24 },{ 12 , 7 , 10 , 13 , 17 , -24 },{ 12 , 19 , 10 , 13 , 17 , -24 }}, | |||
{ 36 ,"13#9",{ 0 , 4 , 7 , 10 , 15 , 21 },{ 12 , 4 , 7 , 10 , 15 , 21 },{ 12 , 16 , 7 , 10 , 15 , 21 }}, | |||
{ 37 ,"13b5",{ 0 , 4 , 6 , 9 , 10 , 14 },{ 12 , 4 , 6 , 9 , 10 , 14 },{ 12 , 16 , 6 , 9 , 10 , 14 }}, | |||
{ 38 ,"13b9",{ 0 , 4 , 7 , 10 , 13 , 21 },{ 12 , 4 , 7 , 10 , 13 , 21 },{ 12 , 16 , 7 , 10 , 13 , 21 }}, | |||
{ 39 ,"13no5",{ 0 , 4 , 10 , 14 , 21 , -24 },{ 12 , 4 , 10 , 14 , 21 , -24 },{ 12 , 16 , 10 , 14 , 21 , -24 }}, | |||
{ 40 ,"13sus4",{ 0 , 5 , 7 , 10 , 14 , 21 },{ 12 , 5 , 7 , 10 , 14 , 21 },{ 12 , 17 , 7 , 10 , 14 , 21 }}, | |||
{ 41 ,"69#11",{ 0 , 4 , 7 , 9 , 14 , 18 },{ 12 , 4 , 7 , 9 , 14 , 18 },{ 12 , 16 , 7 , 9 , 14 , 18 }}, | |||
{ 42 ,"7#11",{ 0 , 4 , 7 , 10 , 18 , -24 },{ 12 , 4 , 7 , 10 , 18 , -24 },{ 12 , 16 , 7 , 10 , 18 , -24 }}, | |||
{ 43 ,"7#11b13",{ 0 , 4 , 7 , 10 , 18 , 20 },{ 12 , 4 , 7 , 10 , 18 , 20 },{ 12 , 16 , 7 , 10 , 18 , 20 }}, | |||
{ 44 ,"7#5",{ 0 , 4 , 8 , 10 , -24 , -20 },{ 12 , 4 , 8 , 10 , -24 , -20 },{ 12 , 16 , 8 , 10 , -12 , -20 }}, | |||
{ 45 ,"7#5#9",{ 0 , 4 , 8 , 10 , 15 , -24 },{ 12 , 4 , 8 , 10 , 15 , -24 },{ 12 , 16 , 8 , 10 , 15 , -24 }}, | |||
{ 46 ,"7#5b9",{ 0 , 4 , 8 , 10 , 13 , -24 },{ 12 , 4 , 8 , 10 , 13 , -24 },{ 12 , 16 , 8 , 10 , 13 , -24 }}, | |||
{ 47 ,"7#5b9#11",{ 0 , 4 , 8 , 10 , 13 , 18 },{ 12 , 4 , 8 , 10 , 13 , 18 },{ 12 , 16 , 8 , 10 , 13 , 18 }}, | |||
{ 48 ,"7#5sus4",{ 0 , 5 , 8 , 10 , -24 , -19 },{ 12 , 5 , 8 , 10 , -24 , -19 },{ 12 , 17 , 8 , 10 , -12 , -19 }}, | |||
{ 49 ,"7#9",{ 0 , 4 , 7 , 10 , 15 , -24 },{ 12 , 4 , 7 , 10 , 15 , -24 },{ 12 , 16 , 7 , 10 , 15 , -24 }}, | |||
{ 50 ,"7#9#11",{ 0 , 4 , 7 , 10 , 15 , 18 },{ 12 , 4 , 7 , 10 , 15 , 18 },{ 12 , 16 , 7 , 10 , 15 , 18 }}, | |||
{ 51 ,"7#9b13",{ 0 , 4 , 7 , 10 , 15 , 20 },{ 12 , 4 , 7 , 10 , 15 , 20 },{ 12 , 16 , 7 , 10 , 15 , 20 }}, | |||
{ 52 ,"7add6",{ 0 , 4 , 7 , 10 , 21 , -24 },{ 12 , 4 , 7 , 10 , 21 , -24 },{ 12 , 16 , 7 , 10 , 21 , -24 }}, | |||
{ 53 ,"7b13",{ 0 , 4 , 10 , 20 , -24 , -20 },{ 12 , 4 , 10 , 20 , -24 , -20 },{ 12 , 16 , 10 , 20 , -12 , -20 }}, | |||
{ 54 ,"7b5",{ 0 , 4 , 6 , 10 , -24 , -20 },{ 12 , 4 , 6 , 10 , -24 , -20 },{ 12 , 16 , 6 , 10 , -12 , -20 }}, | |||
{ 55 ,"7b6",{ 0 , 4 , 7 , 8 , 10 , -24 },{ 12 , 4 , 7 , 8 , 10 , -24 },{ 12 , 16 , 7 , 8 , 10 , -24 }}, | |||
{ 56 ,"7b9",{ 0 , 4 , 7 , 10 , 13 , -24 },{ 12 , 4 , 7 , 10 , 13 , -24 },{ 12 , 16 , 7 , 10 , 13 , -24 }}, | |||
{ 57 ,"7b9#11",{ 0 , 4 , 7 , 10 , 13 , 18 },{ 12 , 4 , 7 , 10 , 13 , 18 },{ 12 , 16 , 7 , 10 , 13 , 18 }}, | |||
{ 58 ,"7b9#9",{ 0 , 4 , 7 , 10 , 13 , 15 },{ 12 , 4 , 7 , 10 , 13 , 15 },{ 12 , 16 , 7 , 10 , 13 , 15 }}, | |||
{ 59 ,"7b9b13",{ 0 , 4 , 7 , 10 , 13 , 20 },{ 12 , 4 , 7 , 10 , 13 , 20 },{ 12 , 16 , 7 , 10 , 13 , 20 }}, | |||
{ 60 ,"7no5",{ 0 , 4 , 10 , -24 , -20 , -14 },{ 12 , 4 , 10 , -12 , -20 , -14 },{ 12 , 16 , 10 , -12 , -8 , -14 }}, | |||
{ 61 ,"7sus4",{ 0 , 5 , 7 , 10 , -24 , -19 },{ 12 , 5 , 7 , 10 , -24 , -19 },{ 12 , 17 , 7 , 10 , -12 , -19 }}, | |||
{ 62 ,"7sus4b9",{ 0 , 5 , 7 , 10 , 13 , -24 },{ 12 , 5 , 7 , 10 , 13 , -24 },{ 12 , 17 , 7 , 10 , 13 , -24 }}, | |||
{ 63 ,"7sus4b9b13",{ 0 , 5 , 7 , 10 , 13 , 20 },{ 12 , 5 , 7 , 10 , 13 , 20 },{ 12 , 17 , 7 , 10 , 13 , 20 }}, | |||
{ 64 ,"9#11",{ 0 , 4 , 7 , 10 , 14 , 18 },{ 12 , 4 , 7 , 10 , 14 , 18 },{ 12 , 16 , 7 , 10 , 14 , 18 }}, | |||
{ 65 ,"9#5",{ 0 , 4 , 8 , 10 , 14 , -24 },{ 12 , 4 , 8 , 10 , 14 , -24 },{ 12 , 16 , 8 , 10 , 14 , -24 }}, | |||
{ 66 ,"9#5#11",{ 0 , 4 , 8 , 10 , 14 , 18 },{ 12 , 4 , 8 , 10 , 14 , 18 },{ 12 , 16 , 8 , 10 , 14 , 18 }}, | |||
{ 67 ,"9b13",{ 0 , 4 , 10 , 14 , 20 , -24 },{ 12 , 4 , 10 , 14 , 20 , -24 },{ 12 , 16 , 10 , 14 , 20 , -24 }}, | |||
{ 68 ,"9b5",{ 0 , 4 , 6 , 10 , 14 , -24 },{ 12 , 4 , 6 , 10 , 14 , -24 },{ 12 , 16 , 6 , 10 , 14 , -24 }}, | |||
{ 69 ,"9no5",{ 0 , 4 , 10 , 14 , -24 , -20 },{ 12 , 4 , 10 , 14 , -24 , -20 },{ 12 , 16 , 10 , 14 , -12 , -20 }}, | |||
{ 70 ,"9sus4",{ 0 , 5 , 7 , 10 , 14 , -24 },{ 12 , 5 , 7 , 10 , 14 , -24 },{ 12 , 17 , 7 , 10 , 14 , -24 }}, | |||
{ 71 ,"m",{ 0 , 3 , 7 , -24 , -21 , -17 },{ 12 , 3 , 7 , -12 , -21 , -17 },{ 12 , 15 , 7 , -12 , -9 , -17 }}, | |||
{ 72 ,"m#5",{ 0 , 3 , 8 , -24 , -21 , -16 },{ 12 , 3 , 8 , -12 , -21 , -16 },{ 12 , 15 , 8 , -12 , -9 , -16 }}, | |||
{ 73 ,"m11",{ 0 , 3 , 7 , 10 , 14 , 17 },{ 12 , 3 , 7 , 10 , 14 , 17 },{ 12 , 15 , 7 , 10 , 14 , 17 }}, | |||
{ 74 ,"m11A5",{ 0 , 3 , 8 , 10 , 14 , 17 },{ 12 , 3 , 8 , 10 , 14 , 17 },{ 12 , 15 , 8 , 10 , 14 , 17 }}, | |||
{ 75 ,"m11b5",{ 0 , 3 , 10 , 14 , 17 , 18 },{ 12 , 3 , 10 , 14 , 17 , 18 },{ 12 , 15 , 10 , 14 , 17 , 18 }}, | |||
{ 76 ,"m6",{ 0 , 3 , 5 , 7 , 21 , -24 },{ 12 , 3 , 5 , 7 , 21 , -24 },{ 12 , 15 , 5 , 7 , 21 , -24 }}, | |||
{ 77 ,"m69",{ 0 , 3 , 7 , 9 , 14 , -24 },{ 12 , 3 , 7 , 9 , 14 , -24 },{ 12 , 15 , 7 , 9 , 14 , -24 }}, | |||
{ 78 ,"m7",{ 0 , 3 , 7 , 10 , -24 , -21 },{ 12 , 3 , 7 , 10 , -24 , -21 },{ 12 , 15 , 7 , 10 , -12 , -21 }}, | |||
{ 79 ,"m7#5",{ 0 , 3 , 8 , 10 , -24 , -21 },{ 12 , 3 , 8 , 10 , -24 , -21 },{ 12 , 15 , 8 , 10 , -12 , -21 }}, | |||
{ 80 ,"m7add11",{ 0 , 3 , 7 , 10 , 17 , -24 },{ 12 , 3 , 7 , 10 , 17 , -24 },{ 12 , 15 , 7 , 10 , 17 , -24 }}, | |||
{ 81 ,"m7b5",{ 0 , 3 , 6 , 10 , -24 , -21 },{ 12 , 3 , 6 , 10 , -24 , -21 },{ 12 , 15 , 6 , 10 , -12 , -21 }}, | |||
{ 82 ,"m9",{ 0 , 3 , 7 , 10 , 14 , -24 },{ 12 , 3 , 7 , 10 , 14 , -24 },{ 12 , 15 , 7 , 10 , 14 , -24 }}, | |||
{ 83 ,"#5",{ 0 , 3 , 8 , 10 , 14 , -24 },{ 12 , 3 , 8 , 10 , 14 , -24 },{ 12 , 15 , 8 , 10 , 14 , -24 }}, | |||
{ 84 ,"m9b5",{ 0 , 3 , 10 , 14 , 18 , -24 },{ 12 , 3 , 10 , 14 , 18 , -24 },{ 12 , 15 , 10 , 14 , 18 , -24 }}, | |||
{ 85 ,"mMaj7",{ 0 , 3 , 7 , 11 , -24 , -21 },{ 12 , 3 , 7 , 11 , -24 , -21 },{ 12 , 15 , 7 , 11 , -12 , -21 }}, | |||
{ 86 ,"mMaj7b6",{ 0 , 3 , 7 , 8 , 11 , -24 },{ 12 , 3 , 7 , 8 , 11 , -24 },{ 12 , 15 , 7 , 8 , 11 , -24 }}, | |||
{ 87 ,"mM9",{ 0 , 3 , 7 , 11 , 14 , -24 },{ 12 , 3 , 7 , 11 , 14 , -24 },{ 12 , 15 , 7 , 11 , 14 , -24 }}, | |||
{ 88 ,"mM9b6",{ 0 , 3 , 7 , 8 , 11 , 14 },{ 12 , 3 , 7 , 8 , 11 , 14 },{ 12 , 15 , 7 , 8 , 11 , 14 }}, | |||
{ 89 ,"mb6M7",{ 0 , 3 , 8 , 11 , -24 , -21 },{ 12 , 3 , 8 , 11 , -24 , -21 },{ 12 , 15 , 8 , 11 , -12 , -21 }}, | |||
{ 90 ,"mb6b9",{ 0 , 3 , 8 , 13 , -24 , -21 },{ 12 , 3 , 8 , 13 , -24 , -21 },{ 12 , 15 , 8 , 13 , -12 , -21 }}, | |||
{ 91 ,"dim",{ 0 , 3 , 6 , -24 , -21 , -18 },{ 12 , 3 , 6 , -12 , -21 , -18 },{ 12 , 15 , 6 , -12 , -9 , -18 }}, | |||
{ 92 ,"dim7",{ 0 , 3 , 6 , 21 , -24 , -21 },{ 12 , 3 , 6 , 21 , -24 , -21 },{ 12 , 15 , 6 , 21 , -12 , -21 }}, | |||
{ 93 ,"dim7M7",{ 0 , 3 , 6 , 9 , 11 , -24 },{ 12 , 3 , 6 , 9 , 11 , -24 },{ 12 , 15 , 6 , 9 , 11 , -24 }}, | |||
{ 94 ,"dimM7",{ 0 , 3 , 6 , 11 , -24 , -21 },{ 12 , 3 , 6 , 11 , -24 , -21 },{ 12 , 15 , 6 , 11 , -12 , -21 }}, | |||
{ 95 ,"sus24",{ 0 , 2 , 5 , 7 , -24 , -22 },{ 12 , 2 , 5 , 7 , -24 , -22 },{ 12 , 14 , 5 , 7 , -12 , -22 }}, | |||
{ 96 ,"augadd#9",{ 0 , 4 , 8 , 15 , -24 , -20 },{ 12 , 4 , 8 , 15 , -24 , -20 },{ 12 , 16 , 8 , 15 , -12 , -20 }}, | |||
{ 97 ,"madd4",{ 0 , 3 , 5 , 7 , -24 , -21 },{ 12 , 3 , 5 , 7 , -24 , -21 },{ 12 , 15 , 5 , 7 , -12 , -21 }}, | |||
{ 98 ,"madd9",{ 0 , 3 , 7 , 14 , -24 , -21 },{ 12 , 3 , 7 , 14 , -24 , -21 },{ 12 , 15 , 7 , 14 , -12 , -21 }}, | |||
}; | |||
int ModeQuality[7][7] { | |||
{MAJ,MIN,MIN,MAJ,MAJ,MIN,DIM}, // Ionian | |||
{MIN,MIN,MAJ,MAJ,MIN,DIM,MAJ}, // Dorian | |||
{MIN,MAJ,MAJ,MIN,DIM,MAJ,MIN}, // Phrygian | |||
{MAJ,MAJ,MIN,DIM,MAJ,MIN,MIN}, // Lydian | |||
{MAJ,MIN,DIM,MAJ,MIN,MIN,MAJ}, // Mixolydian | |||
{MIN,DIM,MAJ,MIN,MIN,MAJ,MAJ}, // Aeolian | |||
{DIM,MAJ,MIN,MIN,MAJ,MAJ,MIN} // Locrian | |||
}; | |||
int ModeOffset[7][7] { | |||
{0,0,0,0,0,0,0}, // Ionian | |||
{0,0,-1,-1,0,0,-1}, // Dorian | |||
{0,-1,-1,0,0,-1,-1}, // Phrygian | |||
{0,0,0,1,0,0,0}, // Lydian | |||
{0,0,0,0,0,0,-1}, // Mixolydian | |||
{0,0,-1,0,0,-1,-1}, // Aeolian | |||
{0,-1,-1,0,-1,-1,-1} // Locrian | |||
}; | |||
// NOTE_C = 0, | |||
// NOTE_D_FLAT, // C Sharp | |||
// NOTE_D, | |||
// NOTE_E_FLAT, // D Sharp | |||
// NOTE_E, | |||
// NOTE_F, | |||
// NOTE_G_FLAT, //F Sharp | |||
// NOTE_G, | |||
// NOTE_A_FLAT, // G Sharp | |||
// NOTE_A, | |||
// NOTE_B_FLAT, // A Sharp | |||
// NOTE_B, | |||
//0 1 2 3 4 5 6 7 8 9 10 11 12 | |||
int tonicIndex[13] {1, 3, 5, 0, 2, 4, 6, 1, 3, 5, 0, 2, 4}; | |||
int scaleIndex[7] {5, 3, 1, 6, 4, 2, 0}; | |||
int noteIndex[13] { | |||
NOTE_G_FLAT, | |||
NOTE_D_FLAT, | |||
NOTE_A_FLAT, | |||
NOTE_E_FLAT, | |||
NOTE_B_FLAT, | |||
NOTE_F, | |||
NOTE_C, | |||
NOTE_G, | |||
NOTE_D, | |||
NOTE_A, | |||
NOTE_E, | |||
NOTE_B, | |||
NOTE_G_FLAT}; | |||
}; | |||
Core & CoreUtil(); | |||
} // namespace rack_plugin_AmalgamatedHarmonics |
@@ -0,0 +1,213 @@ | |||
#include "dsp/digital.hpp" | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct Imperfect : AHModule { | |||
enum ParamIds { | |||
DELAY_PARAM, | |||
DELAYSPREAD_PARAM = DELAY_PARAM + 8, | |||
LENGTH_PARAM = DELAYSPREAD_PARAM + 8, | |||
LENGTHSPREAD_PARAM = LENGTH_PARAM + 8, | |||
DIVISION_PARAM = LENGTHSPREAD_PARAM + 8, | |||
NUM_PARAMS = DIVISION_PARAM + 8 | |||
}; | |||
enum InputIds { | |||
TRIG_INPUT, | |||
NUM_INPUTS = TRIG_INPUT + 8 | |||
}; | |||
enum OutputIds { | |||
OUT_OUTPUT, | |||
NUM_OUTPUTS = OUT_OUTPUT + 8 | |||
}; | |||
enum LightIds { | |||
OUT_LIGHT, | |||
NUM_LIGHTS = OUT_LIGHT + 16 | |||
}; | |||
Imperfect() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
reset(); | |||
} | |||
void step() override; | |||
void reset() override { | |||
for (int i = 0; i < 8; i++) { | |||
delayState[i] = false; | |||
gateState[i] = false; | |||
delayTime[i] = 0.0; | |||
gateTime[i] = 0.0; | |||
} | |||
} | |||
Core core; | |||
bool delayState[8]; | |||
bool gateState[8]; | |||
float delayTime[8]; | |||
float gateTime[8]; | |||
PulseGenerator delayPhase[8]; | |||
PulseGenerator gatePhase[8]; | |||
SchmittTrigger inTrigger[8]; | |||
int counter[8]; | |||
}; | |||
void Imperfect::step() { | |||
stepX++; | |||
float dlyLen; | |||
float dlySpr; | |||
float gateLen; | |||
float gateSpr; | |||
int lastValidInput = -1; | |||
for (int i = 0; i < 8; i++) { | |||
bool generateSignal = false; | |||
bool inputActive = inputs[TRIG_INPUT + i].active; | |||
bool haveTrigger = inTrigger[i].process(inputs[TRIG_INPUT + i].value); | |||
bool outputActive = outputs[OUT_OUTPUT + i].active; | |||
// If we have an active input, we should forget about previous valid inputs | |||
if (inputActive) { | |||
lastValidInput = -1; | |||
if (haveTrigger) { | |||
if (debugEnabled()) { std::cout << stepX << " " << i << " has active input and has received trigger" << std::endl; } | |||
generateSignal = true; | |||
lastValidInput = i; | |||
} | |||
} else { | |||
// We have an output and previously seen a trigger | |||
if (outputActive && lastValidInput > -1) { | |||
if (debugEnabled()) { std::cout << stepX << " " << i << " has active out and has seen trigger on " << lastValidInput << std::endl; } | |||
generateSignal = true; | |||
} | |||
} | |||
if (generateSignal) { | |||
counter[i]++; | |||
int target = core.ipow(2,params[DIVISION_PARAM + i].value); | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Div: " << i << ": Target: " << target << " Cnt: " << counter[lastValidInput] << " Exp: " << counter[lastValidInput] % target << std::endl; | |||
} | |||
if (counter[lastValidInput] % target == 0) { | |||
dlyLen = log2(params[DELAY_PARAM + i].value); | |||
dlySpr = log2(params[DELAYSPREAD_PARAM + i].value); | |||
gateLen = log2(params[LENGTH_PARAM + i].value); | |||
gateSpr = log2(params[LENGTHSPREAD_PARAM + i].value); | |||
// Determine delay and gate times for all active outputs | |||
double rndD = clamp(core.gaussrand(), -2.0f, 2.0f); | |||
delayTime[i] = clamp(dlyLen + dlySpr * rndD, 0.0f, 100.0f); | |||
// The modified gate time cannot be earlier than the start of the delay | |||
double rndG = clamp(core.gaussrand(), -2.0f, 2.0f); | |||
gateTime[i] = clamp(gateLen + gateSpr * rndG, 0.02f, 100.0f); | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Delay: " << i << ": Len: " << dlyLen << " Spr: " << dlySpr << " r: " << rndD << " = " << delayTime[i] << std::endl; | |||
std::cout << stepX << " Gate: " << i << ": Len: " << gateLen << ", Spr: " << gateSpr << " r: " << rndG << " = " << gateTime[i] << std::endl; | |||
} | |||
// Trigger the respective delay pulse generators | |||
delayState[i] = true; | |||
delayPhase[i].trigger(delayTime[i]); | |||
} | |||
} | |||
} | |||
for (int i = 0; i < 8; i++) { | |||
if (delayState[i] && !delayPhase[i].process(delta)) { | |||
gatePhase[i].trigger(gateTime[i]); | |||
gateState[i] = true; | |||
delayState[i] = false; | |||
} | |||
lights[OUT_LIGHT + i * 2].value = 0.0; | |||
lights[OUT_LIGHT + i * 2 + 1].value = 0.0; | |||
if (gatePhase[i].process(delta)) { | |||
outputs[OUT_OUTPUT + i].value = 10.0; | |||
lights[OUT_LIGHT + i * 2].value = 1.0; | |||
lights[OUT_LIGHT + i * 2 + 1].value = 0.0; | |||
} else { | |||
outputs[OUT_OUTPUT + i].value = 0.0; | |||
gateState[i] = false; | |||
if (delayState[i]) { | |||
lights[OUT_LIGHT + i * 2].value = 0.0; | |||
lights[OUT_LIGHT + i * 2 + 1].value = 1.0; | |||
} | |||
} | |||
} | |||
} | |||
struct ImperfectWidget : ModuleWidget { | |||
ImperfectWidget(Imperfect *module); | |||
}; | |||
ImperfectWidget::ImperfectWidget(Imperfect *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(315, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/Imperfect.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
for (int i = 0; i < 8; i++) { | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, i + 1, true, true), Port::INPUT, module, Imperfect::TRIG_INPUT + i)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 1, i + 1, true, true), module, Imperfect::DELAY_PARAM + i, 1.0, 2.0, 1.0)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 2, i + 1, true, true), module, Imperfect::DELAYSPREAD_PARAM + i, 1.0, 2.0, 1.0)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 3, i + 1, true, true), module, Imperfect::LENGTH_PARAM + i, 1.001, 2.0, 1.001)); // Always produce gate | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 4, i + 1, true, true), module, Imperfect::LENGTHSPREAD_PARAM + i, 1.0, 2.0, 1.0)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 5, i + 1, true, true), module, Imperfect::DIVISION_PARAM + i, 0, 8, 0)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(ui.getPosition(UI::LIGHT, 6, i + 1, true, true), module, Imperfect::OUT_LIGHT + i * 2)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 7, i + 1, true, true), Port::OUTPUT, module, Imperfect::OUT_OUTPUT + i)); | |||
} | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Imperfect) { | |||
Model *modelImperfect = Model::create<Imperfect, ImperfectWidget>( "Amalgamated Harmonics", "Imperfect", "Imperfect (deprecated)", UTILITY_TAG); | |||
return modelImperfect; | |||
} | |||
@@ -0,0 +1,416 @@ | |||
#include "dsp/digital.hpp" | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct Imperfect2 : AHModule { | |||
enum ParamIds { | |||
ENUMS(DELAY_PARAM,4), | |||
ENUMS(DELAYSPREAD_PARAM,4), | |||
ENUMS(LENGTH_PARAM,4), | |||
ENUMS(LENGTHSPREAD_PARAM,4), | |||
ENUMS(DIVISION_PARAM,4), | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
ENUMS(TRIG_INPUT,4), | |||
ENUMS(DELAY_INPUT,4), | |||
ENUMS(DELAYSPREAD_INPUT,4), | |||
ENUMS(LENGTH_INPUT,4), | |||
ENUMS(LENGTHSPREAD_INPUT,4), | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
ENUMS(OUT_OUTPUT,4), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(OUT_LIGHT,8), | |||
NUM_LIGHTS | |||
}; | |||
Imperfect2() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
reset(); | |||
} | |||
void step() override; | |||
void reset() override { | |||
for (int i = 0; i < 4; i++) { | |||
delayState[i] = false; | |||
gateState[i] = false; | |||
delayTime[i] = 0.0; | |||
gateTime[i] = 0.0; | |||
bpm[i] = 0.0; | |||
} | |||
} | |||
Core core; | |||
bool delayState[4]; | |||
bool gateState[4]; | |||
float delayTime[4]; | |||
int delayTimeMs[4]; | |||
int delaySprMs[4]; | |||
float gateTime[4]; | |||
int gateTimeMs[4]; | |||
int gateSprMs[4]; | |||
float bpm[4]; | |||
int division[4]; | |||
int actDelayMs[4] = {0, 0, 0, 0}; | |||
int actGateMs[4] = {0, 0, 0, 0}; | |||
AHPulseGenerator delayPhase[4]; | |||
AHPulseGenerator gatePhase[4]; | |||
SchmittTrigger inTrigger[4]; | |||
int counter[4]; | |||
BpmCalculator bpmCalc[4]; | |||
}; | |||
void Imperfect2::step() { | |||
stepX++; | |||
float dlyLen; | |||
float dlySpr; | |||
float gateLen; | |||
float gateSpr; | |||
int lastValidInput = -1; | |||
for (int i = 0; i < 4; i++) { | |||
bool generateSignal = false; | |||
bool inputActive = inputs[TRIG_INPUT + i].active; | |||
bool haveTrigger = inTrigger[i].process(inputs[TRIG_INPUT + i].value); | |||
bool outputActive = outputs[OUT_OUTPUT + i].active; | |||
// This is where we manage row-chaining/normalisation, i.e a row can be active without an | |||
// input by receiving the input clock from a previous (higher) row | |||
// If we have an active input, we should forget about previous valid inputs | |||
if (inputActive) { | |||
bpm[i] = bpmCalc[i].calculateBPM(delta, inputs[TRIG_INPUT + i].value); | |||
lastValidInput = -1; | |||
if (haveTrigger) { | |||
if (debugEnabled()) { std::cout << stepX << " " << i << " has active input and has received trigger" << std::endl; } | |||
generateSignal = true; | |||
lastValidInput = i; // Row i has a valid input | |||
} | |||
} else { | |||
// We have an output plugged in this row and previously seen a trigger on previous row | |||
if (outputActive && lastValidInput > -1) { | |||
if (debugEnabled()) { std::cout << stepX << " " << i << " has active out and has seen trigger on " << lastValidInput << std::endl; } | |||
generateSignal = true; | |||
} | |||
bpm[i] = 0.0; | |||
} | |||
if (inputs[DELAY_INPUT + i].active) { | |||
dlyLen = log2(fabs(inputs[DELAY_INPUT + i].value) + 1.0f); | |||
} else { | |||
dlyLen = log2(params[DELAY_PARAM + i].value); | |||
} | |||
if (inputs[DELAYSPREAD_INPUT + i].active) { | |||
dlySpr = log2(fabs(inputs[DELAYSPREAD_INPUT + i].value) + 1.0f); | |||
} else { | |||
dlySpr = log2(params[DELAYSPREAD_PARAM + i].value); | |||
} | |||
if (inputs[LENGTH_INPUT + i].active) { | |||
gateLen = log2(fabs(inputs[LENGTH_INPUT + i].value) + 1.001f); | |||
} else { | |||
gateLen = log2(params[LENGTH_PARAM + i].value); | |||
} | |||
if (inputs[LENGTHSPREAD_INPUT + i].active) { | |||
gateSpr = log2(fabs(inputs[LENGTHSPREAD_INPUT + i].value) + 1.0f); | |||
} else { | |||
gateSpr = log2(params[LENGTHSPREAD_PARAM + i].value); | |||
} | |||
division[i] = params[DIVISION_PARAM + i].value; | |||
delayTimeMs[i] = dlyLen * 1000; | |||
delaySprMs[i] = dlySpr * 2000; // scaled by ±2 below | |||
gateTimeMs[i] = gateLen * 1000; | |||
gateSprMs[i] = gateSpr * 2000; // scaled by ±2 below | |||
if (generateSignal) { | |||
counter[i]++; | |||
int target = division[i]; | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Div: " << i << ": Target: " << target << " Cnt: " << counter[lastValidInput] << " Exp: " << counter[lastValidInput] % division[i] << std::endl; | |||
} | |||
if (counter[lastValidInput] % target == 0) { | |||
// check that we are not in the gate phase | |||
if (!gatePhase[i].ishigh() && !delayPhase[i].ishigh()) { | |||
// Determine delay and gate times for all active outputs | |||
double rndD = clamp(core.gaussrand(), -2.0f, 2.0f); | |||
delayTime[i] = clamp(dlyLen + dlySpr * rndD, 0.0f, 100.0f); | |||
// The modified gate time cannot be earlier than the start of the delay | |||
double rndG = clamp(core.gaussrand(), -2.0f, 2.0f); | |||
gateTime[i] = clamp(gateLen + gateSpr * rndG, Core::TRIGGER, 100.0f); | |||
if (debugEnabled()) { | |||
std::cout << stepX << " Delay: " << i << ": Len: " << dlyLen << " Spr: " << dlySpr << " r: " << rndD << " = " << delayTime[i] << std::endl; | |||
std::cout << stepX << " Gate: " << i << ": Len: " << gateLen << ", Spr: " << gateSpr << " r: " << rndG << " = " << gateTime[i] << std::endl; | |||
} | |||
// Trigger the respective delay pulse generator | |||
delayState[i] = true; | |||
if (delayPhase[i].trigger(delayTime[i])) { | |||
actDelayMs[i] = delayTime[i] * 1000; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
for (int i = 0; i < 4; i++) { | |||
if (delayState[i] && !delayPhase[i].process(delta)) { | |||
if (gatePhase[i].trigger(gateTime[i])) { | |||
actGateMs[i] = gateTime[i] * 1000; | |||
} | |||
gateState[i] = true; | |||
delayState[i] = false; | |||
} | |||
lights[OUT_LIGHT + i * 2].value = 0.0f; | |||
lights[OUT_LIGHT + i * 2 + 1].value = 0.0f; | |||
if (gatePhase[i].process(delta)) { | |||
outputs[OUT_OUTPUT + i].value = 10.0f; | |||
lights[OUT_LIGHT + i * 2].value = 1.0f; | |||
lights[OUT_LIGHT + i * 2 + 1].value = 0.0f; | |||
} else { | |||
outputs[OUT_OUTPUT + i].value = 0.0f; | |||
gateState[i] = false; | |||
if (delayState[i]) { | |||
lights[OUT_LIGHT + i * 2].value = 0.0f; | |||
lights[OUT_LIGHT + i * 2 + 1].value = 1.0f; | |||
} | |||
} | |||
} | |||
} | |||
struct Imperfect2Box : TransparentWidget { | |||
Imperfect2 *module; | |||
int frame = 0; | |||
std::shared_ptr<Font> font; | |||
float *bpm; | |||
int *dly; | |||
int *dlySpr; | |||
int *gate; | |||
int *gateSpr; | |||
int *division; | |||
int *actDly; | |||
int *actGate; | |||
Imperfect2Box() { | |||
font = Font::load(assetPlugin(plugin, "res/DSEG14ClassicMini-BoldItalic.ttf")); | |||
} | |||
void draw(NVGcontext *vg) override { | |||
Vec pos = Vec(0, 15); | |||
nvgFontSize(vg, 10); | |||
nvgFontFaceId(vg, font->handle); | |||
nvgTextLetterSpacing(vg, -1); | |||
nvgTextAlign(vg, NVGalign::NVG_ALIGN_CENTER); | |||
nvgFillColor(vg, nvgRGBA(255, 0, 0, 0xff)); | |||
char text[10]; | |||
if (*bpm == 0.0f) { | |||
snprintf(text, sizeof(text), "-"); | |||
} else { | |||
snprintf(text, sizeof(text), "%.1f", *bpm); | |||
} | |||
nvgText(vg, pos.x + 20, pos.y, text, NULL); | |||
snprintf(text, sizeof(text), "%d", *dly); | |||
nvgText(vg, pos.x + 74, pos.y, text, NULL); | |||
if (*dlySpr != 0) { | |||
snprintf(text, sizeof(text), "%d", *dlySpr); | |||
nvgText(vg, pos.x + 144, pos.y, text, NULL); | |||
} | |||
snprintf(text, sizeof(text), "%d", *gate); | |||
nvgText(vg, pos.x + 214, pos.y, text, NULL); | |||
if (*gateSpr != 0) { | |||
snprintf(text, sizeof(text), "%d", *gateSpr); | |||
nvgText(vg, pos.x + 284, pos.y, text, NULL); | |||
} | |||
snprintf(text, sizeof(text), "%d", *division); | |||
nvgText(vg, pos.x + 334, pos.y, text, NULL); | |||
nvgFillColor(vg, nvgRGBA(0, 0, 0, 0xff)); | |||
snprintf(text, sizeof(text), "%d", *actDly); | |||
nvgText(vg, pos.x + 372, pos.y, text, NULL); | |||
snprintf(text, sizeof(text), "%d", *actGate); | |||
nvgText(vg, pos.x + 408, pos.y, text, NULL); | |||
} | |||
}; | |||
struct Imperfect2Widget : ModuleWidget { | |||
Imperfect2Widget(Imperfect2 *module); | |||
}; | |||
Imperfect2Widget::Imperfect2Widget(Imperfect2 *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(450, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/Imperfect2.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
{ | |||
Imperfect2Box *display = new Imperfect2Box(); | |||
display->module = module; | |||
display->box.pos = Vec(10, 95); | |||
display->box.size = Vec(200, 20); | |||
display->bpm = &(module->bpm[0]); | |||
display->dly = &(module->delayTimeMs[0]); | |||
display->dlySpr = &(module->delaySprMs[0]); | |||
display->gate = &(module->gateTimeMs[0]); | |||
display->gateSpr = &(module->gateSprMs[0]); | |||
display->division = &(module->division[0]); | |||
display->actDly = &(module->actDelayMs[0]); | |||
display->actGate = &(module->actGateMs[0]); | |||
addChild(display); | |||
} | |||
{ | |||
Imperfect2Box *display = new Imperfect2Box(); | |||
display->module = module; | |||
display->box.pos = Vec(10, 165); | |||
display->box.size = Vec(200, 20); | |||
display->bpm = &(module->bpm[1]); | |||
display->dly = &(module->delayTimeMs[1]); | |||
display->dlySpr = &(module->delaySprMs[1]); | |||
display->gate = &(module->gateTimeMs[1]); | |||
display->gateSpr = &(module->gateSprMs[1]); | |||
display->division = &(module->division[1]); | |||
display->actDly = &(module->actDelayMs[1]); | |||
display->actGate = &(module->actGateMs[1]); | |||
addChild(display); | |||
} | |||
{ | |||
Imperfect2Box *display = new Imperfect2Box(); | |||
display->module = module; | |||
display->box.pos = Vec(10, 235); | |||
display->box.size = Vec(200, 20); | |||
display->bpm = &(module->bpm[2]); | |||
display->dly = &(module->delayTimeMs[2]); | |||
display->dlySpr = &(module->delaySprMs[2]); | |||
display->gate = &(module->gateTimeMs[2]); | |||
display->gateSpr = &(module->gateSprMs[2]); | |||
display->division = &(module->division[2]); | |||
display->actDly = &(module->actDelayMs[2]); | |||
display->actGate = &(module->actGateMs[2]); | |||
addChild(display); | |||
} | |||
{ | |||
Imperfect2Box *display = new Imperfect2Box(); | |||
display->module = module; | |||
display->box.pos = Vec(10, 305); | |||
display->box.size = Vec(200, 20); | |||
display->bpm = &(module->bpm[3]); | |||
display->dly = &(module->delayTimeMs[3]); | |||
display->dlySpr = &(module->delaySprMs[3]); | |||
display->gate = &(module->gateTimeMs[3]); | |||
display->gateSpr = &(module->gateSprMs[3]); | |||
display->division = &(module->division[3]); | |||
display->actDly = &(module->actDelayMs[3]); | |||
display->actGate = &(module->actGateMs[3]); | |||
addChild(display); | |||
} | |||
for (int i = 0; i < 4; i++) { | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, i * 2 + 1, true, true), Port::INPUT, module, Imperfect2::TRIG_INPUT + i)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, i * 2 + 1, true, true), Port::INPUT, module, Imperfect2::DELAY_INPUT + i)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 2, i * 2 + 1, true, true), module, Imperfect2::DELAY_PARAM + i, 1.0f, 2.0f, 1.0f)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, i * 2 + 1, true, true), Port::INPUT, module, Imperfect2::DELAYSPREAD_INPUT + i)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 4, i * 2 + 1, true, true), module, Imperfect2::DELAYSPREAD_PARAM + i, 1.0f, 2.0f, 1.0f)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, i * 2 + 1, true, true), Port::INPUT, module, Imperfect2::LENGTH_INPUT + i)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 6, i * 2 + 1, true, true), module, Imperfect2::LENGTH_PARAM + i, 1.001f, 2.0f, 1.001f)); // Always produce gate | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 7, i * 2 + 1, true, true), Port::INPUT, module, Imperfect2::LENGTHSPREAD_INPUT + i)); | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 8, i * 2 + 1, true, true), module, Imperfect2::LENGTHSPREAD_PARAM + i, 1.0, 2.0, 1.0f)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 9, i * 2 + 1, true, true), module, Imperfect2::DIVISION_PARAM + i, 1, 64, 1)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(ui.getPosition(UI::LIGHT, 10, i * 2 + 1, true, true), module, Imperfect2::OUT_LIGHT + i * 2)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 11, i * 2+ 1, true, true), Port::OUTPUT, module, Imperfect2::OUT_OUTPUT + i)); | |||
} | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Imperfect2) { | |||
Model *modelImperfect2 = Model::create<Imperfect2, Imperfect2Widget>( "Amalgamated Harmonics", "Imperfect2", "Imperfect MkII", UTILITY_TAG); | |||
return modelImperfect2; | |||
} | |||
@@ -0,0 +1,578 @@ | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include "dsp/digital.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct Progress : AHModule { | |||
const static int NUM_PITCHES = 6; | |||
enum ParamIds { | |||
CLOCK_PARAM, | |||
RUN_PARAM, | |||
RESET_PARAM, | |||
STEPS_PARAM, | |||
ENUMS(ROOT_PARAM,8), | |||
ENUMS(CHORD_PARAM,8), | |||
ENUMS(INV_PARAM,8), | |||
ENUMS(GATE_PARAM,8), | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
KEY_INPUT, | |||
MODE_INPUT, | |||
CLOCK_INPUT, | |||
EXT_CLOCK_INPUT, | |||
RESET_INPUT, | |||
STEPS_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
GATES_OUTPUT, | |||
ENUMS(PITCH_OUTPUT,6), | |||
ENUMS(GATE_OUTPUT,8), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
RUNNING_LIGHT, | |||
RESET_LIGHT, | |||
GATES_LIGHT, | |||
ENUMS(GATE_LIGHTS,16), | |||
NUM_LIGHTS | |||
}; | |||
Progress() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { } | |||
void step() override; | |||
enum ParamType { | |||
ROOT_TYPE, | |||
CHORD_TYPE, | |||
INV_TYPE | |||
}; | |||
void receiveEvent(ParamEvent e) override { | |||
if (receiveEvents && e.pType != -1) { // AHParamWidgets that are no config through set<>() have a pType of -1 | |||
if (modeMode) { | |||
paramState = "> " + | |||
CoreUtil().noteNames[currRoot[e.pId]] + | |||
CoreUtil().ChordTable[currChord[e.pId]].quality + " " + | |||
CoreUtil().inversionNames[currInv[e.pId]] + " " + "[" + | |||
CoreUtil().degreeNames[currDegree[e.pId] * 3 + currQuality[e.pId]] + "]"; | |||
} else { | |||
paramState = "> " + | |||
CoreUtil().noteNames[currRoot[e.pId]] + | |||
CoreUtil().ChordTable[currChord[e.pId]].quality + " " + | |||
CoreUtil().inversionNames[currInv[e.pId]]; | |||
} | |||
} | |||
keepStateDisplay = 0; | |||
} | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// running | |||
json_object_set_new(rootJ, "running", json_boolean(running)); | |||
// gates | |||
json_t *gatesJ = json_array(); | |||
for (int i = 0; i < 8; i++) { | |||
json_t *gateJ = json_integer((int) gates[i]); | |||
json_array_append_new(gatesJ, gateJ); | |||
} | |||
json_object_set_new(rootJ, "gates", gatesJ); | |||
// gateMode | |||
json_t *gateModeJ = json_integer((int) gateMode); | |||
json_object_set_new(rootJ, "gateMode", gateModeJ); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
// running | |||
json_t *runningJ = json_object_get(rootJ, "running"); | |||
if (runningJ) | |||
running = json_is_true(runningJ); | |||
// gates | |||
json_t *gatesJ = json_object_get(rootJ, "gates"); | |||
if (gatesJ) { | |||
for (int i = 0; i < 8; i++) { | |||
json_t *gateJ = json_array_get(gatesJ, i); | |||
if (gateJ) | |||
gates[i] = !!json_integer_value(gateJ); | |||
} | |||
} | |||
// gateMode | |||
json_t *gateModeJ = json_object_get(rootJ, "gateMode"); | |||
if (gateModeJ) | |||
gateMode = (GateMode)json_integer_value(gateModeJ); | |||
} | |||
bool running = true; | |||
// for external clock | |||
SchmittTrigger clockTrigger; | |||
// For buttons | |||
SchmittTrigger runningTrigger; | |||
SchmittTrigger resetTrigger; | |||
SchmittTrigger gateTriggers[8]; | |||
PulseGenerator gatePulse; | |||
/** Phase of internal LFO */ | |||
float phase = 0.0f; | |||
// Step index | |||
int index = 0; | |||
bool gates[8] = {true,true,true,true,true,true,true,true}; | |||
float resetLight = 0.0f; | |||
float gateLight = 0.0f; | |||
float stepLights[8] = {}; | |||
enum GateMode { | |||
TRIGGER, | |||
RETRIGGER, | |||
CONTINUOUS, | |||
}; | |||
GateMode gateMode = CONTINUOUS; | |||
bool modeMode = false; | |||
bool prevModeMode = false; | |||
int offset = 24; // Repeated notes in chord and expressed in the chord definition as being transposed 2 octaves lower. | |||
// When played this offset needs to be removed (or the notes removed, or the notes transposed to an octave higher) | |||
float prevRootInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0}; | |||
float prevChrInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0}; | |||
float prevDegreeInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0}; | |||
float prevQualityInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0}; | |||
float prevInvInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0}; | |||
float currRootInput[8]; | |||
float currChrInput[8]; | |||
float currDegreeInput[8]; | |||
float currQualityInput[8]; | |||
float currInvInput[8]; | |||
int currMode; | |||
int currKey; | |||
int prevMode = -1; | |||
int prevKey = -1; | |||
int currRoot[8]; | |||
int currChord[8]; | |||
int currInv[8]; | |||
int currDegree[8]; | |||
int currQuality[8]; | |||
float pitches[8][6]; | |||
float oldPitches[6]; | |||
void reset() override { | |||
for (int i = 0; i < 8; i++) { | |||
gates[i] = true; | |||
} | |||
} | |||
void setIndex(int index, int nSteps) { | |||
phase = 0.0f; | |||
this->index = index; | |||
if (this->index >= nSteps) { | |||
this->index = 0; | |||
} | |||
this->gatePulse.trigger(Core::TRIGGER); | |||
} | |||
}; | |||
void Progress::step() { | |||
AHModule::step(); | |||
// Run | |||
if (runningTrigger.process(params[RUN_PARAM].value)) { | |||
running = !running; | |||
} | |||
int numSteps = (int) clamp(roundf(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value), 1.0f, 8.0f); | |||
if (running) { | |||
if (inputs[EXT_CLOCK_INPUT].active) { | |||
// External clock | |||
if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { | |||
setIndex(index + 1, numSteps); | |||
} | |||
} | |||
else { | |||
// Internal clock | |||
float clockTime = powf(2.0f, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value); | |||
phase += clockTime * delta; | |||
if (phase >= 1.0f) { | |||
setIndex(index + 1, numSteps); | |||
} | |||
} | |||
} | |||
// Reset | |||
if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { | |||
setIndex(0, numSteps); | |||
} | |||
bool haveRoot = false; | |||
bool haveMode = false; | |||
// index is our current step | |||
if (inputs[KEY_INPUT].active) { | |||
float fRoot = inputs[KEY_INPUT].value; | |||
currKey = CoreUtil().getKeyFromVolts(fRoot); | |||
haveRoot = true; | |||
} | |||
if (inputs[MODE_INPUT].active) { | |||
float fMode = inputs[MODE_INPUT].value; | |||
currMode = CoreUtil().getModeFromVolts(fMode); | |||
haveMode = true; | |||
} | |||
modeMode = haveRoot && haveMode; | |||
if (modeMode && ((prevMode != currMode) || (prevKey != currKey))) { // Input changes so force re-read | |||
for (int step = 0; step < 8; step++) { | |||
prevDegreeInput[step] = -100.0; | |||
prevQualityInput[step] = -100.0; | |||
} | |||
prevMode = currMode; | |||
prevKey = currKey; | |||
} | |||
// Read inputs | |||
for (int step = 0; step < 8; step++) { | |||
if (modeMode) { | |||
currDegreeInput[step] = params[CHORD_PARAM + step].value; | |||
currQualityInput[step] = params[ROOT_PARAM + step].value; | |||
if (prevModeMode != modeMode) { // Switching mode, so reset history to ensure re-read on return | |||
prevChrInput[step] = -100.0; | |||
prevRootInput[step] = -100.0; | |||
} | |||
} else { | |||
currChrInput[step] = params[CHORD_PARAM + step].value; | |||
currRootInput[step] = params[ROOT_PARAM + step].value; | |||
if (prevModeMode != modeMode) { // Switching mode, so reset history to ensure re-read on return | |||
prevDegreeInput[step] = -100.0; | |||
prevQualityInput[step] = -100.0; | |||
} | |||
} | |||
currInvInput[step] = params[INV_PARAM + step].value; | |||
} | |||
// Remember mode | |||
prevModeMode = modeMode; | |||
// Check for changes on all steps | |||
for (int step = 0; step < 8; step++) { | |||
bool update = false; | |||
if (modeMode) { | |||
currDegreeInput[step] = params[ROOT_PARAM + step].value; | |||
currQualityInput[step] = params[CHORD_PARAM + step].value; | |||
if (prevDegreeInput[step] != currDegreeInput[step]) { | |||
prevDegreeInput[step] = currDegreeInput[step]; | |||
update = true; | |||
} | |||
if (prevQualityInput[step] != currQualityInput[step]) { | |||
prevQualityInput[step] = currQualityInput[step]; | |||
update = true; | |||
} | |||
if (update) { | |||
// Get Degree (I- VII) | |||
currDegree[step] = round(rescale(fabs(currDegreeInput[step]), 0.0f, 10.0f, 0.0f, Core::NUM_DEGREES - 1)); | |||
// From the input root, mode and degree, we can get the root chord note and quality (Major,Minor,Diminshed) | |||
CoreUtil().getRootFromMode(currMode,currKey,currDegree[step],&currRoot[step],&currQuality[step]); | |||
// Now get the actual chord from the main list | |||
switch(currQuality[step]) { | |||
case Core::MAJ: | |||
currChord[step] = round(rescale(fabs(currQualityInput[step]), 0.0f, 10.0f, 1.0f, 70.0f)); | |||
break; | |||
case Core::MIN: | |||
currChord[step] = round(rescale(fabs(currQualityInput[step]), 0.0f, 10.0f, 71.0f, 90.0f)); | |||
break; | |||
case Core::DIM: | |||
currChord[step] = round(rescale(fabs(currQualityInput[step]), 0.0f, 10.0f, 91.0f, 98.0f)); | |||
break; | |||
} | |||
} | |||
} else { | |||
// Chord Mode | |||
// If anything has changed, recalculate output for that step | |||
if (prevRootInput[step] != currRootInput[step]) { | |||
prevRootInput[step] = currRootInput[step]; | |||
currRoot[step] = round(rescale(fabs(currRootInput[step]), 0.0f, 10.0f, 0.0f, Core::NUM_NOTES - 1)); // Param range is 0 to 10, mapped to 0 to 11 | |||
update = true; | |||
} | |||
if (prevChrInput[step] != currChrInput[step]) { | |||
prevChrInput[step] = currChrInput[step]; | |||
currChord[step] = round(rescale(fabs(currChrInput[step]), 0.0f, 10.0f, 1.0f, 98.0f)); // Param range is 0 to 10 | |||
update = true; | |||
} | |||
} | |||
// Inversions remain the same between Chord and Mode mode | |||
if (prevInvInput[step] != currInvInput[step]) { | |||
prevInvInput[step] = currInvInput[step]; | |||
currInv[step] = currInvInput[step]; | |||
update = true; | |||
} | |||
// So, after all that, we calculate the pitch output | |||
if (update) { | |||
int *chordArray; | |||
// Get the array of pitches based on the inversion | |||
switch(currInv[step]) { | |||
case Core::ROOT: chordArray = CoreUtil().ChordTable[currChord[step]].root; break; | |||
case Core::FIRST_INV: chordArray = CoreUtil().ChordTable[currChord[step]].first; break; | |||
case Core::SECOND_INV: chordArray = CoreUtil().ChordTable[currChord[step]].second; break; | |||
default: chordArray = CoreUtil().ChordTable[currChord[step]].root; | |||
} | |||
for (int j = 0; j < NUM_PITCHES; j++) { | |||
// Set the pitches for this step. If the chord has less than 6 notes, the empty slots are | |||
// filled with repeated notes. These notes are identified by a 24 semi-tome negative | |||
// offset. We correct for that offset now, pitching thaem back into the original octave. | |||
// They could be pitched into the octave above (or below) | |||
if (chordArray[j] < 0) { | |||
pitches[step][j] = CoreUtil().getVoltsFromPitch(chordArray[j] + offset,currRoot[step]); | |||
} else { | |||
pitches[step][j] = CoreUtil().getVoltsFromPitch(chordArray[j],currRoot[step]); | |||
} | |||
} | |||
} | |||
} | |||
bool pulse = gatePulse.process(delta); | |||
// Gate buttons | |||
for (int i = 0; i < 8; i++) { | |||
if (gateTriggers[i].process(params[GATE_PARAM + i].value)) { | |||
gates[i] = !gates[i]; | |||
} | |||
bool gateOn = (running && i == index && gates[i]); | |||
if (gateMode == TRIGGER) { | |||
gateOn = gateOn && pulse; | |||
} else if (gateMode == RETRIGGER) { | |||
gateOn = gateOn && !pulse; | |||
} | |||
outputs[GATE_OUTPUT + i].value = gateOn ? 10.0f : 0.0f; | |||
if (gateOn && i == index) { | |||
if (gates[i]) { | |||
// Gate is on and active = flash green | |||
lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(1.0f); | |||
lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(0.0f); | |||
} else { | |||
// Gate is off and active = flash red - this seems to not have any effect :( | |||
lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(0.0); | |||
lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(0.33f); | |||
} | |||
} else { | |||
if (gates[i]) { | |||
// Gate is on and not active = red | |||
lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(0.0f); | |||
lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(1.0f); | |||
} else { | |||
// Gate is off and not active = black | |||
lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(0.0f); | |||
lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(0.0f); | |||
} | |||
} | |||
} | |||
bool gatesOn = (running && gates[index]); | |||
if (gateMode == TRIGGER) { | |||
gatesOn = gatesOn && pulse; | |||
} else if (gateMode == RETRIGGER) { | |||
gatesOn = gatesOn && !pulse; | |||
} | |||
// Outputs | |||
outputs[GATES_OUTPUT].value = gatesOn ? 10.0f : 0.0f; | |||
lights[RUNNING_LIGHT].value = (running); | |||
lights[RESET_LIGHT].setBrightnessSmooth(resetTrigger.isHigh()); | |||
lights[GATES_LIGHT].setBrightnessSmooth(pulse); | |||
for (int i = 0; i < NUM_PITCHES; i++) { | |||
outputs[PITCH_OUTPUT + i].value = pitches[index][i]; | |||
} | |||
} | |||
struct ProgressWidget : ModuleWidget { | |||
ProgressWidget(Progress *module); | |||
Menu *createContextMenu() override; | |||
}; | |||
ProgressWidget::ProgressWidget(Progress *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(15*26, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/Progress.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365))); | |||
{ | |||
StateDisplay *display = new StateDisplay(); | |||
display->module = module; | |||
display->box.pos = Vec(0, 135); | |||
display->box.size = Vec(100, 140); | |||
addChild(display); | |||
} | |||
addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 0, 0, true, false), module, Progress::CLOCK_PARAM, -2.0, 6.0, 2.0)); | |||
addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, 1, 0, true, false), module, Progress::RUN_PARAM, 0.0, 1.0, 0.0)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 1, 0, true, false), module, Progress::RUNNING_LIGHT)); | |||
addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, 2, 0, true, false), module, Progress::RESET_PARAM, 0.0, 1.0, 0.0)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 2, 0, true, false), module, Progress::RESET_LIGHT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 3, 0, true, false), module, Progress::STEPS_PARAM, 1.0, 8.0, 8.0)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 4, 0, true, false), module, Progress::GATES_LIGHT)); | |||
// static const float portX[13] = {20, 58, 96, 135, 173, 212, 250, 288, 326, 364, 402, 440, 478}; | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 1, true, false), Port::INPUT, module, Progress::CLOCK_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, 1, true, false), Port::INPUT, module, Progress::EXT_CLOCK_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 1, true, false), Port::INPUT, module, Progress::RESET_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, 1, true, false), Port::INPUT, module, Progress::STEPS_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 1, true, false), Port::INPUT, module, Progress::KEY_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 1, true, false), Port::INPUT, module, Progress::MODE_INPUT)); | |||
for (int i = 0; i < 3; i++) { | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 7 + i, 0, true, false), Port::OUTPUT, module, Progress::PITCH_OUTPUT + i)); | |||
} | |||
for (int i = 0; i < 3; i++) { | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 7 + i, 1, true, false), Port::OUTPUT, module, Progress::PITCH_OUTPUT + 3 + i)); | |||
} | |||
for (int i = 0; i < 8; i++) { | |||
AHKnobNoSnap *rootW = ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, i + 1, 4, true, true), module, Progress::ROOT_PARAM + i, 0.0, 10.0, 0.0); | |||
AHParamWidget::set<AHKnobNoSnap>(rootW, Progress::ROOT_TYPE, i); | |||
addParam(rootW); | |||
AHKnobNoSnap *chordW = ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, i + 1, 5, true, true), module, Progress::CHORD_PARAM + i, 0.0, 10.0, 0.0); | |||
AHParamWidget::set<AHKnobNoSnap>(chordW, Progress::CHORD_TYPE, i); | |||
addParam(chordW); | |||
AHKnobSnap *invW = ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, i + 1, 6, true, true), module, Progress::INV_PARAM + i, 0.0, 2.0, 0.0); | |||
AHParamWidget::set<AHKnobSnap>(invW, Progress::INV_TYPE, i); | |||
addParam(invW); | |||
addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, i + 1, 7, true, true), module, Progress::GATE_PARAM + i, 0.0, 1.0, 0.0)); | |||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(ui.getPosition(UI::LIGHT, i + 1, 7, true, true), module, Progress::GATE_LIGHTS + i * 2)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, i + 1, 5, true, false), Port::OUTPUT, module, Progress::GATE_OUTPUT + i)); | |||
} | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 9, 5, true, false), Port::OUTPUT, module, Progress::GATES_OUTPUT)); | |||
} | |||
struct ProgressGateModeItem : MenuItem { | |||
Progress *progress; | |||
Progress::GateMode gateMode; | |||
void onAction(EventAction &e) override { | |||
progress->gateMode = gateMode; | |||
} | |||
void step() override { | |||
rightText = (progress->gateMode == gateMode) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *ProgressWidget::createContextMenu() { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
Progress *progress = dynamic_cast<Progress*>(module); | |||
assert(progress); | |||
MenuLabel *modeLabel = new MenuLabel(); | |||
modeLabel->text = "Gate Mode"; | |||
menu->addChild(modeLabel); | |||
ProgressGateModeItem *triggerItem = new ProgressGateModeItem(); | |||
triggerItem->text = "Trigger"; | |||
triggerItem->progress = progress; | |||
triggerItem->gateMode = Progress::TRIGGER; | |||
menu->addChild(triggerItem); | |||
ProgressGateModeItem *retriggerItem = new ProgressGateModeItem(); | |||
retriggerItem->text = "Retrigger"; | |||
retriggerItem->progress = progress; | |||
retriggerItem->gateMode = Progress::RETRIGGER; | |||
menu->addChild(retriggerItem); | |||
ProgressGateModeItem *continuousItem = new ProgressGateModeItem(); | |||
continuousItem->text = "Continuous"; | |||
continuousItem->progress = progress; | |||
continuousItem->gateMode = Progress::CONTINUOUS; | |||
menu->addChild(continuousItem); | |||
return menu; | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Progress) { | |||
Model *modelProgress = Model::create<Progress, ProgressWidget>( "Amalgamated Harmonics", "Progress", "Progress", SEQUENCER_TAG); | |||
return modelProgress; | |||
} |
@@ -0,0 +1,314 @@ | |||
#include "util/common.hpp" | |||
#include "dsp/digital.hpp" | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct Ruckus : AHModule { | |||
enum ParamIds { | |||
ENUMS(DIV_PARAM,16), | |||
ENUMS(PROB_PARAM,16), | |||
ENUMS(SHIFT_PARAM,16), | |||
ENUMS(XMUTE_PARAM,16), | |||
ENUMS(YMUTE_PARAM,4), | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
TRIG_INPUT, | |||
RESET_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
ENUMS(XOUT_OUTPUT,4), | |||
ENUMS(YOUT_OUTPUT,4), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(XMUTE_LIGHT,4), | |||
ENUMS(YMUTE_LIGHT,4), | |||
NUM_LIGHTS | |||
}; | |||
Ruckus() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
reset(); | |||
} | |||
void step() override; | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// gates | |||
json_t *xMutesJ = json_array(); | |||
json_t *yMutesJ = json_array(); | |||
for (int i = 0; i < 4; i++) { | |||
json_t *xMuteJ = json_integer((int) xMute[i]); | |||
json_array_append_new(xMutesJ, xMuteJ); | |||
json_t *yMuteJ = json_integer((int) yMute[i]); | |||
json_array_append_new(yMutesJ, yMuteJ); | |||
} | |||
json_object_set_new(rootJ, "xMutes", xMutesJ); | |||
json_object_set_new(rootJ, "yMutes", yMutesJ); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
// gates | |||
json_t *xMutesJ = json_object_get(rootJ, "xMutes"); | |||
if (xMutesJ) { | |||
for (int i = 0; i < 4; i++) { | |||
json_t *xMuteJ = json_array_get(xMutesJ, i); | |||
if (xMuteJ) | |||
xMute[i] = !!json_integer_value(xMuteJ); | |||
} | |||
} | |||
json_t *yMutesJ = json_object_get(rootJ, "yMutes"); | |||
if (yMutesJ) { | |||
for (int i = 0; i < 4; i++) { | |||
json_t *yMuteJ = json_array_get(yMutesJ, i); | |||
if (yMuteJ) | |||
yMute[i] = !!json_integer_value(yMuteJ); | |||
} | |||
} | |||
} | |||
enum ParamType { | |||
DIV_TYPE, | |||
SHIFT_TYPE, | |||
PROB_TYPE | |||
}; | |||
void receiveEvent(ParamEvent e) override { | |||
if (receiveEvents) { | |||
switch(e.pType) { | |||
case ParamType::DIV_TYPE: | |||
paramState = "> Division: " + std::to_string((int)e.value); | |||
break; | |||
case ParamType::SHIFT_TYPE: | |||
paramState = "> Beat shift: " + std::to_string((int)e.value); | |||
break; | |||
case ParamType::PROB_TYPE: | |||
paramState = "> Probability: " + std::to_string(e.value * 100.0f).substr(0,6) + "%"; | |||
break; | |||
default: | |||
paramState = "> UNK:" + std::to_string(e.value).substr(0,6); | |||
} | |||
} | |||
keepStateDisplay = 0; | |||
} | |||
void reset() override { | |||
for (int i = 0; i < 4; i++) { | |||
xMute[i] = true; | |||
yMute[i] = true; | |||
} | |||
} | |||
Core core; | |||
AHPulseGenerator xGate[4]; | |||
AHPulseGenerator yGate[4]; | |||
bool xMute[4] = {true, true, true, true}; | |||
bool yMute[4] = {true, true, true, true}; | |||
SchmittTrigger xLockTrigger[4]; | |||
SchmittTrigger yLockTrigger[4]; | |||
SchmittTrigger inTrigger; | |||
SchmittTrigger resetTrigger; | |||
int division[16]; | |||
int shift[16]; | |||
float prob[16]; | |||
unsigned int beatCounter = 0; | |||
}; | |||
void Ruckus::step() { | |||
AHModule::step(); | |||
for (int i = 0; i < 4; i++) { | |||
if (xLockTrigger[i].process(params[XMUTE_PARAM + i].value)) { | |||
xMute[i] = !xMute[i]; | |||
} | |||
if (yLockTrigger[i].process(params[YMUTE_PARAM + i].value)) { | |||
yMute[i] = !yMute[i]; | |||
} | |||
} | |||
for (int i = 0; i < 16; i++) { | |||
division[i] = params[DIV_PARAM + i].value; | |||
prob[i] = params[PROB_PARAM + i].value; | |||
shift[i] = params[SHIFT_PARAM + i].value; | |||
} | |||
if (resetTrigger.process(inputs[RESET_INPUT].value)) { | |||
beatCounter = 0; | |||
} | |||
if (inTrigger.process(inputs[TRIG_INPUT].value)) { | |||
beatCounter++; | |||
for (int y = 0; y < 4; y++) { | |||
for (int x = 0; x < 4; x++) { | |||
int i = y * 4 + x; | |||
if(division[i] == 0) { // 0 == skip | |||
continue; | |||
} | |||
int target = beatCounter + shift[i]; | |||
if (target < 0) { // shifted into negative count | |||
continue; | |||
} | |||
if (target % division[i] == 0) { | |||
if (randomUniform() < prob[i]) { | |||
xGate[x].trigger(Core::TRIGGER); | |||
yGate[y].trigger(Core::TRIGGER); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
for (int i = 0; i < 4; i++) { | |||
if (xGate[i].process(delta) && xMute[i]) { | |||
outputs[XOUT_OUTPUT + i].value = 10.0f; | |||
} else { | |||
outputs[XOUT_OUTPUT + i].value = 0.0f; | |||
} | |||
lights[XMUTE_LIGHT + i].value = xMute[i] ? 1.0 : 0.0; | |||
if (yGate[i].process(delta) && yMute[i]) { | |||
outputs[YOUT_OUTPUT + i].value = 10.0f; | |||
} else { | |||
outputs[YOUT_OUTPUT + i].value = 0.0f; | |||
} | |||
lights[YMUTE_LIGHT + i].value = yMute[i] ? 1.0 : 0.0; | |||
} | |||
} | |||
struct RuckusWidget : ModuleWidget { | |||
RuckusWidget(Ruckus *module); | |||
}; | |||
RuckusWidget::RuckusWidget(Ruckus *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(390, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/Ruckus.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
{ | |||
StateDisplay *display = new StateDisplay(); | |||
display->module = module; | |||
display->box.pos = Vec(30, 335); | |||
display->box.size = Vec(100, 140); | |||
addChild(display); | |||
} | |||
//299.5 329.7 | |||
Vec a = ui.getPosition(UI::PORT, 6, 5, false, false); | |||
a.x = 312.0; | |||
//325.5 329.7 | |||
Vec b = ui.getPosition(UI::PORT, 7, 5, false, false); | |||
b.x = 352.0; | |||
addInput(Port::create<PJ301MPort>(a, Port::INPUT, module, Ruckus::TRIG_INPUT)); | |||
addInput(Port::create<PJ301MPort>(b, Port::INPUT, module, Ruckus::RESET_INPUT)); | |||
float xd = 18.0f; | |||
float yd = 20.0f; | |||
for (int y = 0; y < 4; y++) { | |||
for (int x = 0; x < 4; x++) { | |||
int i = y * 4 + x; | |||
Vec v = ui.getPosition(UI::KNOB, 1 + x * 2, y * 2, true, true); | |||
AHKnobSnap *divW = ParamWidget::create<AHKnobSnap>(v, module, Ruckus::DIV_PARAM + i, 0, 64, 0); | |||
AHParamWidget::set<AHKnobSnap>(divW, Ruckus::DIV_TYPE, i); | |||
addParam(divW); | |||
AHTrimpotNoSnap *probW = ParamWidget::create<AHTrimpotNoSnap>(Vec(v.x + xd, v.y + yd), module, Ruckus::PROB_PARAM + i, 0.0f, 1.0f, 1.0f); | |||
AHParamWidget::set<AHTrimpotNoSnap>(probW, Ruckus::PROB_TYPE, i); | |||
addParam(probW); | |||
AHTrimpotSnap *shiftW = ParamWidget::create<AHTrimpotSnap>(Vec(v.x - xd + 4, v.y + yd), module, Ruckus::SHIFT_PARAM + i, -64.0f, 64.0f, 0.0f); | |||
AHParamWidget::set<AHTrimpotSnap>(shiftW, Ruckus::SHIFT_TYPE, i); | |||
addParam(shiftW); | |||
} | |||
} | |||
float d = 12.0f; | |||
for (int x = 0; x < 4; x++) { | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1 + x * 2, 8, true, true), Port::OUTPUT, module, Ruckus::XOUT_OUTPUT + x)); | |||
Vec bVec = ui.getPosition(UI::BUTTON, 1 + x * 2, 7, true, true); | |||
bVec.y = bVec.y + d; | |||
addParam(ParamWidget::create<AHButton>(bVec, module, Ruckus::XMUTE_PARAM + x, 0.0, 1.0, 0.0)); | |||
Vec lVec = ui.getPosition(UI::LIGHT, 1 + x * 2, 7, true, true); | |||
lVec.y = lVec.y + d; | |||
addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(lVec, module, Ruckus::XMUTE_LIGHT + x)); | |||
} | |||
for (int y = 0; y < 4; y++) { | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT,9, y * 2, true, true), Port::OUTPUT, module, Ruckus::YOUT_OUTPUT + y)); | |||
Vec bVec = ui.getPosition(UI::BUTTON, 8, y * 2, true, true); | |||
bVec.x = bVec.x + d; | |||
addParam(ParamWidget::create<AHButton>(bVec, module, Ruckus::YMUTE_PARAM + y, 0.0, 1.0, 0.0)); | |||
Vec lVec = ui.getPosition(UI::LIGHT, 8, y * 2, true, true); | |||
lVec.x = lVec.x + d; | |||
addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(lVec, module, Ruckus::YMUTE_LIGHT + y)); | |||
} | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Ruckus) { | |||
Model *modelRuckus = Model::create<Ruckus, RuckusWidget>( "Amalgamated Harmonics", "Ruckus", "Ruckus", SEQUENCER_TAG); | |||
return modelRuckus; | |||
} | |||
@@ -0,0 +1,173 @@ | |||
#include "util/common.hpp" | |||
#include "dsp/digital.hpp" | |||
#include "dsp/noise.hpp" | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct SLN : AHModule { | |||
enum ParamIds { | |||
SPEED_PARAM, | |||
SLOPE_PARAM, | |||
NOISE_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
TRIG_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
OUT_OUTPUT, | |||
NOISE_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
NUM_LIGHTS | |||
}; | |||
SLN() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
} | |||
void step() override; | |||
Core core; | |||
SchmittTrigger inTrigger; | |||
bogaudio_dsp::WhiteNoiseGenerator white; | |||
bogaudio_dsp::PinkNoiseGenerator pink; | |||
bogaudio_dsp::RedNoiseGenerator brown; | |||
float target = 0.0f; | |||
float current = 0.0f; | |||
// minimum and maximum slopes in volts per second | |||
const float slewMin = 0.1f; | |||
const float slewMax = 10000.0f; | |||
const float slewRatio = slewMin / slewMax; | |||
// Amount of extra slew per voltage difference | |||
const float shapeScale = 1.0f/10.0f; | |||
}; | |||
void SLN::step() { | |||
AHModule::step(); | |||
float noise; | |||
int noiseType = params[NOISE_PARAM].value; | |||
switch(noiseType) { | |||
case 0: | |||
noise = white.next() * 10.0f; | |||
break; | |||
case 1: | |||
noise = pink.next() * 10.8f; // scale to -10 to 10; | |||
break; | |||
case 2: | |||
noise = brown.next() * 23.4f; // scale to -10 to 10; | |||
break; | |||
default: | |||
noise = white.next() * 10.0f; | |||
} | |||
// Capture noise | |||
if (inTrigger.process(inputs[TRIG_INPUT].value / 0.7)) { | |||
target = noise; | |||
} | |||
float shape = params[SLOPE_PARAM].value; | |||
float speed = params[SPEED_PARAM].value; | |||
float slew = slewMax * powf(slewRatio, speed); | |||
// Rise | |||
if (target > current) { | |||
current += slew * crossfade(1.0f, shapeScale * (target - current), shape) * delta; | |||
if (current > target) // Trap overshoot | |||
current = target; | |||
} | |||
// Fall | |||
else if (target < current) { | |||
current -= slew * crossfade(1.0f, shapeScale * (current - target), shape) * delta; | |||
if (current < target) // Trap overshoot | |||
current = target; | |||
} | |||
outputs[OUT_OUTPUT].value = current; | |||
outputs[NOISE_OUTPUT].value = noise; | |||
} | |||
struct SLNWidget : ModuleWidget { | |||
SLNWidget(SLN *module); | |||
}; | |||
SLNWidget::SLNWidget(SLN *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(45, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/SLN.svg"))); | |||
addChild(panel); | |||
} | |||
float panelwidth = 45.0; | |||
float portwidth = 25.0; | |||
float knobwidth = 23.0; | |||
float portX = (panelwidth - portwidth) / 2.0; | |||
float knobX = (panelwidth - knobwidth) / 2.0; | |||
Vec p1 = ui.getPosition(UI::PORT, 0, 0, false, false); | |||
p1.x = portX; | |||
addInput(Port::create<PJ301MPort>(p1, Port::INPUT, module, SLN::TRIG_INPUT)); | |||
Vec p2 = ui.getPosition(UI::PORT, 0, 4, false, false); | |||
p2.x = portX; | |||
addOutput(Port::create<PJ301MPort>(p2, Port::OUTPUT, module, SLN::OUT_OUTPUT)); | |||
Vec p3 = ui.getPosition(UI::PORT, 0, 5, false, false); | |||
p3.x = portX; | |||
addOutput(Port::create<PJ301MPort>(p3, Port::OUTPUT, module, SLN::NOISE_OUTPUT)); | |||
Vec k1 = ui.getPosition(UI::PORT, 0, 1, false, false); | |||
k1.x = knobX; | |||
AHKnobNoSnap *speedW = ParamWidget::create<AHKnobNoSnap>(k1, module, SLN::SPEED_PARAM, 0.0, 1.0, 0.0); | |||
addParam(speedW); | |||
Vec k2 = ui.getPosition(UI::PORT, 0, 2, false, false); | |||
k2.x = knobX; | |||
AHKnobNoSnap *slopeW = ParamWidget::create<AHKnobNoSnap>(k2, module, SLN::SLOPE_PARAM, 0.0, 1.0, 0.0); | |||
addParam(slopeW); | |||
Vec k3 = ui.getPosition(UI::PORT, 0, 3, false, false); | |||
k3.x = knobX; | |||
AHKnobSnap *noiseW = ParamWidget::create<AHKnobSnap>(k3, module, SLN::NOISE_PARAM, 0.0, 2.0, 0.0); | |||
addParam(noiseW); | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, SLN) { | |||
Model *modelSLN = Model::create<SLN, SLNWidget>( "Amalgamated Harmonics", "SLN", "SLN", NOISE_TAG); | |||
return modelSLN; | |||
} | |||
@@ -0,0 +1,167 @@ | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct ScaleQuantizer : AHModule { | |||
enum ParamIds { | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
IN_INPUT, | |||
KEY_INPUT, | |||
SCALE_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
OUT_OUTPUT, | |||
TRIG_OUTPUT, | |||
GATE_OUTPUT, | |||
NUM_OUTPUTS = GATE_OUTPUT + 12 | |||
}; | |||
enum LightIds { | |||
NOTE_LIGHT, | |||
KEY_LIGHT = NOTE_LIGHT + 12, | |||
SCALE_LIGHT = KEY_LIGHT + 12, | |||
DEGREE_LIGHT = SCALE_LIGHT + 12, | |||
NUM_LIGHTS = DEGREE_LIGHT + 12 | |||
}; | |||
ScaleQuantizer() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||
void step() override; | |||
bool firstStep = true; | |||
int lastScale = 0; | |||
int lastRoot = 0; | |||
float lastPitch = 0.0; | |||
int currScale = 0; | |||
int currRoot = 0; | |||
int currNote = 0; | |||
int currDegree = 0; | |||
float currPitch = 0.0; | |||
}; | |||
void ScaleQuantizer::step() { | |||
stepX++; | |||
lastScale = currScale; | |||
lastRoot = currRoot; | |||
lastPitch = currPitch; | |||
// Get the input pitch | |||
float volts = inputs[IN_INPUT].value; | |||
float root = inputs[KEY_INPUT].value; | |||
float scale = inputs[SCALE_INPUT].value; | |||
// Calculate output pitch from raw voltage | |||
currPitch = CoreUtil().getPitchFromVolts(volts, root, scale, &currRoot, &currScale, &currNote, &currDegree); | |||
// Set the value | |||
outputs[OUT_OUTPUT].value = currPitch; | |||
// update tone lights | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[NOTE_LIGHT + i].value = 0.0; | |||
} | |||
lights[NOTE_LIGHT + currNote].value = 1.0; | |||
// update degree lights | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[DEGREE_LIGHT + i].value = 0.0; | |||
outputs[GATE_OUTPUT + i].value = 0.0; | |||
} | |||
lights[DEGREE_LIGHT + currDegree].value = 1.0; | |||
outputs[GATE_OUTPUT + currDegree].value = 10.0; | |||
if (lastScale != currScale || firstStep) { | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[SCALE_LIGHT + i].value = 0.0; | |||
} | |||
lights[SCALE_LIGHT + currScale].value = 1.0; | |||
} | |||
if (lastRoot != currRoot || firstStep) { | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[KEY_LIGHT + i].value = 0.0; | |||
} | |||
lights[KEY_LIGHT + currRoot].value = 1.0; | |||
} | |||
if (lastPitch != currPitch || firstStep) { | |||
outputs[TRIG_OUTPUT].value = 10.0; | |||
} else { | |||
outputs[TRIG_OUTPUT].value = 0.0; | |||
} | |||
firstStep = false; | |||
} | |||
struct ScaleQuantizerWidget : ModuleWidget { | |||
ScaleQuantizerWidget(ScaleQuantizer *module); | |||
}; | |||
ScaleQuantizerWidget::ScaleQuantizerWidget(ScaleQuantizer *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(240, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/ScaleQuantizer.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 5, false, false), Port::INPUT, module, ScaleQuantizer::IN_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, 5, false, false), Port::INPUT, module, ScaleQuantizer::KEY_INPUT)); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 5, false, false), Port::INPUT, module, ScaleQuantizer::SCALE_INPUT)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, 5, false, false), Port::OUTPUT, module, ScaleQuantizer::TRIG_OUTPUT)); | |||
addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 5, false, false), Port::OUTPUT, module, ScaleQuantizer::OUT_OUTPUT)); | |||
float xOffset = 18.0; | |||
float xSpace = 21.0; | |||
float xPos = 0.0; | |||
float yPos = 0.0; | |||
int scale = 0; | |||
for (int i = 0; i < 12; i++) { | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xOffset + i * 18.0, 280.0), module, ScaleQuantizer::SCALE_LIGHT + i)); | |||
ui.calculateKeyboard(i, xSpace, xOffset, 230.0, &xPos, &yPos, &scale); | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos, yPos), module, ScaleQuantizer::KEY_LIGHT + scale)); | |||
ui.calculateKeyboard(i, xSpace, xOffset + 72.0, 165.0, &xPos, &yPos, &scale); | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos, yPos), module, ScaleQuantizer::NOTE_LIGHT + scale)); | |||
ui.calculateKeyboard(i, 30.0, xOffset + 9.5, 110.0, &xPos, &yPos, &scale); | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos, yPos), module, ScaleQuantizer::DEGREE_LIGHT + scale)); | |||
ui.calculateKeyboard(i, 30.0, xOffset, 85.0, &xPos, &yPos, &scale); | |||
addOutput(Port::create<PJ301MPort>(Vec(xPos, yPos), Port::OUTPUT, module, ScaleQuantizer::GATE_OUTPUT + scale)); | |||
} | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, ScaleQuantizer) { | |||
Model *modelScaleQuantizer = Model::create<ScaleQuantizer, ScaleQuantizerWidget>( "Amalgamated Harmonics", "ScaleQuantizer", "Scale Quantizer (deprecated)", QUANTIZER_TAG); | |||
return modelScaleQuantizer; | |||
} | |||
@@ -0,0 +1,215 @@ | |||
#include "dsp/digital.hpp" | |||
#include "AH.hpp" | |||
#include "Core.hpp" | |||
#include "UI.hpp" | |||
#include <iostream> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct ScaleQuantizer2 : AHModule { | |||
enum ParamIds { | |||
KEY_PARAM, | |||
SCALE_PARAM, | |||
ENUMS(SHIFT_PARAM,8), | |||
TRANS_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
ENUMS(IN_INPUT,8), | |||
KEY_INPUT, | |||
SCALE_INPUT, | |||
TRANS_INPUT, | |||
ENUMS(HOLD_INPUT,8), | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
ENUMS(OUT_OUTPUT,8), | |||
ENUMS(TRIG_OUTPUT,8), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(KEY_LIGHT,12), | |||
ENUMS(SCALE_LIGHT,12), | |||
NUM_LIGHTS | |||
}; | |||
ScaleQuantizer2() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||
void step() override; | |||
bool firstStep = true; | |||
int lastScale = 0; | |||
int lastRoot = 0; | |||
float lastTrans = -10000.0f; | |||
SchmittTrigger holdTrigger[8]; | |||
float holdPitch[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0}; | |||
float lastPitch[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0}; | |||
PulseGenerator triggerPulse[8]; | |||
int currScale = 0; | |||
int currRoot = 0; | |||
}; | |||
void ScaleQuantizer2::step() { | |||
AHModule::step(); | |||
lastScale = currScale; | |||
lastRoot = currRoot; | |||
int currNote = 0; | |||
int currDegree = 0; | |||
if (inputs[KEY_INPUT].active) { | |||
float fRoot = inputs[KEY_INPUT].value; | |||
currRoot = CoreUtil().getKeyFromVolts(fRoot); | |||
} else { | |||
currRoot = params[KEY_PARAM].value; | |||
} | |||
if (inputs[SCALE_INPUT].active) { | |||
float fScale = inputs[SCALE_INPUT].value; | |||
currScale = CoreUtil().getScaleFromVolts(fScale); | |||
} else { | |||
currScale = params[SCALE_PARAM].value; | |||
} | |||
float trans = (inputs[TRANS_INPUT].value + params[TRANS_PARAM].value) / 12.0; | |||
if (trans != 0.0) { | |||
if (trans != lastTrans) { | |||
int i; | |||
int d; | |||
trans = CoreUtil().getPitchFromVolts(trans, Core::NOTE_C, Core::SCALE_CHROMATIC, &i, &d); | |||
lastTrans = trans; | |||
} else { | |||
trans = lastTrans; | |||
} | |||
} | |||
for (int i = 0; i < 8; i++) { | |||
float holdInput = inputs[HOLD_INPUT + i].value; | |||
bool holdActive = inputs[HOLD_INPUT + i].active; | |||
bool holdStatus = holdTrigger[i].process(holdInput); | |||
float volts = inputs[IN_INPUT + i].value; | |||
float shift = params[SHIFT_PARAM + i].value; | |||
if (holdActive) { | |||
// Sample the pitch | |||
if (holdStatus && inputs[IN_INPUT + i].active) { | |||
holdPitch[i] = CoreUtil().getPitchFromVolts(volts, currRoot, currScale, &currNote, &currDegree); | |||
} | |||
} else { | |||
if (inputs[IN_INPUT + i].active) { | |||
holdPitch[i] = CoreUtil().getPitchFromVolts(volts, currRoot, currScale, &currNote, &currDegree); | |||
} | |||
} | |||
// If the quantised pitch has changed | |||
if (lastPitch[i] != holdPitch[i]) { | |||
// Pulse the gate | |||
triggerPulse[i].trigger(Core::TRIGGER); | |||
// Record the pitch | |||
lastPitch[i] = holdPitch[i]; | |||
} | |||
if (triggerPulse[i].process(delta)) { | |||
outputs[TRIG_OUTPUT + i].value = 10.0f; | |||
} else { | |||
outputs[TRIG_OUTPUT + i].value = 0.0f; | |||
} | |||
outputs[OUT_OUTPUT + i].value = holdPitch[i] + shift + trans; | |||
} | |||
if (lastScale != currScale || firstStep) { | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[SCALE_LIGHT + i].value = 0.0f; | |||
} | |||
lights[SCALE_LIGHT + currScale].value = 1.0f; | |||
} | |||
if (lastRoot != currRoot || firstStep) { | |||
for (int i = 0; i < Core::NUM_NOTES; i++) { | |||
lights[KEY_LIGHT + i].value = 0.0f; | |||
} | |||
lights[KEY_LIGHT + currRoot].value = 1.0f; | |||
} | |||
firstStep = false; | |||
} | |||
struct ScaleQuantizer2Widget : ModuleWidget { | |||
ScaleQuantizer2Widget(ScaleQuantizer2 *module); | |||
}; | |||
ScaleQuantizer2Widget::ScaleQuantizer2Widget(ScaleQuantizer2 *module) : ModuleWidget(module) { | |||
UI ui; | |||
box.size = Vec(300, 380); | |||
{ | |||
SVGPanel *panel = new SVGPanel(); | |||
panel->box.size = box.size; | |||
panel->setBackground(SVG::load(assetPlugin(plugin, "res/ScaleQuantizerMkII.svg"))); | |||
addChild(panel); | |||
} | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); | |||
addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 5, true, false), Port::INPUT, module, ScaleQuantizer2::KEY_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 1, 5, true, false), module, ScaleQuantizer2::KEY_PARAM, 0.0f, 11.0f, 0.0f)); // 12 notes | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, 5, true, false), Port::INPUT, module, ScaleQuantizer2::SCALE_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::PORT, 4, 5, true, false), module, ScaleQuantizer2::SCALE_PARAM, 0.0f, 11.0f, 0.0f)); // 12 notes | |||
addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 6, 5, true, false), Port::INPUT, module, ScaleQuantizer2::TRANS_INPUT)); | |||
addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::PORT, 7, 5, true, false), module, ScaleQuantizer2::TRANS_PARAM, -11.0f, 11.0f, 0.0f)); // 12 notes | |||
for (int i = 0; i < 8; i++) { | |||
addInput(Port::create<PJ301MPort>(Vec(6 + i * 29, 41), Port::INPUT, module, ScaleQuantizer2::IN_INPUT + i)); | |||
addParam(ParamWidget::create<AHTrimpotSnap>(Vec(9 + i * 29.1, 101), module, ScaleQuantizer2::SHIFT_PARAM + i, -3.0f, 3.0f, 0.0f)); | |||
addOutput(Port::create<PJ301MPort>(Vec(6 + i * 29, 125), Port::OUTPUT, module, ScaleQuantizer2::OUT_OUTPUT + i)); | |||
addInput(Port::create<PJ301MPort>(Vec(6 + i * 29, 71), Port::INPUT, module, ScaleQuantizer2::HOLD_INPUT + i)); | |||
addOutput(Port::create<PJ301MPort>(Vec(6 + i * 29, 155), Port::OUTPUT, module, ScaleQuantizer2::TRIG_OUTPUT + i)); | |||
} | |||
float xOffset = 18.0; | |||
float xSpace = 21.0; | |||
float xPos = 0.0; | |||
float yPos = 0.0; | |||
int scale = 0; | |||
for (int i = 0; i < 12; i++) { | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xOffset + i * 18.0f, 280.0f), module, ScaleQuantizer2::SCALE_LIGHT + i)); | |||
ui.calculateKeyboard(i, xSpace, xOffset, 230.0f, &xPos, &yPos, &scale); | |||
addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos, yPos), module, ScaleQuantizer2::KEY_LIGHT + scale)); | |||
} | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics | |||
using namespace rack_plugin_AmalgamatedHarmonics; | |||
RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, ScaleQuantizer2) { | |||
Model *modelScaleQuantizer2 = Model::create<ScaleQuantizer2, ScaleQuantizer2Widget>( "Amalgamated Harmonics", "ScaleQuantizer2", "Scale Quantizer MkII", QUANTIZER_TAG); | |||
return modelScaleQuantizer2; | |||
} | |||
@@ -0,0 +1,80 @@ | |||
#include "UI.hpp" | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
void UI::calculateKeyboard(int inKey, float spacing, float xOff, float yOff, float *x, float *y, int *scale) { | |||
// Sanitise input | |||
int key = inKey % 12; | |||
// White | |||
int whiteO [7] = {0, 1, 2, 3, 4, 5, 6}; // 'spaces' occupied by keys | |||
int whiteK [7] = {0, 2, 4, 5, 7, 9, 11}; // scale mapped to key | |||
// Black | |||
int blackO [5] = {0, 1, 3, 4, 5}; // 'spaces' occupied by keys | |||
int blackK [5] = {1, 3, 6, 8, 10}; // scale mapped to keys | |||
float bOffset = xOff + (spacing / 2); | |||
float bSpace = spacing; | |||
// Make an equilateral triangle pattern, tri height = base * (sqrt(3) / 2) | |||
float bDelta = bSpace * sqrt(3) * 0.5; | |||
// Are we black or white key?, check black keys | |||
for (int i = 0; i < 5; i++) { | |||
if (blackK[i] == key) { | |||
*x = bOffset + bSpace * blackO[i]; | |||
*y = yOff - bDelta; | |||
*scale = blackK[i]; | |||
return; | |||
} | |||
} | |||
int wOffset = xOff; | |||
int wSpace = spacing; | |||
for (int i = 0; i < 7; i++) { | |||
if (whiteK[i] == key) { | |||
*x = wOffset + wSpace * whiteO[i]; | |||
*y = yOff; | |||
*scale = whiteK[i]; | |||
return; | |||
} | |||
} | |||
} | |||
Vec UI::getPosition(int type, int xSlot, int ySlot, bool xDense, bool yDense) { | |||
float *xArray; | |||
float *yArray; | |||
switch(type) { | |||
case KNOB: | |||
if (xDense) { xArray = X_KNOB_COMPACT; } else { xArray = X_KNOB; } | |||
if (yDense) { yArray = Y_KNOB_COMPACT; } else { yArray = Y_KNOB; } | |||
break; | |||
case PORT: | |||
if (xDense) { xArray = X_PORT_COMPACT; } else { xArray = X_PORT; } | |||
if (yDense) { yArray = Y_PORT_COMPACT; } else { yArray = Y_PORT; } | |||
break; | |||
case BUTTON: | |||
if (xDense) { xArray = X_BUTTON_COMPACT; } else { xArray = X_BUTTON; } | |||
if (yDense) { yArray = Y_BUTTON_COMPACT; } else { yArray = Y_BUTTON; } | |||
break; | |||
case LIGHT: | |||
if (xDense) { xArray = X_LIGHT_COMPACT; } else { xArray = X_LIGHT; } | |||
if (yDense) { yArray = Y_LIGHT_COMPACT; } else { yArray = Y_LIGHT; } | |||
break; | |||
case TRIMPOT: | |||
if (xDense) { xArray = X_TRIMPOT_COMPACT; } else { xArray = X_TRIMPOT; } | |||
if (yDense) { yArray = Y_TRIMPOT_COMPACT; } else { yArray = Y_TRIMPOT; } | |||
break; | |||
default: | |||
if (xDense) { xArray = X_KNOB_COMPACT; } else { xArray = X_KNOB; } | |||
if (yDense) { yArray = Y_KNOB_COMPACT; } else { yArray = Y_KNOB; } | |||
} | |||
return Vec(xArray[0] + xArray[1] * xSlot, yArray[0] + yArray[1] * ySlot); | |||
} | |||
} // namespace rack_plugin_AmalgamatedHarmonics |
@@ -0,0 +1,206 @@ | |||
#pragma once | |||
#include <iostream> | |||
#include "AH.hpp" | |||
#include "componentlibrary.hpp" | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
struct ParamEvent { | |||
ParamEvent(int t, int i, float v) : pType(t), pId(i), value(v) {} | |||
int pType; | |||
int pId; | |||
float value; | |||
}; | |||
struct AHModule : Module { | |||
float delta; | |||
float rho; | |||
AHModule(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module(numParams, numInputs, numOutputs, numLights) { | |||
delta = engineGetSampleTime(); | |||
rho = engineGetSampleRate(); | |||
} | |||
void onSampleRateChange() override { | |||
delta = engineGetSampleTime(); | |||
rho = engineGetSampleRate(); | |||
} | |||
int stepX = 0; | |||
bool debugFlag = false; | |||
inline bool debugEnabled() { | |||
return debugFlag; | |||
} | |||
bool receiveEvents = false; | |||
int keepStateDisplay = 0; | |||
std::string paramState = ">"; | |||
virtual void receiveEvent(ParamEvent e) { | |||
paramState = ">"; | |||
keepStateDisplay = 0; | |||
} | |||
void step() override { | |||
stepX++; | |||
// Once we start stepping, we can process events | |||
receiveEvents = true; | |||
// Timeout for display | |||
keepStateDisplay++; | |||
if (keepStateDisplay > 50000) { | |||
paramState = ">"; | |||
} | |||
} | |||
}; | |||
struct StateDisplay : TransparentWidget { | |||
AHModule *module; | |||
int frame = 0; | |||
std::shared_ptr<Font> font; | |||
StateDisplay() { | |||
font = Font::load(assetPlugin(plugin, "res/EurostileBold.ttf")); | |||
} | |||
void draw(NVGcontext *vg) override { | |||
Vec pos = Vec(0, 15); | |||
nvgFontSize(vg, 16); | |||
nvgFontFaceId(vg, font->handle); | |||
nvgTextLetterSpacing(vg, -1); | |||
nvgFillColor(vg, nvgRGBA(255, 0, 0, 0xff)); | |||
char text[128]; | |||
snprintf(text, sizeof(text), "%s", module->paramState.c_str()); | |||
nvgText(vg, pos.x + 10, pos.y + 5, text, NULL); | |||
} | |||
}; | |||
struct AHParamWidget { // it's a mix-in | |||
int pType = -1; // Should be set by ste<>(), but if not this allows us to catch pwidgers we are not interested in | |||
int pId; | |||
AHModule *mod = NULL; | |||
virtual ParamEvent generateEvent(float value) { | |||
return ParamEvent(pType,pId,value); | |||
}; | |||
template <typename T = AHParamWidget> | |||
static void set(T *param, int pType, int pId) { | |||
param->pType = pType; | |||
param->pId = pId; | |||
} | |||
}; | |||
// Not going to monitor buttons | |||
struct AHButton : SVGSwitch, MomentarySwitch { | |||
AHButton() { | |||
addFrame(SVG::load(assetPlugin(plugin,"res/ComponentLibrary/AHButton.svg"))); | |||
} | |||
}; | |||
struct AHKnob : RoundKnob, AHParamWidget { | |||
void onChange(EventChange &e) override { | |||
// One off cast, don't want to subclass from ParamWidget, so have to grab it here | |||
if (!mod) { | |||
mod = static_cast<AHModule *>(this->module); | |||
} | |||
mod->receiveEvent(generateEvent(value)); | |||
RoundKnob::onChange(e); | |||
} | |||
}; | |||
struct AHKnobSnap : AHKnob { | |||
AHKnobSnap() { | |||
snap = true; | |||
setSVG(SVG::load(assetPlugin(plugin,"res/ComponentLibrary/AHKnob.svg"))); | |||
} | |||
}; | |||
struct AHKnobNoSnap : AHKnob { | |||
AHKnobNoSnap() { | |||
snap = false; | |||
setSVG(SVG::load(assetPlugin(plugin,"res/ComponentLibrary/AHKnob.svg"))); | |||
} | |||
}; | |||
struct AHTrimpotSnap : AHKnob { | |||
AHTrimpotSnap() { | |||
snap = true; | |||
setSVG(SVG::load(assetPlugin(plugin,"res/ComponentLibrary/AHTrimpot.svg"))); | |||
} | |||
}; | |||
struct AHTrimpotNoSnap : AHKnob { | |||
AHTrimpotNoSnap() { | |||
snap = false; | |||
setSVG(SVG::load(assetPlugin(plugin,"res/ComponentLibrary/AHTrimpot.svg"))); | |||
} | |||
}; | |||
struct UI { | |||
enum UIElement { | |||
KNOB = 0, | |||
PORT, | |||
BUTTON, | |||
LIGHT, | |||
TRIMPOT | |||
}; | |||
float Y_KNOB[2] = {50.8f, 56.0f}; // w.r.t 22 = 28.8 from bottom | |||
float Y_PORT[2] = {49.7f, 56.0f}; // 27.7 | |||
float Y_BUTTON[2] = {53.3f, 56.0f}; // 31.3 | |||
float Y_LIGHT[2] = {57.7f, 56.0f}; // 35.7 | |||
float Y_TRIMPOT[2] = {52.8f, 56.0f}; // 30.8 | |||
float Y_KNOB_COMPACT[2] = {30.1f, 35.0f}; // Calculated relative to PORT=29 and the deltas above | |||
float Y_PORT_COMPACT[2] = {29.0f, 35.0f}; | |||
float Y_BUTTON_COMPACT[2] = {32.6f, 35.0f}; | |||
float Y_LIGHT_COMPACT[2] = {37.0f, 35.0f}; | |||
float Y_TRIMPOT_COMPACT[2] = {32.1f, 35.0f}; | |||
float X_KNOB[2] = {12.5f, 48.0f}; // w.r.t 6.5 = 6 from left | |||
float X_PORT[2] = {11.5f, 48.0f}; // 5 | |||
float X_BUTTON[2] = {14.7f, 48.0f}; // 8.2 | |||
float X_LIGHT[2] = {19.1f, 48.0f}; // 12.6 | |||
float X_TRIMPOT[2] = {14.7f, 48.0f}; // 8.2 | |||
float X_KNOB_COMPACT[2] = {21.0f, 35.0f}; // 15 + 6, see calc above | |||
float X_PORT_COMPACT[2] = {20.0f, 35.0f}; // 15 + 5 | |||
float X_BUTTON_COMPACT[2] = {23.2f, 35.0f}; // 15 + 8.2 | |||
float X_LIGHT_COMPACT[2] = {27.6f, 35.0f}; // 15 + 12.6 | |||
float X_TRIMPOT_COMPACT[2] = {23.2f, 35.0f}; // 15 + 8.2 | |||
Vec getPosition(int type, int xSlot, int ySlot, bool xDense, bool yDense); | |||
/* From the numerical key on a keyboard (0 = C, 11 = B), spacing in px between white keys and a starting x and Y coordinate for the C key (in px) | |||
* calculate the actual X and Y coordinate for a key, and the scale note to which that key belongs (see Midi note mapping) | |||
* http://www.grantmuller.com/MidiReference/doc/midiReference/ScaleReference.html */ | |||
void calculateKeyboard(int inKey, float spacing, float xOff, float yOff, float *x, float *y, int *scale); | |||
}; | |||
} // namespace rack_plugin_AmalgamatedHarmonics |
@@ -0,0 +1,29 @@ | |||
BSD 3-Clause License | |||
Copyright (c) 2017, bogaudio | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are met: | |||
* Redistributions of source code must retain the above copyright notice, this | |||
list of conditions and the following disclaimer. | |||
* Redistributions in binary form must reproduce the above copyright notice, | |||
this list of conditions and the following disclaimer in the documentation | |||
and/or other materials provided with the distribution. | |||
* Neither the name of the copyright holder nor the names of its | |||
contributors may be used to endorse or promote products derived from | |||
this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -0,0 +1,107 @@ | |||
#pragma once | |||
#include <random> | |||
namespace rack_plugin_AmalgamatedHarmonics { | |||
namespace bogaudio_dsp { | |||
struct Generator { | |||
float _current = 0.0; | |||
Generator() {} | |||
virtual ~Generator() {} | |||
float current() { | |||
return _current; | |||
} | |||
float next() { | |||
return _current = _next(); | |||
} | |||
virtual float _next() = 0; | |||
}; | |||
class Seeds { | |||
private: | |||
std::mt19937 _generator; | |||
Seeds() { | |||
std::random_device rd; | |||
_generator.seed(rd()); | |||
} | |||
unsigned int _next() { | |||
return _generator(); | |||
} | |||
public: | |||
Seeds(const Seeds&) = delete; | |||
void operator=(const Seeds&) = delete; | |||
static Seeds& getInstance() { | |||
static Seeds instance; | |||
return instance; | |||
} | |||
static unsigned int next() { | |||
return getInstance()._next(); | |||
}; | |||
}; | |||
struct NoiseGenerator : Generator { | |||
std::minstd_rand _generator; // one of the faster options. | |||
NoiseGenerator() : _generator(Seeds::next()) {} | |||
}; | |||
struct WhiteNoiseGenerator : NoiseGenerator { | |||
std::uniform_real_distribution<float> _uniform; | |||
WhiteNoiseGenerator() : _uniform(-1.0, 1.0) {} | |||
virtual float _next() override { | |||
return _uniform(_generator); | |||
} | |||
}; | |||
template<typename G> | |||
struct BasePinkNoiseGenerator : NoiseGenerator { | |||
static const int _n = 6; | |||
G _g; | |||
G _gs[_n]; | |||
uint32_t _count = _g.next(); | |||
virtual float _next() override { | |||
// See: http://www.firstpr.com.au/dsp/pink-noise/ | |||
float sum = _g.next(); | |||
for (int i = 0, bit = 1; i < _n; ++i, bit <<= 1) { | |||
if (_count & bit) { | |||
sum += _gs[i].next(); | |||
} | |||
else { | |||
sum += _gs[i].current(); | |||
} | |||
} | |||
++_count; | |||
return sum / (float)(_n + 1); | |||
} | |||
}; | |||
struct PinkNoiseGenerator : BasePinkNoiseGenerator<WhiteNoiseGenerator> {}; | |||
struct RedNoiseGenerator : BasePinkNoiseGenerator<PinkNoiseGenerator> {}; | |||
struct GaussianNoiseGenerator : NoiseGenerator { | |||
std::normal_distribution<float> _normal; | |||
GaussianNoiseGenerator() : _normal(0, 1.0) {} | |||
virtual float _next() override { | |||
return _normal(_generator); | |||
} | |||
}; | |||
} // namespace bogaudio_dsp | |||
} // namespace rack_plugin_AmalgamatedHarmonics |
@@ -0,0 +1,3 @@ | |||
/build | |||
/dist | |||
plugin.* |
@@ -0,0 +1,85 @@ | |||
#### GEODESICS #### | |||
Copyright (c) 2018 Pierre Collard and Marc Boulé. All rights reserved. | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
#### GRAYSCALE #### | |||
Component Library graphics by Grayscale (http://grayscale.info/) | |||
Licensed under CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/) | |||
#### FUNDAMENTAL #### | |||
Copyright (c) 2016 Andrew Belt | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
#### VCV RACK #### | |||
Copyright 2016 Andrew Belt | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
#### VALLEY RACK FREE #### | |||
Copyright 2018 Dale Johnson | |||
1. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
#### NOHMAD #### | |||
MIT License | |||
Copyright (c) 2017 Joel Robichaud | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,27 @@ | |||
# Must follow the format in the Naming section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||
SLUG = Geodesics | |||
# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||
VERSION = 0.6.1 | |||
# FLAGS will be passed to both the C and C++ compiler | |||
FLAGS += | |||
CFLAGS += | |||
CXXFLAGS += | |||
# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. | |||
# Static libraries are fine. | |||
LDFLAGS += | |||
# Add .cpp and .c files to the build | |||
SOURCES += $(wildcard src/*.cpp) | |||
# Add files to the ZIP package when running `make dist` | |||
# The compiled plugin is automatically added. | |||
DISTRIBUTABLES += $(wildcard LICENSE*) res | |||
# If RACK_DIR is not defined when calling the Makefile, default to two levels above | |||
RACK_DIR ?= ../.. | |||
# Include the VCV Rack plugin Makefile framework | |||
include $(RACK_DIR)/plugin.mk |
@@ -0,0 +1,59 @@ | |||
# A Modular Collection for VCV Rack by Pyer and Marc Boulé | |||
Modules for [VCV Rack](https://vcvrack.com), available in the [plugin manager](https://vcvrack.com/plugins.html). | |||
Module concept and graphics by Pierre Collard (Pyer), code By Marc Boulé. [Website](https://www.pyer.be/geodesics.html) | |||
 | |||
## License | |||
Based on code from the Fundamental plugins by Andrew Belt and graphics from the Component Library by Wes Milholen. See ./LICENSE.txt for all licenses. | |||
# Modules <a id="modules"></a> | |||
Here are the modules. Short desctiptions are given below, while more detailed information can be found in the [user manual](GeodesicsUserManual061.pdf). A summary of changes done in version 0.6.1 can be found [here](GeodesicsWhatsNew061.pdf). | |||
* [Black Holes](#blackholes): Gravitational Voltage Controled Amplifiers. | |||
* [Pulsars](#pulsars): Neutron Powered Rotating Crossfader. | |||
* [Branes](#branes): Colliding Sample and Hold. | |||
* [Ions](#ions): Atomic Duophonic Voltage Sequencer. | |||
## Black Holes <a id="blackholes"></a> | |||
 | |||
A black whole attracts everything that gravitates around to its center, even audio and CV signals... BLACK HOLES is 8 vcas in two groups of 4, it’s also two mixers with 4 channels each. | |||
## Pulsars <a id="pulsars"></a> | |||
 | |||
A pulsar is a star turning on itself and emitting very high and precise frequencies on its spinning axis. PULSARS is a rotating 8 to 1 and 1 to 8 selector with crossfade in between each signal. It can be used to create cross fade mix of audio, complex wave tables with CV, standard sequential switch or extreme effects when turning at audio range speed. | |||
## Branes <a id="branes"></a> | |||
 | |||
Branes are multidimensional object involved in the ekpyrotic universe theory that describes two parallel universes colliding to create our world... BRANES is 2 groups of seven S&H driven by the same trigger source. Two of them receive added trigger clocks for polyrhythmic effects. | |||
## Ions <a id="ions"></a> | |||
 | |||
An Ionic bond describes two atoms that exchanges electrons. IONS is a two voices sequencer. While each voice has its own sequence, they can exchange their sequences as easily as an electron can jump from one atom to another. |
@@ -0,0 +1,9 @@ | |||
ALL_OBJ=\ | |||
src/BlackHoles.o \ | |||
src/BlankInfo.o \ | |||
src/BlankLogo.o \ | |||
src/Branes.o \ | |||
src/Geodesics.o \ | |||
src/GeoWidgets.o \ | |||
src/Ions.o \ | |||
src/Pulsars.o |
@@ -0,0 +1,7 @@ | |||
SLUG=Geodesics | |||
include ../../../build_plugin_pre.mk | |||
include make.objects | |||
include ../../../build_plugin_post.mk |
@@ -0,0 +1,7 @@ | |||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"> | |||
<title>C</title> | |||
<g> | |||
<path d="M10.579,0A10.54956,10.54956,0,0,0,0,10.47282a10.5,10.5,0,1,0,21,0A10.44793,10.44793,0,0,0,10.579,0Z" style="fill: #e5e5e5"/> | |||
<path d="M12.98207,6.19728l2.507-3.40019c2.95378,2.20882,4.24413,4.81517,4.24413,7.694a9.19575,9.19575,0,1,1-18.39149,0c0-2.92853,1.36489-5.58457,4.31867-7.79339L8.19175,6.14759C6.33036,7.53772,5.5858,8.9523,5.5858,10.4911a4.9392,4.9392,0,1,0,9.87839,0C15.46419,8.9523,14.819,7.58741,12.98207,6.19728Z" style="fill: #231f20"/> | |||
</g> | |||
</svg> |
@@ -0,0 +1,4 @@ | |||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"> | |||
<title>O trsp</title> | |||
<path d="M10.56615.02587A10.54957,10.54957,0,0,0-.01285,10.4987a10.5,10.5,0,1,0,21,0A10.44793,10.44793,0,0,0,10.56615.02587Z" style="fill: none"/> | |||
</svg> |
@@ -0,0 +1,8 @@ | |||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"> | |||
<title>jack</title> | |||
<path d="M11.5.57A10.93,10.93,0,1,0,22.43,11.5,10.94,10.94,0,0,0,11.5.57Z" style="fill: #e5e5e5"/> | |||
<path d="M11.5,2.27a9.23,9.23,0,1,0,9.23,9.23v0A9.24,9.24,0,0,0,11.5,2.27Zm0,16.8a7.57,7.57,0,1,1,7.57-7.57,7.57,7.57,0,0,1-7.57,7.57Z" style="fill: #969696"/> | |||
<path d="M11.5,6.31a5.19,5.19,0,1,0,5.19,5.19A5.19,5.19,0,0,0,11.5,6.31Z" style="fill: #0e0e0e"/> | |||
<path d="M11.5,17.26a5.76,5.76,0,1,1,5.76-5.76h0A5.76,5.76,0,0,1,11.5,17.26Zm0-10.78082A5.02083,5.02083,0,1,0,16.52083,11.5,5.02083,5.02083,0,0,0,11.5,6.47917Z" style="fill: #2b2b2b"/> | |||
<path d="M11.5.57A10.93,10.93,0,1,1,.57,11.5h0A10.94,10.94,0,0,1,11.5.57m0-.57A11.5,11.5,0,1,0,23,11.5,11.5,11.5,0,0,0,11.5,0Z" style="fill: #878787"/> | |||
</svg> |
@@ -0,0 +1,8 @@ | |||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="27" height="27" viewBox="0 0 27 27"> | |||
<title>knob</title> | |||
<path d="M.13,15.28c0,.16,0,.32.07.48a.49.49,0,0,0,.47.36H.73a.47.47,0,0,1,.46.36,13,13,0,0,0,1.52,3.65.47.47,0,0,1-.06.58H2.59a.51.51,0,0,0,0,.66,13.05,13.05,0,0,0,3.08,3.08.49.49,0,0,0,.65-.05l.06-.07A.5.5,0,0,1,7,24.26a12.24,12.24,0,0,0,3.62,1.51.47.47,0,0,1,.36.46v.1a.5.5,0,0,0,.42.49,13.29,13.29,0,0,0,4.36,0,.5.5,0,0,0,.42-.49v-.1a.47.47,0,0,1,.36-.46,12.24,12.24,0,0,0,3.62-1.51.5.5,0,0,1,.59.07l.06.07a.5.5,0,0,0,.65.06l0,0c.26-.19.52-.39.76-.59s.5-.44.74-.67l.08-.08a13.27,13.27,0,0,0,1.55-1.87.51.51,0,0,0-.09-.5h-.06a.47.47,0,0,1-.06-.58,13,13,0,0,0,1.52-3.65.47.47,0,0,1,.46-.36h.06a.47.47,0,0,0,.33-.13A13.43,13.43,0,0,0,27,13.52a13.84,13.84,0,0,0-.2-2.25.49.49,0,0,0-.47-.36.45.45,0,0,1-.45-.37,12.87,12.87,0,0,0-1.51-3.68.47.47,0,0,1,.06-.58.5.5,0,0,0,0-.65,13.29,13.29,0,0,0-3.08-3.09.5.5,0,0,0-.65,0,.46.46,0,0,1-.58.07,12.4,12.4,0,0,0-3.7-1.54.46.46,0,0,1-.35-.45.49.49,0,0,0-.42-.49A13.33,13.33,0,0,0,12.56,0h-.21l-.84.11-.25,0a.48.48,0,0,0-.39.48.46.46,0,0,1-.35.45,12.4,12.4,0,0,0-3.7,1.54.46.46,0,0,1-.58-.07.49.49,0,0,0-.55-.1A13.71,13.71,0,0,0,2.42,5.8a.5.5,0,0,0,.09.48.47.47,0,0,1,.06.58,12.87,12.87,0,0,0-1.51,3.68.45.45,0,0,1-.45.37H.54a.46.46,0,0,0-.3.11A13.67,13.67,0,0,0,0,13.52c0,.42,0,.83.06,1.24C.08,14.93.1,15.11.13,15.28Z" style="fill: #2d2d2d"/> | |||
<path d="M26.8,11.26a.49.49,0,0,0-.47-.36h-2.1a1,1,0,0,1-.92-.65,9.94,9.94,0,0,0-.58-1.39,1,1,0,0,1,.19-1.11l1.49-1.48a.5.5,0,0,0,0-.65,13.24,13.24,0,0,0-3.08-3.08.49.49,0,0,0-.65,0L19.2,4.08a1,1,0,0,1-1.11.19,9.94,9.94,0,0,0-1.39-.58,1,1,0,0,1-.65-.92V.67a.49.49,0,0,0-.4-.48l-.17,0c-.27,0-.55-.08-.83-.1S14,0,13.72,0h-.24c-.44,0-.87,0-1.3.07L12,.1l-.78.11a.5.5,0,0,0-.32.46v2.1a1,1,0,0,1-.65.92,9.94,9.94,0,0,0-1.39.58A1,1,0,0,1,7.7,4.08L6.22,2.59a.49.49,0,0,0-.47-.13A13.49,13.49,0,0,0,2.39,5.84a.51.51,0,0,0,.1.43L4,7.75a1,1,0,0,1,.19,1.11,9.94,9.94,0,0,0-.58,1.39,1,1,0,0,1-.92.65h-2a.51.51,0,0,0-.46.34c0,.18-.05.36-.08.53l-.06.49c0,.41-.06.82-.06,1.24v0c0,.41,0,.81.06,1.21,0,.18,0,.36.07.54s0,.31.07.47a.49.49,0,0,0,.47.36h2.1a1,1,0,0,1,.92.65,9.94,9.94,0,0,0,.58,1.39,1,1,0,0,1-.19,1.11L2.59,20.73a.5.5,0,0,0,0,.65,13.24,13.24,0,0,0,3.08,3.08.49.49,0,0,0,.65-.05L7.8,22.92a1,1,0,0,1,1.11-.19,9.94,9.94,0,0,0,1.39.58,1,1,0,0,1,.65.92v2.1a.49.49,0,0,0,.4.48l.17,0c.27,0,.55.08.83.1s.62,0,.93,0h.24c.44,0,.87,0,1.3-.07l.23,0,.78-.11a.5.5,0,0,0,.32-.46v-2.1a1,1,0,0,1,.65-.92,9.94,9.94,0,0,0,1.39-.58,1,1,0,0,1,1.11.19l1.48,1.49a.49.49,0,0,0,.47.13,13.49,13.49,0,0,0,3.36-3.38.51.51,0,0,0-.1-.43L23,19.25a1,1,0,0,1-.19-1.11,9.94,9.94,0,0,0,.58-1.39,1,1,0,0,1,.92-.65h2.1a.44.44,0,0,0,.33-.14A13.32,13.32,0,0,0,27,13.5,13.82,13.82,0,0,0,26.8,11.26Z" style="fill: #3d3d3d"/> | |||
<circle cx="13.48" cy="13.5" r="10.34" style="fill: #545454"/> | |||
<path d="M14.41.05V4.2a1.28,1.28,0,0,0,1,1.21,8.31,8.31,0,1,1-3.91,0,1.23,1.23,0,0,0,1-1.2V.05L11.92.1V4.23a.65.65,0,0,1-.5.64,8.86,8.86,0,0,0,1.64,17.46h.44a8.86,8.86,0,0,0,2-17.48A.66.66,0,0,1,15,4.21V.09Z" style="fill: #b2b2b2"/> | |||
<path d="M13.5,0c-.33,0-.65,0-1,.05V4.2a1.22,1.22,0,0,1-1,1.2,8.32,8.32,0,1,0,3.91,0,1.27,1.27,0,0,1-1-1.21V.05C14.12,0,13.82,0,13.5,0Z" style="fill: #f3f3f3"/> | |||
</svg> |
@@ -0,0 +1,6 @@ | |||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"> | |||
<title>button</title> | |||
<circle cx="7.5" cy="7.5" r="7.5"/> | |||
<path d="M7.5,1.59A5.91,5.91,0,1,0,13.41,7.5,5.93,5.93,0,0,0,7.5,1.59Z" style="fill: #707070"/> | |||
<path d="M7.5,1.59A5.91,5.91,0,1,1,1.59,7.5,5.92,5.92,0,0,1,7.5,1.59M7.5,1A6.5,6.5,0,1,0,14,7.5,6.5,6.5,0,0,0,7.5,1Z" style="fill: gray"/> | |||
</svg> |
@@ -0,0 +1,6 @@ | |||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"> | |||
<title>button_push-01</title> | |||
<circle cx="7.5" cy="7.5" r="7.5"/> | |||
<path d="M7.5,1.6A5.84765,5.84765,0,0,0,1.6,7.5a5.84765,5.84765,0,0,0,5.9,5.9,5.84765,5.84765,0,0,0,5.9-5.9A5.84765,5.84765,0,0,0,7.5,1.6Z" style="fill: #5c5e5e"/> | |||
<path d="M7.5,1.6a5.84765,5.84765,0,0,1,5.9,5.9,5.84765,5.84765,0,0,1-5.9,5.9A5.84765,5.84765,0,0,1,1.6,7.5,5.84765,5.84765,0,0,1,7.5,1.6m0-.6A6.5,6.5,0,1,0,14,7.5,6.487,6.487,0,0,0,7.5,1Z" style="fill: #424242"/> | |||
</svg> |
@@ -0,0 +1,412 @@ | |||
//*********************************************************************************************** | |||
//Gravitational Voltage Controled Amplifiers module for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt and graphics | |||
// from the Component Library by Wes Milholen. | |||
//See ./LICENSE.txt for all licenses | |||
//See ./res/fonts/ for font licenses | |||
// | |||
//*********************************************************************************************** | |||
#include "Geodesics.hpp" | |||
namespace rack_plugin_Geodesics { | |||
struct BlackHoles : Module { | |||
enum ParamIds { | |||
ENUMS(LEVEL_PARAMS, 8),// -1.0f to 1.0f knob, set to default (0.0f) when using CV input | |||
ENUMS(EXP_PARAMS, 2),// push-button | |||
WORMHOLE_PARAM, | |||
ENUMS(CVLEVEL_PARAMS, 2),// push-button | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
ENUMS(IN_INPUTS, 8),// -10 to 10 V | |||
ENUMS(LEVELCV_INPUTS, 8),// 0 to 10V CV or -5 to 5V depeding on cvMode | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
ENUMS(OUT_OUTPUTS, 8),// input * [-1;1] when input connected, else [-10;10] CV when input unconnected | |||
ENUMS(BLACKHOLE_OUTPUTS, 2), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(EXP_LIGHTS, 2), | |||
ENUMS(WORMHOLE_LIGHT, 2),// room for WhiteRed | |||
ENUMS(CVALEVEL_LIGHTS, 2),// White, but two lights (light 0 is cvMode bit = 0, light 1 is cvMode bit = 1) | |||
ENUMS(CVBLEVEL_LIGHTS, 2),// White, but two lights | |||
NUM_LIGHTS | |||
}; | |||
// Constants | |||
static constexpr float expBase = 50.0f; | |||
// Need to save, with reset | |||
bool isExponential[2]; | |||
bool wormhole; | |||
int cvMode;// 0 is -5v to 5v, 1 is -10v to 10v; bit 0 is upper BH, bit 1 is lower BH | |||
// Need to save, no reset | |||
int panelTheme; | |||
// No need to save, with reset | |||
// none | |||
// No need to save, no reset | |||
SchmittTrigger expTriggers[2]; | |||
SchmittTrigger cvLevelTriggers[2]; | |||
SchmittTrigger wormholeTrigger; | |||
BlackHoles() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
// Need to save, no reset | |||
panelTheme = 0; | |||
// No need to save, no reset | |||
expTriggers[0].reset(); | |||
expTriggers[1].reset(); | |||
onReset(); | |||
} | |||
// widgets are not yet created when module is created | |||
// even if widgets not created yet, can use params[] and should handle 0.0f value since step may call | |||
// this before widget creation anyways | |||
// called from the main thread if by constructor, called by engine thread if right-click initialization | |||
// when called by constructor, module is created before the first step() is called | |||
void onReset() override { | |||
// Need to save, with reset | |||
cvMode = 0x3; | |||
isExponential[0] = false; | |||
isExponential[1] = false; | |||
wormhole = true; | |||
// No need to save, with reset | |||
// none | |||
} | |||
// widgets randomized before onRandomize() is called | |||
// called by engine thread if right-click randomize | |||
void onRandomize() override { | |||
// Need to save, with reset | |||
for (int i = 0; i < 2; i++) { | |||
isExponential[i] = (randomu32() % 2) > 0; | |||
} | |||
wormhole = (randomu32() % 2) > 0; | |||
// No need to save, with reset | |||
// none | |||
} | |||
// called by main thread | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// Need to save (reset or not) | |||
// isExponential | |||
json_object_set_new(rootJ, "isExponential0", json_real(isExponential[0])); | |||
json_object_set_new(rootJ, "isExponential1", json_real(isExponential[1])); | |||
// wormhole | |||
json_object_set_new(rootJ, "wormhole", json_boolean(wormhole)); | |||
// panelTheme | |||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||
// cvMode | |||
json_object_set_new(rootJ, "cvMode", json_integer(cvMode)); | |||
return rootJ; | |||
} | |||
// widgets have their fromJson() called before this fromJson() is called | |||
// called by main thread | |||
void fromJson(json_t *rootJ) override { | |||
// Need to save (reset or not) | |||
// isExponential | |||
json_t *isExponential0J = json_object_get(rootJ, "isExponential0"); | |||
if (isExponential0J) | |||
isExponential[0] = json_real_value(isExponential0J); | |||
json_t *isExponential1J = json_object_get(rootJ, "isExponential1"); | |||
if (isExponential1J) | |||
isExponential[1] = json_real_value(isExponential1J); | |||
// wormhole | |||
json_t *wormholeJ = json_object_get(rootJ, "wormhole"); | |||
if (wormholeJ) | |||
wormhole = json_is_true(wormholeJ); | |||
// panelTheme | |||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||
if (panelThemeJ) | |||
panelTheme = json_integer_value(panelThemeJ); | |||
// cvMode | |||
json_t *cvModeJ = json_object_get(rootJ, "cvMode"); | |||
if (cvModeJ) | |||
cvMode = json_integer_value(cvModeJ); | |||
// No need to save, with reset | |||
// none | |||
} | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
// Exponential buttons | |||
for (int i = 0; i < 2; i++) | |||
if (expTriggers[i].process(params[EXP_PARAMS + i].value)) { | |||
isExponential[i] = !isExponential[i]; | |||
} | |||
// Wormhole buttons | |||
if (wormholeTrigger.process(params[WORMHOLE_PARAM].value)) { | |||
wormhole = ! wormhole; | |||
} | |||
// CV Level buttons | |||
for (int i = 0; i < 2; i++) { | |||
if (cvLevelTriggers[i].process(params[CVLEVEL_PARAMS + i].value)) | |||
cvMode ^= (0x1 << i); | |||
} | |||
// BlackHole 0 all outputs | |||
float blackHole0 = 0.0f; | |||
float inputs0[4] = {10.0f, 10.0f, 10.0f, 10.0f};// default to generate CV when no input connected | |||
for (int i = 0; i < 4; i++) | |||
if (inputs[IN_INPUTS + i].active) | |||
inputs0[i] = inputs[IN_INPUTS + i].value; | |||
for (int i = 0; i < 4; i++) { | |||
float chanVal = calcChannel(inputs0[i], params[LEVEL_PARAMS + i], inputs[LEVELCV_INPUTS + i], isExponential[0], cvMode & 0x1); | |||
outputs[OUT_OUTPUTS + i].value = chanVal; | |||
blackHole0 += chanVal; | |||
} | |||
outputs[BLACKHOLE_OUTPUTS + 0].value = clamp(blackHole0, -10.0f, 10.0f); | |||
// BlackHole 1 all outputs | |||
float blackHole1 = 0.0f; | |||
float inputs1[4] = {10.0f, 10.0f, 10.0f, 10.0f};// default to generate CV when no input connected | |||
bool allUnconnected = true; | |||
for (int i = 0; i < 4; i++) | |||
if (inputs[IN_INPUTS + i + 4].active) { | |||
inputs1[i] = inputs[IN_INPUTS + i + 4].value; | |||
allUnconnected = false; | |||
} | |||
if (allUnconnected && wormhole) | |||
for (int i = 0; i < 4; i++) | |||
inputs1[i] = blackHole0; | |||
for (int i = 0; i < 4; i++) { | |||
float chanVal = calcChannel(inputs1[i], params[LEVEL_PARAMS + i + 4], inputs[LEVELCV_INPUTS + i + 4], isExponential[1], cvMode >> 1); | |||
outputs[OUT_OUTPUTS + i + 4].value = chanVal; | |||
blackHole1 += chanVal; | |||
} | |||
outputs[BLACKHOLE_OUTPUTS + 1].value = clamp(blackHole1, -10.0f, 10.0f); | |||
// Wormhole light | |||
lights[WORMHOLE_LIGHT + 0].value = ((wormhole && allUnconnected) ? 1.0f : 0.0f); | |||
lights[WORMHOLE_LIGHT + 1].value = ((wormhole && !allUnconnected) ? 1.0f : 0.0f); | |||
// isExponential lights | |||
for (int i = 0; i < 2; i++) | |||
lights[EXP_LIGHTS + i].value = isExponential[i] ? 1.0f : 0.0f; | |||
// CV Level lights | |||
lights[CVALEVEL_LIGHTS + 0].value = (cvMode & 0x1) == 0 ? 1.0f : 0.0f; | |||
lights[CVALEVEL_LIGHTS + 1].value = 1.0f - lights[CVALEVEL_LIGHTS + 0].value; | |||
lights[CVBLEVEL_LIGHTS + 0].value = (cvMode & 0x2) == 0 ? 1.0f : 0.0f; | |||
lights[CVBLEVEL_LIGHTS + 1].value = 1.0f - lights[CVBLEVEL_LIGHTS + 0].value; | |||
}// step() | |||
float calcChannel(float in, Param &level, Input &levelCV, bool isExp, int cvMode) { | |||
float levCv = levelCV.active ? (levelCV.value / (cvMode != 0 ? 10.0f : 5.0f)) : 0.0f; | |||
float lev = clamp(level.value + levCv, -1.0f, 1.0f); | |||
if (isExp) { | |||
float newlev = rescale(powf(expBase, fabs(lev)), 1.0f, expBase, 0.0f, 1.0f); | |||
if (lev < 0.0f) | |||
newlev *= -1.0f; | |||
lev = newlev; | |||
} | |||
float ret = lev * in; | |||
return ret; | |||
} | |||
}; | |||
struct BlackHolesWidget : ModuleWidget { | |||
struct PanelThemeItem : MenuItem { | |||
BlackHoles *module; | |||
int theme; | |||
void onAction(EventAction &e) override { | |||
module->panelTheme = theme; | |||
} | |||
void step() override { | |||
rightText = (module->panelTheme == theme) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
BlackHoles *module = dynamic_cast<BlackHoles*>(this->module); | |||
assert(module); | |||
MenuLabel *themeLabel = new MenuLabel(); | |||
themeLabel->text = "Panel Theme"; | |||
menu->addChild(themeLabel); | |||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||
lightItem->text = lightPanelID;// Geodesics.hpp | |||
lightItem->module = module; | |||
lightItem->theme = 0; | |||
menu->addChild(lightItem); | |||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||
darkItem->text = darkPanelID;// Geodesics.hpp | |||
darkItem->module = module; | |||
darkItem->theme = 1; | |||
//menu->addChild(darkItem); | |||
return menu; | |||
} | |||
BlackHolesWidget(BlackHoles *module) : ModuleWidget(module) { | |||
// Main panel from Inkscape | |||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BlackHolesBG-01.svg"))); | |||
//panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BlackHolesBG-02.svg")));// no dark pannel for now | |||
box.size = panel->box.size; | |||
panel->mode = &module->panelTheme; | |||
addChild(panel); | |||
// Screws | |||
// part of svg panel, no code required | |||
float colRulerCenter = box.size.x / 2.0f; | |||
static constexpr float rowRulerBlack0 = 108.5f; | |||
static constexpr float rowRulerBlack1 = 272.5f; | |||
static constexpr float radiusIn = 30.0f; | |||
static constexpr float radiusOut = 61.0f; | |||
static constexpr float offsetL = 53.0f; | |||
static constexpr float offsetS = 30.0f; | |||
// BlackHole0 knobs | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerBlack0 - radiusOut), module, BlackHoles::LEVEL_PARAMS + 0, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnobRight>(Vec(colRulerCenter + radiusOut, rowRulerBlack0), module, BlackHoles::LEVEL_PARAMS + 1, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnobBottom>(Vec(colRulerCenter, rowRulerBlack0 + radiusOut), module, BlackHoles::LEVEL_PARAMS + 2, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnobLeft>(Vec(colRulerCenter - radiusOut, rowRulerBlack0), module, BlackHoles::LEVEL_PARAMS + 3, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// BlackHole0 level CV inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerBlack0 - radiusIn), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusIn, rowRulerBlack0), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 1, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerBlack0 + radiusIn), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 2, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusIn, rowRulerBlack0), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 3, &module->panelTheme)); | |||
// BlackHole0 inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetS, rowRulerBlack0 - offsetL), Port::INPUT, module, BlackHoles::IN_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetL, rowRulerBlack0 - offsetS), Port::INPUT, module, BlackHoles::IN_INPUTS + 1, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetS, rowRulerBlack0 + offsetL), Port::INPUT, module, BlackHoles::IN_INPUTS + 2, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetL, rowRulerBlack0 + offsetS), Port::INPUT, module, BlackHoles::IN_INPUTS + 3, &module->panelTheme)); | |||
// BlackHole0 outputs | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetS, rowRulerBlack0 - offsetL), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 0, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetL, rowRulerBlack0 + offsetS), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 1, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetS, rowRulerBlack0 + offsetL), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 2, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetL, rowRulerBlack0 - offsetS), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 3, &module->panelTheme)); | |||
// BlackHole0 center output | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerBlack0), Port::OUTPUT, module, BlackHoles::BLACKHOLE_OUTPUTS + 0, &module->panelTheme)); | |||
// BlackHole1 knobs | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerBlack1 - radiusOut), module, BlackHoles::LEVEL_PARAMS + 4, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnobRight>(Vec(colRulerCenter + radiusOut, rowRulerBlack1), module, BlackHoles::LEVEL_PARAMS + 5, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnobBottom>(Vec(colRulerCenter, rowRulerBlack1 + radiusOut), module, BlackHoles::LEVEL_PARAMS + 6, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnobLeft>(Vec(colRulerCenter - radiusOut, rowRulerBlack1), module, BlackHoles::LEVEL_PARAMS + 7, -1.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// BlackHole1 level CV inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerBlack1 - radiusIn), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 4, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusIn, rowRulerBlack1), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 5, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerBlack1 + radiusIn), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 6, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusIn, rowRulerBlack1), Port::INPUT, module, BlackHoles::LEVELCV_INPUTS + 7, &module->panelTheme)); | |||
// BlackHole1 inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetS, rowRulerBlack1 - offsetL), Port::INPUT, module, BlackHoles::IN_INPUTS + 4, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetL, rowRulerBlack1 - offsetS), Port::INPUT, module, BlackHoles::IN_INPUTS + 5, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetS, rowRulerBlack1 + offsetL), Port::INPUT, module, BlackHoles::IN_INPUTS + 6, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetL, rowRulerBlack1 + offsetS), Port::INPUT, module, BlackHoles::IN_INPUTS + 7, &module->panelTheme)); | |||
// BlackHole1 outputs | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetS, rowRulerBlack1 - offsetL), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 4, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetL, rowRulerBlack1 + offsetS), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 5, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetS, rowRulerBlack1 + offsetL), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 6, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetL, rowRulerBlack1 - offsetS), Port::OUTPUT, module, BlackHoles::OUT_OUTPUTS + 7, &module->panelTheme)); | |||
// BlackHole1 center output | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerBlack1), Port::OUTPUT, module, BlackHoles::BLACKHOLE_OUTPUTS + 1, &module->panelTheme)); | |||
static constexpr float offsetButtonsX = 62.0f; | |||
static constexpr float offsetButtonsY = 64.0f; | |||
static constexpr float offsetLedVsBut = 9.0f; | |||
static constexpr float offsetLedVsButS = 5.0f;// small | |||
static constexpr float offsetLedVsButL = 12.0f;// large | |||
// BlackHole0 Exp button and light | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetButtonsX, rowRulerBlack0 + offsetButtonsY), module, BlackHoles::EXP_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetButtonsX + offsetLedVsBut, rowRulerBlack0 + offsetButtonsY - offsetLedVsBut - 1.0f), module, BlackHoles::EXP_LIGHTS + 0)); | |||
// BlackHole1 Exp button and light | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetButtonsX, rowRulerBlack1 + offsetButtonsY), module, BlackHoles::EXP_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetButtonsX + offsetLedVsBut, rowRulerBlack1 + offsetButtonsY - offsetLedVsBut -1.0f), module, BlackHoles::EXP_LIGHTS + 1)); | |||
// Wormhole button and light | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetButtonsX, rowRulerBlack1 - offsetButtonsY), module, BlackHoles::WORMHOLE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter - offsetButtonsX + offsetLedVsBut, rowRulerBlack1 - offsetButtonsY + offsetLedVsBut), module, BlackHoles::WORMHOLE_LIGHT)); | |||
// CV Level A button and light | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetButtonsX, rowRulerBlack0 + offsetButtonsY), module, BlackHoles::CVLEVEL_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetButtonsX + offsetLedVsButL, rowRulerBlack0 + offsetButtonsY + offsetLedVsButS), module, BlackHoles::CVALEVEL_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetButtonsX + offsetLedVsButS, rowRulerBlack0 + offsetButtonsY + offsetLedVsButL), module, BlackHoles::CVALEVEL_LIGHTS + 1)); | |||
// CV Level B button and light | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetButtonsX, rowRulerBlack1 + offsetButtonsY), module, BlackHoles::CVLEVEL_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetButtonsX + offsetLedVsButL, rowRulerBlack1 + offsetButtonsY + offsetLedVsButS), module, BlackHoles::CVBLEVEL_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetButtonsX + offsetLedVsButS, rowRulerBlack1 + offsetButtonsY + offsetLedVsButL), module, BlackHoles::CVBLEVEL_LIGHTS + 1)); | |||
} | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
RACK_PLUGIN_MODEL_INIT(Geodesics, BlackHoles) { | |||
Model *modelBlackHoles = Model::create<BlackHoles, BlackHolesWidget>("Geodesics", "BlackHoles", "BlackHoles", AMPLIFIER_TAG); | |||
return modelBlackHoles; | |||
} | |||
/*CHANGE LOG | |||
0.6.1: | |||
add CV level modes buttons and lights | |||
change CV level behavior | |||
0.6.0: | |||
created | |||
*/ |
@@ -0,0 +1,109 @@ | |||
//*********************************************************************************************** | |||
//Blank-Panel Info for VCV Rack by Pierre Collard and Marc Boulé | |||
//*********************************************************************************************** | |||
#include "Geodesics.hpp" | |||
namespace rack_plugin_Geodesics { | |||
struct BlankInfo : Module { | |||
int panelTheme = 0; | |||
BlankInfo() : Module(0, 0, 0, 0) { | |||
onReset(); | |||
} | |||
void onReset() override { | |||
} | |||
void onRandomize() override { | |||
} | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// panelTheme | |||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
// panelTheme | |||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||
if (panelThemeJ) | |||
panelTheme = json_integer_value(panelThemeJ); | |||
} | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
} | |||
}; | |||
struct BlankInfoWidget : ModuleWidget { | |||
struct PanelThemeItem : MenuItem { | |||
BlankInfo *module; | |||
int theme; | |||
void onAction(EventAction &e) override { | |||
module->panelTheme = theme; | |||
} | |||
void step() override { | |||
rightText = (module->panelTheme == theme) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
BlankInfo *module = dynamic_cast<BlankInfo*>(this->module); | |||
assert(module); | |||
MenuLabel *themeLabel = new MenuLabel(); | |||
themeLabel->text = "Panel Theme"; | |||
menu->addChild(themeLabel); | |||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||
lightItem->text = lightPanelID;// Geodesics.hpp | |||
lightItem->module = module; | |||
lightItem->theme = 0; | |||
menu->addChild(lightItem); | |||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||
darkItem->text = darkPanelID;// Geodesics.hpp | |||
darkItem->module = module; | |||
darkItem->theme = 1; | |||
//menu->addChild(darkItem); | |||
return menu; | |||
} | |||
BlankInfoWidget(BlankInfo *module) : ModuleWidget(module) { | |||
// Main panel from Inkscape | |||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BlankInfo-01.svg"))); | |||
//panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/BlankInfo-02.svg")));// no dark pannel for now | |||
box.size = panel->box.size; | |||
panel->mode = &module->panelTheme; | |||
addChild(panel); | |||
// Screws | |||
// part of svg panel, no code required | |||
} | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
RACK_PLUGIN_MODEL_INIT(Geodesics, BlankInfo) { | |||
Model *modelBlankInfo = Model::create<BlankInfo, BlankInfoWidget>("Geodesics", "Blank-Panel Info", "MISC - Blank-Panel Info", BLANK_TAG); | |||
return modelBlankInfo; | |||
} |
@@ -0,0 +1,178 @@ | |||
//*********************************************************************************************** | |||
//Blank-Panel Logo for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt | |||
//See ./LICENSE.txt for all licenses | |||
//*********************************************************************************************** | |||
#include "Geodesics.hpp" | |||
namespace rack_plugin_Geodesics { | |||
// From Fundamental LFO.cpp | |||
struct LowFrequencyOscillator { | |||
float phase = 0.0f; | |||
float freq = 1.0f; | |||
LowFrequencyOscillator() {} | |||
void setPitch(float pitch) { | |||
pitch = fminf(pitch, 8.0f); | |||
freq = powf(2.0f, pitch); | |||
} | |||
void step(float dt) { | |||
float deltaPhase = fminf(freq * dt, 0.5f); | |||
phase += deltaPhase; | |||
if (phase >= 1.0f) | |||
phase -= 1.0f; | |||
} | |||
float sqr() { | |||
return (phase < 0.5f) ? 2.0f : 0.0f; | |||
} | |||
}; | |||
//***************************************************************************** | |||
struct BlankLogo : Module { | |||
enum ParamIds { | |||
CLK_FREQ_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
OUT_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
NUM_LIGHTS | |||
}; | |||
int panelTheme = 0; | |||
float clkValue; | |||
int stepIndex; | |||
float song[5] = {7.0f/12.0f, 9.0f/12.0f, 5.0f/12.0f, 5.0f/12.0f - 1.0f, 0.0f/12.0f}; | |||
LowFrequencyOscillator oscillatorClk; | |||
SchmittTrigger clkTrigger; | |||
BlankLogo() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
clkTrigger.reset(); | |||
onReset(); | |||
} | |||
void onReset() override { | |||
clkValue = 0.0f; | |||
stepIndex = 0; | |||
} | |||
void onRandomize() override { | |||
} | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// panelTheme | |||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
// panelTheme | |||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||
if (panelThemeJ) | |||
panelTheme = json_integer_value(panelThemeJ); | |||
} | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
if (outputs[OUT_OUTPUT].active) { | |||
// CLK | |||
oscillatorClk.setPitch(params[CLK_FREQ_PARAM].value); | |||
oscillatorClk.step(engineGetSampleTime()); | |||
float clkValue = oscillatorClk.sqr(); | |||
if (clkTrigger.process(clkValue)) { | |||
stepIndex++; | |||
if (stepIndex >= 5) | |||
stepIndex = 0; | |||
outputs[OUT_OUTPUT].value = song[stepIndex]; | |||
} | |||
} | |||
} | |||
}; | |||
struct BlankLogoWidget : ModuleWidget { | |||
struct PanelThemeItem : MenuItem { | |||
BlankLogo *module; | |||
int theme; | |||
void onAction(EventAction &e) override { | |||
module->panelTheme = theme; | |||
} | |||
void step() override { | |||
rightText = (module->panelTheme == theme) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
BlankLogo *module = dynamic_cast<BlankLogo*>(this->module); | |||
assert(module); | |||
MenuLabel *themeLabel = new MenuLabel(); | |||
themeLabel->text = "Panel Theme"; | |||
menu->addChild(themeLabel); | |||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||
lightItem->text = lightPanelID;// Geodesics.hpp | |||
lightItem->module = module; | |||
lightItem->theme = 0; | |||
menu->addChild(lightItem); | |||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||
darkItem->text = darkPanelID;// Geodesics.hpp | |||
darkItem->module = module; | |||
darkItem->theme = 1; | |||
//menu->addChild(darkItem); | |||
return menu; | |||
} | |||
BlankLogoWidget(BlankLogo *module) : ModuleWidget(module) { | |||
// Main panel from Inkscape | |||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BlankLogoBG-01.svg"))); | |||
//panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/BlankLogo-02.svg")));// no dark pannel for now | |||
box.size = panel->box.size; | |||
panel->mode = &module->panelTheme; | |||
addChild(panel); | |||
// Screws | |||
// part of svg panel, no code required | |||
addParam(createParamCentered<BlankCKnob>(Vec(29.5f,74.2f), module, BlankLogo::CLK_FREQ_PARAM, -2.0f, 4.0f, 1.0f));// 120 BMP when default value | |||
addOutput(createOutputCentered<BlankPort>(Vec(29.5f,187.5f), module, BlankLogo::OUT_OUTPUT)); | |||
} | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
RACK_PLUGIN_MODEL_INIT(Geodesics, BlankLogo) { | |||
Model *modelBlankLogo = Model::create<BlankLogo, BlankLogoWidget>("Geodesics", "Blank-Panel Logo", "MISC - Blank-Panel Logo", BLANK_TAG); | |||
return modelBlankLogo; | |||
} |
@@ -0,0 +1,427 @@ | |||
//*********************************************************************************************** | |||
//Colliding Sample and Hold module for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt and graphics | |||
// from the Component Library by Wes Milholen. | |||
//Also based on code from Joel Robichaud's Nohmad Noise module | |||
//See ./LICENSE.txt for all licenses | |||
// | |||
//*********************************************************************************************** | |||
#include <dsp/filter.hpp> | |||
#include <random> | |||
#include "Geodesics.hpp" | |||
namespace rack_plugin_Geodesics { | |||
// By Joel Robichaud - Nohmad Noise module | |||
struct NoiseGenerator { | |||
std::mt19937 rng; | |||
std::uniform_real_distribution<float> uniform; | |||
NoiseGenerator() : uniform(-1.0f, 1.0f) { | |||
rng.seed(std::random_device()()); | |||
} | |||
float white() { | |||
return uniform(rng); | |||
} | |||
}; | |||
//***************************************************************************** | |||
// By Joel Robichaud - Nohmad Noise module | |||
struct PinkFilter { | |||
float b0, b1, b2, b3, b4, b5, b6; // Coefficients | |||
float y; // Out | |||
void process(float x) { | |||
b0 = 0.99886f * b0 + x * 0.0555179f; | |||
b1 = 0.99332f * b1 + x * 0.0750759f; | |||
b2 = 0.96900f * b2 + x * 0.1538520f; | |||
b3 = 0.86650f * b3 + x * 0.3104856f; | |||
b4 = 0.55000f * b4 + x * 0.5329522f; | |||
b5 = -0.7616f * b5 - x * 0.0168980f; | |||
y = b0 + b1 + b2 + b3 + b4 + b5 + b6 + x * 0.5362f; | |||
b6 = x * 0.115926f; | |||
} | |||
float pink() { | |||
return y; | |||
} | |||
}; | |||
//***************************************************************************** | |||
struct Branes : Module { | |||
enum ParamIds { | |||
ENUMS(TRIG_BYPASS_PARAMS, 2), | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
ENUMS(IN_INPUTS, 14), | |||
ENUMS(TRIG_INPUTS, 2), | |||
ENUMS(TRIG_BYPASS_INPUTS, 2), | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
ENUMS(OUT_OUTPUTS, 14), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(BYPASS_CV_LIGHTS, 2 * 2),// room for white-red | |||
ENUMS(BYPASS_TRIG_LIGHTS, 2 * 2),// room for white-red | |||
NUM_LIGHTS | |||
}; | |||
// Constants | |||
// S&H are numbered 0 to 6 in BraneA from lower left to lower right | |||
// S&H are numbered 7 to 13 in BraneB from top right to top left | |||
enum NoiseId {NONE, WHITE, PINK, RED, BLUE};//use negative value for inv phase | |||
int noiseSources[14] = {PINK, RED, BLUE, WHITE, -BLUE, -RED, -PINK, -PINK, -RED, -BLUE, WHITE, BLUE, RED, PINK}; | |||
static constexpr float nullNoise = 100.0f;// when a noise has not been generated for the current step | |||
// Need to save, with reset | |||
bool trigBypass[2]; | |||
// Need to save, no reset | |||
int panelTheme; | |||
// No need to save, with reset | |||
float heldOuts[14]; | |||
// No need to save, no reset | |||
SchmittTrigger sampleTriggers[2]; | |||
SchmittTrigger trigBypassTriggers[2]; | |||
NoiseGenerator whiteNoise; | |||
PinkFilter pinkFilter; | |||
RCFilter redFilter; | |||
RCFilter blueFilter; | |||
float trigLights[2]; | |||
Branes() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
// Need to save, no reset | |||
panelTheme = 0; | |||
// No need to save, no reset | |||
for (int i = 0; i < 2; i++) { | |||
sampleTriggers[i].reset(); | |||
trigBypassTriggers[i].reset(); | |||
trigLights[i] = 0.0f; | |||
} | |||
redFilter.setCutoff(441.0f / engineGetSampleRate()); | |||
blueFilter.setCutoff(44100.0f / engineGetSampleRate()); | |||
onReset(); | |||
} | |||
// widgets are not yet created when module is created | |||
// even if widgets not created yet, can use params[] and should handle 0.0f value since step may call | |||
// this before widget creation anyways | |||
// called from the main thread if by constructor, called by engine thread if right-click initialization | |||
// when called by constructor, module is created before the first step() is called | |||
void onReset() override { | |||
// Need to save, with reset | |||
for (int i = 0; i < 2; i++) | |||
trigBypass[i] = false; | |||
// No need to save, with reset | |||
for (int i = 0; i < 14; i++) | |||
heldOuts[i] = 0.0f; | |||
} | |||
// widgets randomized before onRandomize() is called | |||
// called by engine thread if right-click randomize | |||
void onRandomize() override { | |||
// Need to save, with reset | |||
for (int i = 0; i < 2; i++) | |||
trigBypass[i] = (randomu32() % 2) > 0; | |||
// No need to save, with reset | |||
for (int i = 0; i < 14; i++) | |||
heldOuts[i] = 0.0f; | |||
} | |||
// called by main thread | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// Need to save (reset or not) | |||
// trigBypass | |||
json_object_set_new(rootJ, "trigBypass0", json_real(trigBypass[0])); | |||
json_object_set_new(rootJ, "trigBypass1", json_real(trigBypass[1])); | |||
// panelTheme | |||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||
return rootJ; | |||
} | |||
// widgets have their fromJson() called before this fromJson() is called | |||
// called by main thread | |||
void fromJson(json_t *rootJ) override { | |||
// Need to save (reset or not) | |||
// trigBypass | |||
json_t *trigBypass0J = json_object_get(rootJ, "trigBypass0"); | |||
if (trigBypass0J) | |||
trigBypass[0] = json_real_value(trigBypass0J); | |||
json_t *trigBypass1J = json_object_get(rootJ, "trigBypass1"); | |||
if (trigBypass1J) | |||
trigBypass[1] = json_real_value(trigBypass1J); | |||
// panelTheme | |||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||
if (panelThemeJ) | |||
panelTheme = json_integer_value(panelThemeJ); | |||
// No need to save, with reset | |||
for (int i = 0; i < 14; i++) | |||
heldOuts[i] = 0.0f; | |||
} | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
float stepNoises[6] = {nullNoise, nullNoise, nullNoise, nullNoise, nullNoise, 0.0f};// order is whiteBase (-1 to 1), white, pink, red, blue, pink_processed (1.0f or 0.0f) | |||
// trigBypass buttons and cv inputs | |||
for (int i = 0; i < 2; i++) { | |||
if (trigBypassTriggers[i].process(params[TRIG_BYPASS_PARAMS + i].value + inputs[TRIG_BYPASS_INPUTS + i].value)) { | |||
trigBypass[i] = !trigBypass[i]; | |||
} | |||
} | |||
// trig inputs | |||
bool trigs[2]; | |||
bool trigInputsActive[2]; | |||
for (int i = 0; i < 2; i++) { | |||
trigs[i] = sampleTriggers[i].process(inputs[TRIG_INPUTS + i].value); | |||
if (trigs[i]) | |||
trigLights[i] = 1.0f; | |||
trigInputsActive[i] = trigBypass[i] ? false : inputs[TRIG_INPUTS + i].active; | |||
} | |||
// sample and hold outputs | |||
for (int sh = 0; sh < 14; sh++) { | |||
if (trigInputsActive[sh / 7] || (sh == 13 && trigInputsActive[0]) || (sh == 6 && trigInputsActive[1])) {// trig connected (with crosstrigger mechanism) | |||
if (trigs[sh / 7] || (sh == 13 && trigs[0]) || (sh == 6 && trigs[1])) { | |||
if (inputs[IN_INPUTS + sh].active)// if input cable | |||
heldOuts[sh] = inputs[IN_INPUTS + sh].value;// sample and hold input | |||
else { | |||
int noiseIndex = prepareNoise(stepNoises, sh);// sample and hold noise | |||
heldOuts[sh] = stepNoises[noiseIndex] * (noiseSources[sh] > 0 ? 1.0f : -1.0f); | |||
} | |||
} | |||
} | |||
else { // no trig connected | |||
if (inputs[IN_INPUTS + sh].active) { | |||
heldOuts[sh] = inputs[IN_INPUTS + sh].value;// copy of input if no trig and no input | |||
} | |||
else { | |||
heldOuts[sh] = 0.0f; | |||
if (outputs[OUT_OUTPUTS + sh].active) { | |||
int noiseIndex = prepareNoise(stepNoises, sh); | |||
heldOuts[sh] = stepNoises[noiseIndex] * (noiseSources[sh] > 0 ? 1.0f : -1.0f); | |||
} | |||
} | |||
} | |||
outputs[OUT_OUTPUTS + sh].value = heldOuts[sh]; | |||
} | |||
// Lights | |||
for (int i = 0; i < 2; i++) { | |||
float red = trigBypass[i] ? 1.0f : 0.0f; | |||
float white = !trigBypass[i] ? trigLights[i] : 0.0f; | |||
lights[BYPASS_CV_LIGHTS + i * 2 + 0].value = white; | |||
lights[BYPASS_CV_LIGHTS + i * 2 + 1].value = red; | |||
lights[BYPASS_TRIG_LIGHTS + i * 2 + 0].value = white; | |||
lights[BYPASS_TRIG_LIGHTS + i * 2 + 1].value = red; | |||
trigLights[i] -= (trigLights[i] / lightLambda) * (float)engineGetSampleTime(); | |||
} | |||
}// step() | |||
int prepareNoise(float* stepNoises, int sh) { | |||
int noiseIndex = abs( noiseSources[sh] ); | |||
if (stepNoises[noiseIndex] == nullNoise) { | |||
if (stepNoises[0] == nullNoise) | |||
stepNoises[0] = whiteNoise.white(); | |||
if ((noiseIndex == PINK || noiseIndex == BLUE) && stepNoises[5] == 0.0f) { | |||
pinkFilter.process(stepNoises[0]); | |||
stepNoises[5] = 1.0f; | |||
} | |||
switch (noiseIndex) { | |||
// most of the code in here is from Joel Robichaud - Nohmad Noise module | |||
case (PINK) : | |||
stepNoises[noiseIndex] = 5.0f * clamp(0.18f * pinkFilter.pink(), -1.0f, 1.0f); | |||
break; | |||
case (RED) : | |||
redFilter.process(stepNoises[0]); | |||
stepNoises[noiseIndex] = 5.0f * clamp(7.8f * redFilter.lowpass(), -1.0f, 1.0f); | |||
break; | |||
case (BLUE) : | |||
blueFilter.process(pinkFilter.pink()); | |||
stepNoises[noiseIndex] = 5.0f * clamp(0.64f * blueFilter.highpass(), -1.0f, 1.0f); | |||
break; | |||
default ://(WHITE) | |||
stepNoises[noiseIndex] = 5.0f * stepNoises[0]; | |||
break; | |||
} | |||
} | |||
return noiseIndex; | |||
} | |||
}; | |||
struct BranesWidget : ModuleWidget { | |||
struct PanelThemeItem : MenuItem { | |||
Branes *module; | |||
int theme; | |||
void onAction(EventAction &e) override { | |||
module->panelTheme = theme; | |||
} | |||
void step() override { | |||
rightText = (module->panelTheme == theme) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
Branes *module = dynamic_cast<Branes*>(this->module); | |||
assert(module); | |||
MenuLabel *themeLabel = new MenuLabel(); | |||
themeLabel->text = "Panel Theme"; | |||
menu->addChild(themeLabel); | |||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||
lightItem->text = lightPanelID;// Geodesics.hpp | |||
lightItem->module = module; | |||
lightItem->theme = 0; | |||
menu->addChild(lightItem); | |||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||
darkItem->text = darkPanelID;// Geodesics.hpp | |||
darkItem->module = module; | |||
darkItem->theme = 1; | |||
//menu->addChild(darkItem); | |||
return menu; | |||
} | |||
BranesWidget(Branes *module) : ModuleWidget(module) { | |||
// Main panel from Inkscape | |||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BranesBG-01.svg"))); | |||
//panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BranesBG-02.svg")));// no dark pannel for now | |||
box.size = panel->box.size; | |||
panel->mode = &module->panelTheme; | |||
addChild(panel); | |||
// Screws | |||
// part of svg panel, no code required | |||
float colRulerCenter = box.size.x / 2.0f; | |||
static constexpr float rowRulerHoldA = 119.5; | |||
static constexpr float rowRulerHoldB = 248.5f; | |||
static constexpr float radiusIn = 35.0f; | |||
static constexpr float radiusOut = 64.0f; | |||
static constexpr float offsetIn = 25.0f; | |||
static constexpr float offsetOut = 46.0f; | |||
// BraneA trig intput | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldA), Port::INPUT, module, Branes::TRIG_INPUTS + 0, &module->panelTheme)); | |||
// BraneA inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldA + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusIn, rowRulerHoldA), Port::INPUT, module, Branes::IN_INPUTS + 1, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldA - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 2, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldA - radiusIn), Port::INPUT, module, Branes::IN_INPUTS + 3, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldA - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 4, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusIn, rowRulerHoldA), Port::INPUT, module, Branes::IN_INPUTS + 5, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldA + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 6, &module->panelTheme)); | |||
// BraneA outputs | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldA + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 0, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusOut, rowRulerHoldA), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 1, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldA - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 2, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldA - radiusOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 3, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldA - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 4, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusOut, rowRulerHoldA), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 5, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldA + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 6, &module->panelTheme)); | |||
// BraneB trig intput | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldB), Port::INPUT, module, Branes::TRIG_INPUTS + 1, &module->panelTheme)); | |||
// BraneB inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldB - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 7, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusIn, rowRulerHoldB), Port::INPUT, module, Branes::IN_INPUTS + 8, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetIn, rowRulerHoldB + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 9, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldB + radiusIn), Port::INPUT, module, Branes::IN_INPUTS + 10, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldB + offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 11, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusIn, rowRulerHoldB), Port::INPUT, module, Branes::IN_INPUTS + 12, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetIn, rowRulerHoldB - offsetIn), Port::INPUT, module, Branes::IN_INPUTS + 13, &module->panelTheme)); | |||
// BraneB outputs | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldB - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 7, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusOut, rowRulerHoldB), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 8, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetOut, rowRulerHoldB + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 9, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerHoldB + radiusOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 10, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldB + offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 11, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusOut, rowRulerHoldB), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 12, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetOut, rowRulerHoldB - offsetOut), Port::OUTPUT, module, Branes::OUT_OUTPUTS + 13, &module->panelTheme)); | |||
static constexpr float rowRulerBypass = 345.5f; | |||
// Trigger bypass | |||
// buttons | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - 32.0f, rowRulerBypass), module, Branes::TRIG_BYPASS_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + 32.0f, rowRulerBypass), module, Branes::TRIG_BYPASS_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// cv inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - 65.0f, rowRulerBypass), Port::INPUT, module, Branes::TRIG_BYPASS_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + 65.0f, rowRulerBypass), Port::INPUT, module, Branes::TRIG_BYPASS_INPUTS + 1, &module->panelTheme)); | |||
// LEDs bottom | |||
addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter - 46.5f, rowRulerBypass), module, Branes::BYPASS_CV_LIGHTS + 0 * 2)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter + 46.5f, rowRulerBypass), module, Branes::BYPASS_CV_LIGHTS + 1 * 2)); | |||
// LEDs top | |||
addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter + 5.5f, rowRulerHoldA + 19.5f), module, Branes::BYPASS_TRIG_LIGHTS + 0 * 2)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteRedLight>>(Vec(colRulerCenter - 5.5f, rowRulerHoldB - 19.5f), module, Branes::BYPASS_TRIG_LIGHTS + 1 * 2)); | |||
} | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
RACK_PLUGIN_MODEL_INIT(Geodesics, Branes) { | |||
Model *modelBranes = Model::create<Branes, BranesWidget>("Geodesics", "Branes", "Branes", SAMPLE_AND_HOLD_TAG); | |||
return modelBranes; | |||
} | |||
/*CHANGE LOG | |||
0.6.0: | |||
created | |||
*/ |
@@ -0,0 +1,210 @@ | |||
//*********************************************************************************************** | |||
//Geodesics: A modular collection for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from Valley Rack Free by Dale Johnson | |||
//See ./LICENSE.txt for all licenses | |||
//*********************************************************************************************** | |||
#include "GeoWidgets.hpp" | |||
namespace rack_plugin_Geodesics { | |||
// Dynamic SVGPanel | |||
void PanelBorderWidget::draw(NVGcontext *vg) { // carbon copy from SVGPanel.cpp | |||
NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); | |||
nvgBeginPath(vg); | |||
nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0);// full rect of module (including expansion area if a module has one) | |||
nvgStrokeColor(vg, borderColor); | |||
nvgStrokeWidth(vg, 1.0); | |||
nvgStroke(vg); | |||
if (expWidth != nullptr && *expWidth != nullptr) {// add expansion division when pannel uses expansion area | |||
int expW = **expWidth; | |||
nvgBeginPath(vg); | |||
nvgMoveTo(vg, box.size.x - expW, 1); | |||
nvgLineTo(vg, box.size.x - expW, box.size.y - 1.0); | |||
nvgStrokeWidth(vg, 2.0); | |||
nvgStroke(vg); | |||
} | |||
} | |||
DynamicSVGPanel::DynamicSVGPanel() { | |||
mode = nullptr; | |||
oldMode = -1; | |||
expWidth = nullptr; | |||
visiblePanel = new SVGWidget(); | |||
addChild(visiblePanel); | |||
border = new PanelBorderWidget(); | |||
border->expWidth = &expWidth; | |||
addChild(border); | |||
} | |||
void DynamicSVGPanel::addPanel(std::shared_ptr<SVG> svg) { | |||
panels.push_back(svg); | |||
if(!visiblePanel->svg) { | |||
visiblePanel->setSVG(svg); | |||
box.size = visiblePanel->box.size.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE); | |||
border->box.size = box.size; | |||
} | |||
} | |||
void DynamicSVGPanel::step() { // all code except middle if() from SVGPanel::step() in SVGPanel.cpp | |||
if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { | |||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
oversample = 2.f; | |||
} | |||
if(mode != nullptr && *mode != oldMode) { | |||
if ((unsigned)(*mode) < panels.size()) { | |||
visiblePanel->setSVG(panels[*mode]); | |||
dirty = true; | |||
} | |||
oldMode = *mode; | |||
} | |||
FramebufferWidget::step(); | |||
} | |||
// Dynamic SVGPort | |||
DynamicSVGPort::DynamicSVGPort() { | |||
mode = nullptr; | |||
oldMode = -1; | |||
//SVGPort constructor automatically called | |||
} | |||
void DynamicSVGPort::addFrame(std::shared_ptr<SVG> svg) { | |||
frames.push_back(svg); | |||
if(!background->svg) | |||
SVGPort::setSVG(svg); | |||
} | |||
void DynamicSVGPort::step() { | |||
if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { | |||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
oversample = 2.f; | |||
} | |||
if(mode != nullptr && *mode != oldMode) { | |||
if ((unsigned)(*mode) < frames.size()) { | |||
background->setSVG(frames[*mode]); | |||
dirty = true; | |||
} | |||
oldMode = *mode; | |||
} | |||
Port::step(); | |||
} | |||
// Dynamic SVGSwitch | |||
DynamicSVGSwitch::DynamicSVGSwitch() { | |||
mode = nullptr; | |||
oldMode = -1; | |||
//SVGSwitch constructor automatically called | |||
} | |||
void DynamicSVGSwitch::addFrameAll(std::shared_ptr<SVG> svg) { | |||
framesAll.push_back(svg); | |||
if (framesAll.size() == 2) { | |||
addFrame(framesAll[0]); | |||
addFrame(framesAll[1]); | |||
} | |||
} | |||
void DynamicSVGSwitch::step() { | |||
if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { | |||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
oversample = 2.f; | |||
} | |||
if(mode != nullptr && *mode != oldMode) { | |||
if ((unsigned)(*mode) * 2 + 1 < framesAll.size()) { | |||
if ((*mode) == 0) { | |||
frames[0]=framesAll[0]; | |||
frames[1]=framesAll[1]; | |||
} | |||
else { | |||
frames[0]=framesAll[2]; | |||
frames[1]=framesAll[3]; | |||
} | |||
onChange(*(new EventChange()));// required because of the way SVGSwitch changes images, we only change the frames above. | |||
//dirty = true;// dirty is not sufficient when changing via frames assignments above (i.e. onChange() is required) | |||
} | |||
oldMode = *mode; | |||
} | |||
} | |||
// Dynamic SVGKnob | |||
DynamicSVGKnob::DynamicSVGKnob() { | |||
//SVGKnob constructor automatically called first | |||
mode = nullptr; | |||
oldMode = -1; | |||
effect = nullptr; | |||
orientationAngle = 0.0f; | |||
} | |||
void DynamicSVGKnob::addFrameAll(std::shared_ptr<SVG> svg) { | |||
framesAll.push_back(svg); | |||
if (framesAll.size() == 1) { | |||
setSVG(svg); | |||
} | |||
} | |||
void DynamicSVGKnob::addEffect(std::shared_ptr<SVG> svg) { | |||
effect = new SVGWidget(); | |||
effect->setSVG(svg); | |||
addChild(effect); | |||
} | |||
void DynamicSVGKnob::step() { | |||
if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { | |||
// Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||
oversample = 2.f; | |||
} | |||
if(mode != nullptr && *mode != oldMode) { | |||
if ((unsigned)(*mode) < framesAll.size()) { | |||
if ((*mode) == 0) { | |||
setSVG(framesAll[0]); | |||
if (effect != nullptr) | |||
effect->visible = false; | |||
} | |||
else { | |||
setSVG(framesAll[1]); | |||
if (effect != nullptr) | |||
effect->visible = true; | |||
} | |||
dirty = true; | |||
} | |||
oldMode = *mode; | |||
} | |||
//SVGKnob::step(); | |||
// do code here because handle orientationAngle | |||
// Re-transform TransformWidget if dirty | |||
if (dirty) { | |||
float angle; | |||
if (isfinite(minValue) && isfinite(maxValue)) { | |||
angle = rescale(value, minValue, maxValue, minAngle, maxAngle); | |||
} | |||
else { | |||
angle = rescale(value, -1.0, 1.0, minAngle, maxAngle); | |||
angle = fmodf(angle, 2*M_PI); | |||
} | |||
angle += orientationAngle; | |||
tw->identity(); | |||
// Rotate SVG | |||
Vec center = sw->box.getCenter(); | |||
tw->translate(center); | |||
tw->rotate(angle); | |||
tw->translate(center.neg()); | |||
} | |||
FramebufferWidget::step(); | |||
} | |||
} // namespace rack_plugin_Geodesics |
@@ -0,0 +1,117 @@ | |||
//*********************************************************************************************** | |||
//Geodesics: A modular collection for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from Valley Rack Free by Dale Johnson | |||
//See ./LICENSE.txt for all licenses | |||
//*********************************************************************************************** | |||
#ifndef IM_WIDGETS_HPP | |||
#define IM_WIDGETS_HPP | |||
#include "rack.hpp" | |||
#include "window.hpp" | |||
using namespace rack; | |||
namespace rack_plugin_Geodesics { | |||
// ******** Dynamic SVGPanel ******** | |||
struct PanelBorderWidget : TransparentWidget { // from SVGPanel.cpp | |||
int** expWidth = nullptr; | |||
void draw(NVGcontext *vg) override; | |||
}; | |||
struct DynamicSVGPanel : FramebufferWidget { // like SVGPanel (in app.hpp and SVGPanel.cpp) but with dynmically assignable panel | |||
int* mode; | |||
int oldMode; | |||
int* expWidth; | |||
std::vector<std::shared_ptr<SVG>> panels; | |||
SVGWidget* visiblePanel; | |||
PanelBorderWidget* border; | |||
DynamicSVGPanel(); | |||
void addPanel(std::shared_ptr<SVG> svg); | |||
void step() override; | |||
}; | |||
// ******** Dynamic Ports ******** | |||
// General Dynamic Port creation | |||
template <class TDynamicPort> | |||
TDynamicPort* createDynamicPort(Vec pos, Port::PortType type, Module *module, int portId, | |||
int* mode) { | |||
TDynamicPort *dynPort = Port::create<TDynamicPort>(pos, type, module, portId); | |||
dynPort->mode = mode; | |||
dynPort->box.pos = dynPort->box.pos.minus(dynPort->box.size.div(2));// centering | |||
return dynPort; | |||
} | |||
// Dynamic SVGPort (see SVGPort in app.hpp and SVGPort.cpp) | |||
struct DynamicSVGPort : SVGPort { | |||
int* mode; | |||
int oldMode; | |||
std::vector<std::shared_ptr<SVG>> frames; | |||
DynamicSVGPort(); | |||
void addFrame(std::shared_ptr<SVG> svg); | |||
void step() override; | |||
}; | |||
// ******** Dynamic Params ******** | |||
// General Dynamic Param creation | |||
template <class TDynamicParam> | |||
TDynamicParam* createDynamicParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue, | |||
int* mode) { | |||
TDynamicParam *dynParam = ParamWidget::create<TDynamicParam>(pos, module, paramId, minValue, maxValue, defaultValue); | |||
dynParam->mode = mode; | |||
dynParam->box.pos = dynParam->box.pos.minus(dynParam->box.size.div(2));// centering | |||
return dynParam; | |||
} | |||
// General Dynamic Param creation version two with float* instead of one int* | |||
template <class TDynamicParam> | |||
TDynamicParam* createDynamicParam2(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue, | |||
float* wider, float* paramReadRequest) { | |||
TDynamicParam *dynParam = ParamWidget::create<TDynamicParam>(pos, module, paramId, minValue, maxValue, defaultValue); | |||
dynParam->wider = wider; | |||
dynParam->paramReadRequest = paramReadRequest; | |||
return dynParam; | |||
} | |||
// Dynamic SVGSwitch (see SVGSwitch in app.hpp and SVGSwitch.cpp) | |||
struct DynamicSVGSwitch : SVGSwitch { | |||
int* mode; | |||
int oldMode; | |||
std::vector<std::shared_ptr<SVG>> framesAll; | |||
DynamicSVGSwitch(); | |||
void addFrameAll(std::shared_ptr<SVG> svg); | |||
void step() override; | |||
}; | |||
// Dynamic SVGKnob (see SVGKnob in app.hpp and SVGKnob.cpp) | |||
struct DynamicSVGKnob : SVGKnob { | |||
int* mode; | |||
int oldMode; | |||
std::vector<std::shared_ptr<SVG>> framesAll; | |||
SVGWidget* effect; | |||
float orientationAngle; | |||
DynamicSVGKnob(); | |||
void addFrameAll(std::shared_ptr<SVG> svg); | |||
void addEffect(std::shared_ptr<SVG> svg);// do this last | |||
void step() override; | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
#endif |
@@ -0,0 +1,33 @@ | |||
//*********************************************************************************************** | |||
//Geodesics: A modular collection for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt | |||
// and graphics from the Component Library by Wes Milholen | |||
//See ./LICENSE.txt for all licenses | |||
//See ./res/fonts/ for font licenses | |||
// | |||
//*********************************************************************************************** | |||
#include "Geodesics.hpp" | |||
RACK_PLUGIN_MODEL_DECLARE(Geodesics, BlackHoles); | |||
RACK_PLUGIN_MODEL_DECLARE(Geodesics, Pulsars); | |||
RACK_PLUGIN_MODEL_DECLARE(Geodesics, Branes); | |||
RACK_PLUGIN_MODEL_DECLARE(Geodesics, Ions); | |||
RACK_PLUGIN_MODEL_DECLARE(Geodesics, BlankLogo); | |||
RACK_PLUGIN_MODEL_DECLARE(Geodesics, BlankInfo); | |||
RACK_PLUGIN_INIT(Geodesics) { | |||
RACK_PLUGIN_INIT_ID(); | |||
RACK_PLUGIN_INIT_WEBSITE("https://github.com/MarcBoule/Geodesics"); | |||
RACK_PLUGIN_INIT_MANUAL("https://github.com/MarcBoule/Geodesics/blob/master/README.md"); | |||
RACK_PLUGIN_MODEL_ADD(Geodesics, BlackHoles); | |||
RACK_PLUGIN_MODEL_ADD(Geodesics, Pulsars); | |||
RACK_PLUGIN_MODEL_ADD(Geodesics, Branes); | |||
RACK_PLUGIN_MODEL_ADD(Geodesics, Ions); | |||
RACK_PLUGIN_MODEL_ADD(Geodesics, BlankLogo); | |||
RACK_PLUGIN_MODEL_ADD(Geodesics, BlankInfo); | |||
} |
@@ -0,0 +1,162 @@ | |||
//*********************************************************************************************** | |||
//Geodesics: A modular collection for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt | |||
// and graphics from the Component Library by Wes Milholen | |||
//See ./LICENSE.txt for all licenses | |||
//See ./res/fonts/ for font licenses | |||
// | |||
//*********************************************************************************************** | |||
#ifndef GEODESICS_HPP | |||
#define GEODESICS_HPP | |||
#include "rack.hpp" | |||
#include "GeoWidgets.hpp" | |||
#include "dsp/digital.hpp" | |||
using namespace rack; | |||
RACK_PLUGIN_DECLARE(Geodesics); | |||
#ifdef USE_VST2 | |||
#define plugin "Geodesics" | |||
#endif // USE_VST2 | |||
namespace rack_plugin_Geodesics { | |||
// General constants | |||
static const float lightLambda = 0.075f; | |||
static const std::string lightPanelID = "White light"; | |||
static const std::string darkPanelID = "Dark copper"; | |||
// Variations on existing knobs, lights, etc | |||
// Ports | |||
struct GeoPort : DynamicSVGPort { | |||
GeoPort() { | |||
shadow->blurRadius = 10.0; | |||
shadow->opacity = 0.8; | |||
addFrame(SVG::load(assetPlugin(plugin, "res/light/comp/Jack.svg"))); | |||
//addFrame(SVG::load(assetPlugin(plugin, "res/dark/comp/Jack.svg")));// no dark ports in Geodesics for now | |||
} | |||
}; | |||
struct BlankPort : SVGPort { | |||
BlankPort() { | |||
shadow->opacity = 0.0; | |||
setSVG(SVG::load(assetPlugin(plugin, "res/comp/Otrsp-01.svg"))); | |||
} | |||
}; | |||
// Buttons and switches | |||
struct GeoPushButton : DynamicSVGSwitch, MomentarySwitch { | |||
GeoPushButton() {// only one skin for now | |||
addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/PushButton1_0.svg"))); | |||
addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/PushButton1_1.svg"))); | |||
//addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/CKD6b_0.svg"))); // no dark buttons in Geodesics for now | |||
//addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/CKD6b_1.svg"))); // no dark buttons in Geodesics for now | |||
} | |||
}; | |||
// Knobs | |||
struct GeoKnob : DynamicSVGKnob { | |||
GeoKnob() { | |||
minAngle = -0.73*M_PI; | |||
maxAngle = 0.73*M_PI; | |||
shadow->blurRadius = 10.0; | |||
shadow->opacity = 0.8; | |||
//shadow->box.pos = Vec(0.0, box.size.y * 0.15); may need this if know is small (taken from IMSmallKnob) | |||
addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/Knob.svg"))); | |||
//addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/Knob.svg")));// no dark knobs in Geodesics for now | |||
} | |||
}; | |||
struct GeoKnobRight : GeoKnob { | |||
GeoKnobRight() { | |||
orientationAngle = M_PI / 2.0f; | |||
} | |||
}; | |||
struct GeoKnobLeft : GeoKnob { | |||
GeoKnobLeft() { | |||
orientationAngle = M_PI / -2.0f; | |||
} | |||
}; | |||
struct GeoKnobBottom : GeoKnob { | |||
GeoKnobBottom() { | |||
orientationAngle = M_PI; | |||
} | |||
}; | |||
struct BlankCKnob : SVGKnob { | |||
BlankCKnob() { | |||
minAngle = -0.73*M_PI; | |||
maxAngle = 0.73*M_PI; | |||
shadow->opacity = 0.0; | |||
setSVG(SVG::load(assetPlugin(plugin, "res/comp/C-01.svg"))); | |||
} | |||
}; | |||
// Lights | |||
struct GeoGrayModuleLight : ModuleLightWidget { | |||
GeoGrayModuleLight() { | |||
bgColor = nvgRGB(0x8e, 0x8e, 0x8e); | |||
borderColor = nvgRGBA(0, 0, 0, 0x60); | |||
} | |||
}; | |||
struct GeoWhiteLight : GeoGrayModuleLight { | |||
GeoWhiteLight() { | |||
addBaseColor(COLOR_WHITE); | |||
} | |||
}; | |||
struct GeoBlueLight : GeoGrayModuleLight { | |||
GeoBlueLight() { | |||
addBaseColor(COLOR_BLUE); | |||
} | |||
}; | |||
struct GeoRedLight : GeoGrayModuleLight { | |||
GeoRedLight() { | |||
addBaseColor(COLOR_RED); | |||
} | |||
}; | |||
struct GeoYellowLight : GeoGrayModuleLight { | |||
GeoYellowLight() { | |||
addBaseColor(COLOR_YELLOW); | |||
} | |||
}; | |||
struct GeoWhiteRedLight : GeoGrayModuleLight { | |||
GeoWhiteRedLight() { | |||
addBaseColor(COLOR_WHITE); | |||
addBaseColor(COLOR_RED); | |||
} | |||
};struct GeoBlueYellowWhiteLight : GeoGrayModuleLight { | |||
GeoBlueYellowWhiteLight() { | |||
addBaseColor(COLOR_BLUE); | |||
addBaseColor(COLOR_YELLOW); | |||
addBaseColor(COLOR_WHITE); | |||
} | |||
}; | |||
// Other | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
#endif |
@@ -0,0 +1,787 @@ | |||
//*********************************************************************************************** | |||
//Atomic Duophonic Voltage Sequencer module for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt and graphics | |||
// from the Component Library by Wes Milholen. | |||
//See ./LICENSE.txt for all licenses | |||
//See ./res/fonts/ for font licenses | |||
// | |||
//*********************************************************************************************** | |||
#include "Geodesics.hpp" | |||
namespace rack_plugin_Geodesics { | |||
struct Ions : Module { | |||
enum ParamIds { | |||
RUN_PARAM, | |||
RESET_PARAM, | |||
ENUMS(CV_PARAMS, 15),// 0 is center, move conter clockwise top atom, then clockwise bot atom | |||
PROB_PARAM, | |||
ENUMS(OCT_PARAMS, 2), | |||
LEAP_PARAM, | |||
ENUMS(STATE_PARAMS, 2),// 3 states : global, local, global+local | |||
PLANK_PARAM, | |||
uncertainty_PARAM, | |||
RESETONRUN_PARAM, | |||
STEPCLOCKS_PARAM, | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
CLK_INPUT, | |||
ENUMS(CLK_INPUTS, 2), | |||
RUN_INPUT, | |||
RESET_INPUT, | |||
PROB_INPUT,// CV_value/10 is added to PROB_PARAM, which is a 0 to 1 knob | |||
ENUMS(OCTCV_INPUTS, 2), | |||
ENUMS(STATECV_INPUTS, 2), | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
ENUMS(SEQ_OUTPUTS, 2), | |||
ENUMS(JUMP_OUTPUTS, 2), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(BLUE_LIGHTS, 16), | |||
ENUMS(YELLOW_LIGHTS, 16), | |||
RUN_LIGHT, | |||
RESET_LIGHT, | |||
ENUMS(GLOBAL_LIGHTS, 2),// 0 is top atom, 1 is bottom atom | |||
ENUMS(LOCAL_LIGHTS, 2), | |||
LEAP_LIGHT, | |||
ENUMS(OCTA_LIGHTS, 3),// 0 is center, 1 is inside mirrors, 2 is outside mirrors | |||
ENUMS(OCTB_LIGHTS, 3), | |||
ENUMS(PLANK_LIGHT, 3),// room for blue, yellow, white | |||
uncertainty_LIGHT, | |||
ENUMS(JUMP_LIGHTS, 2), | |||
RESETONRUN_LIGHT, | |||
STEPCLOCKS_LIGHT, | |||
NUM_LIGHTS | |||
}; | |||
// Constants | |||
static constexpr float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) | |||
const int cvMap[2][16] = {{0, 1, 2, 3, 4, 5, 6, 7, 0, 8, 9, 10, 11, 12, 13, 14}, | |||
{0, 8, 9 ,10, 11, 12, 13, 14, 0, 1, 2, 3, 4, 5, 6, 7}};// map each of the 16 steps of a sequence step to a CV knob index (0-14) | |||
// Need to save, with reset | |||
bool running; | |||
bool resetOnRun; | |||
int quantize;// a.k.a. plank constant, 0 = none, 1 = blue, 2 = yellow, 3 = white (both) | |||
//bool symmetry; | |||
bool uncertainty; | |||
int stepIndexes[2];// position of electrons (sequencers) | |||
int states[2];// which clocks to use (0 = global, 1 = local, 2 = both) | |||
int ranges[2];// [0; 2], number of extra octaves to span each side of central octave (which is C4: 0 - 1V) | |||
bool leap; | |||
// Need to save, no reset | |||
int panelTheme; | |||
// No need to save, with reset | |||
long clockIgnoreOnReset; | |||
float resetLight; | |||
bool rangeInc[2];// true when 1-3-5 increasing, false when 5-3-1 decreasing | |||
// No need to save, no reset | |||
SchmittTrigger runningTrigger; | |||
SchmittTrigger clockTrigger; | |||
SchmittTrigger clocksTriggers[2]; | |||
SchmittTrigger resetTrigger; | |||
SchmittTrigger stateTriggers[2]; | |||
SchmittTrigger octTriggers[2]; | |||
SchmittTrigger stateCVTriggers[2]; | |||
SchmittTrigger leapTrigger; | |||
SchmittTrigger plankTrigger; | |||
SchmittTrigger uncertaintyTrigger; | |||
SchmittTrigger resetOnRunTrigger; | |||
SchmittTrigger stepClocksTrigger; | |||
PulseGenerator jumpPulses[2]; | |||
float jumpLights[2]; | |||
float stepClocksLight; | |||
inline float quantizeCV(float cv) {return roundf(cv * 12.0f) / 12.0f;} | |||
inline bool jumpRandom() {return (randomUniform() < (params[PROB_PARAM].value + inputs[PROB_INPUT].value / 10.0f));}// randomUniform is [0.0, 1.0), see include/util/common.hpp | |||
Ions() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
// Need to save, no reset | |||
panelTheme = 0; | |||
// No need to save, no reset | |||
runningTrigger.reset(); | |||
clockTrigger.reset(); | |||
for (int i = 0; i < 2; i++) { | |||
clocksTriggers[i].reset(); | |||
stateTriggers[i].reset(); | |||
octTriggers[i].reset(); | |||
stateCVTriggers[i].reset(); | |||
jumpPulses[i].reset(); | |||
jumpLights[i] = 0.0f; | |||
} | |||
stepClocksLight = 0.0f; | |||
resetTrigger.reset(); | |||
leapTrigger.reset(); | |||
plankTrigger.reset(); | |||
uncertaintyTrigger.reset(); | |||
resetOnRunTrigger.reset(); | |||
stepClocksTrigger.reset(); | |||
onReset(); | |||
} | |||
// widgets are not yet created when module is created | |||
// even if widgets not created yet, can use params[] and should handle 0.0f value since step may call | |||
// this before widget creation anyways | |||
// called from the main thread if by constructor, called by engine thread if right-click initialization | |||
// when called by constructor, module is created before the first step() is called | |||
void onReset() override { | |||
// Need to save, with reset | |||
running = false; | |||
resetOnRun = false; | |||
quantize = 3; | |||
//symmetry = false; | |||
uncertainty = false; | |||
for (int i = 0; i < 2; i++) { | |||
states[i] = 0; | |||
ranges[i] = 1; | |||
} | |||
leap = false; | |||
initRun(true, false); | |||
// No need to save, with reset | |||
for (int i = 0; i < 2; i++) { | |||
rangeInc[i] = true; | |||
} | |||
} | |||
// widgets randomized before onRandomize() is called | |||
// called by engine thread if right-click randomize | |||
void onRandomize() override { | |||
// Need to save, with reset | |||
running = false; | |||
resetOnRun = false; | |||
quantize = randomu32() % 4; | |||
//symmetry = false; | |||
uncertainty = false; | |||
for (int i = 0; i < 2; i++) { | |||
states[i] = randomu32() % 3; | |||
ranges[i] = randomu32() % 3; | |||
} | |||
leap = (randomu32() % 2) > 0; | |||
initRun(true, true); | |||
// No need to save, with reset | |||
for (int i = 0; i < 2; i++) { | |||
rangeInc[i] = true; | |||
} | |||
} | |||
void initRun(bool hard, bool randomize) {// run button activated or run edge in run input jack | |||
if (hard) { | |||
if (randomize) { | |||
stepIndexes[0] = randomu32() % 16; | |||
stepIndexes[1] = randomu32() % 16; | |||
} | |||
else { | |||
stepIndexes[0] = 0; | |||
stepIndexes[1] = 0; | |||
} | |||
} | |||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||
resetLight = 0.0f; | |||
} | |||
// called by main thread | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// Need to save (reset or not) | |||
// panelTheme | |||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||
// resetOnRun | |||
json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); | |||
// quantize | |||
json_object_set_new(rootJ, "quantize", json_integer(quantize)); | |||
// symmetry | |||
//json_object_set_new(rootJ, "symmetry", json_boolean(symmetry)); | |||
// uncertainty | |||
json_object_set_new(rootJ, "uncertainty", json_boolean(uncertainty)); | |||
// running | |||
json_object_set_new(rootJ, "running", json_boolean(running)); | |||
// stepIndexes | |||
json_object_set_new(rootJ, "stepIndexes0", json_integer(stepIndexes[0])); | |||
json_object_set_new(rootJ, "stepIndexes1", json_integer(stepIndexes[1])); | |||
// states | |||
json_object_set_new(rootJ, "states0", json_integer(states[0])); | |||
json_object_set_new(rootJ, "states1", json_integer(states[1])); | |||
// ranges | |||
json_object_set_new(rootJ, "ranges0", json_integer(ranges[0])); | |||
json_object_set_new(rootJ, "ranges1", json_integer(ranges[1])); | |||
// leap | |||
json_object_set_new(rootJ, "leap", json_boolean(leap)); | |||
return rootJ; | |||
} | |||
// widgets have their fromJson() called before this fromJson() is called | |||
// called by main thread | |||
void fromJson(json_t *rootJ) override { | |||
// Need to save (reset or not) | |||
// panelTheme | |||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||
if (panelThemeJ) | |||
panelTheme = json_integer_value(panelThemeJ); | |||
// resetOnRun | |||
json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); | |||
if (resetOnRunJ) | |||
resetOnRun = json_is_true(resetOnRunJ); | |||
// quantize | |||
json_t *quantizeJ = json_object_get(rootJ, "quantize"); | |||
if (quantizeJ) | |||
quantize = json_integer_value(quantizeJ); | |||
// symmetry | |||
//json_t *symmetryJ = json_object_get(rootJ, "symmetry"); | |||
//if (symmetryJ) | |||
//symmetry = json_is_true(symmetryJ); | |||
// uncertainty | |||
json_t *uncertaintyJ = json_object_get(rootJ, "uncertainty"); | |||
if (uncertaintyJ) | |||
uncertainty = json_is_true(uncertaintyJ); | |||
// running | |||
json_t *runningJ = json_object_get(rootJ, "running"); | |||
if (runningJ) | |||
running = json_is_true(runningJ); | |||
// stepIndexes | |||
json_t *stepIndexes0J = json_object_get(rootJ, "stepIndexes0"); | |||
if (stepIndexes0J) | |||
stepIndexes[0] = json_integer_value(stepIndexes0J); | |||
json_t *stepIndexes1J = json_object_get(rootJ, "stepIndexes1"); | |||
if (stepIndexes1J) | |||
stepIndexes[1] = json_integer_value(stepIndexes1J); | |||
// states | |||
json_t *states0J = json_object_get(rootJ, "states0"); | |||
if (states0J) | |||
states[0] = json_integer_value(states0J); | |||
json_t *states1J = json_object_get(rootJ, "states1"); | |||
if (states1J) | |||
states[1] = json_integer_value(states1J); | |||
// ranges | |||
json_t *ranges0J = json_object_get(rootJ, "ranges0"); | |||
if (ranges0J) | |||
ranges[0] = json_integer_value(ranges0J); | |||
json_t *ranges1J = json_object_get(rootJ, "ranges1"); | |||
if (ranges1J) | |||
ranges[1] = json_integer_value(ranges1J); | |||
// leap | |||
json_t *leapJ = json_object_get(rootJ, "leap"); | |||
if (leapJ) | |||
leap = json_is_true(leapJ); | |||
// No need to save, with reset | |||
initRun(true, false); | |||
rangeInc[0] = true; | |||
rangeInc[1] = true; | |||
} | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
float sampleTime = engineGetSampleTime(); | |||
//********** Buttons, knobs, switches and inputs ********** | |||
// Run button | |||
if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUN_INPUT].value)) { | |||
running = !running; | |||
if (running) | |||
initRun(resetOnRun, false); | |||
} | |||
// Leap button | |||
if (leapTrigger.process(params[LEAP_PARAM].value)) { | |||
leap = !leap; | |||
} | |||
// Plank button (quatize) | |||
if (plankTrigger.process(params[PLANK_PARAM].value)) { | |||
quantize++; | |||
if (quantize >= 4) | |||
quantize = 0; | |||
} | |||
// uncertainty button | |||
if (uncertaintyTrigger.process(params[uncertainty_PARAM].value)) { | |||
uncertainty = !uncertainty; | |||
} | |||
// Reset on Run button | |||
if (resetOnRunTrigger.process(params[RESETONRUN_PARAM].value)) { | |||
resetOnRun = !resetOnRun; | |||
} | |||
// State buttons and CV inputs (state: 0 = global, 1 = local, 2 = both) | |||
for (int i = 0; i < 2; i++) { | |||
int stateTrig = stateTriggers[i].process(params[STATE_PARAMS + i].value); | |||
if (inputs[STATECV_INPUTS + i].active) { | |||
if (inputs[STATECV_INPUTS + i].value <= -1.0f) | |||
states[i] = 1; | |||
else if (inputs[STATECV_INPUTS + i].value < 1.0f) | |||
states[i] = 2; | |||
else | |||
states[i] = 0; | |||
} | |||
else if (stateTrig) { | |||
states[i]++; | |||
if (states[i] >= 3) | |||
states[i] = 0; | |||
} | |||
} | |||
// Range buttons and CV inputs | |||
for (int i = 0; i < 2; i++) { | |||
int rangeTrig = octTriggers[i].process(params[OCT_PARAMS + i].value); | |||
if (inputs[OCTCV_INPUTS + i].active) { | |||
if (inputs[OCTCV_INPUTS + i].value <= -1.0f) | |||
ranges[i] = 0; | |||
else if (inputs[OCTCV_INPUTS + i].value < 1.0f) | |||
ranges[i] = 1; | |||
else | |||
ranges[i] = 2; | |||
} | |||
else if (rangeTrig) { | |||
if (rangeInc[i]) { | |||
ranges[i]++; | |||
if (ranges[i] >= 3) { | |||
ranges[i] = 1; | |||
rangeInc[i] = false; | |||
} | |||
} | |||
else { | |||
ranges[i]--; | |||
if (ranges[i] < 0) { | |||
ranges[i] = 1; | |||
rangeInc[i] = true; | |||
} | |||
} | |||
} | |||
} | |||
//********** Clock and reset ********** | |||
// Clocks | |||
bool globalClockTrig = clockTrigger.process(inputs[CLK_INPUT].value); | |||
bool stepClocksTrig = stepClocksTrigger.process(params[STEPCLOCKS_PARAM].value); | |||
for (int i = 0; i < 2; i++) { | |||
int jumpCount = 0; | |||
if (running && clockIgnoreOnReset == 0l) { | |||
// Local clocks and uncertainty | |||
bool localClockTrig = clocksTriggers[i].process(inputs[CLK_INPUTS + i].value); | |||
localClockTrig &= (states[i] >= 1); | |||
if (localClockTrig) { | |||
if (uncertainty) {// local clock modified by uncertainty | |||
int numSteps = 8; | |||
int prob = randomu32() % 1000; | |||
if (prob < 175) | |||
numSteps = 1; | |||
else if (prob < 330) // 175 + 155 | |||
numSteps = 2; | |||
else if (prob < 475) // 175 + 155 + 145 | |||
numSteps = 3; | |||
else if (prob < 610) // 175 + 155 + 145 + 135 | |||
numSteps = 4; | |||
else if (prob < 725) // 175 + 155 + 145 + 135 + 115 | |||
numSteps = 5; | |||
else if (prob < 830) // 175 + 155 + 145 + 135 + 115 + 105 | |||
numSteps = 6; | |||
else if (prob < 925) // 175 + 155 + 145 + 135 + 115 + 105 + 95 | |||
numSteps = 7; | |||
for (int n = 0; n < numSteps; n++) | |||
jumpCount += stepElectron(i, leap); | |||
} | |||
else | |||
jumpCount += stepElectron(i, leap);// normal local clock | |||
} | |||
// Global clock | |||
if (globalClockTrig && ((states[i] & 0x1) == 0) && !localClockTrig) { | |||
jumpCount += stepElectron(i, leap); | |||
} | |||
} | |||
// Magnetic clock (step clock) | |||
if (stepClocksTrig) | |||
jumpCount += stepElectron(i, leap); | |||
// Jump occurred feedback | |||
if ((jumpCount & 0x1) != 0) { | |||
jumpPulses[i].trigger(0.001f); | |||
jumpLights[i] = 1.0f; | |||
} | |||
} | |||
//if (symmetry) | |||
//stepIndexes[1] = stepIndexes[0]; | |||
// Reset | |||
if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { | |||
initRun(true, uncertainty); | |||
resetLight = 1.0f; | |||
clockTrigger.reset(); | |||
} | |||
else | |||
resetLight -= (resetLight / lightLambda) * sampleTime; | |||
//********** Outputs and lights ********** | |||
// Outputs | |||
for (int i = 0; i < 2; i++) { | |||
float knobVal = params[CV_PARAMS + cvMap[i][stepIndexes[i]]].value; | |||
float cv = 0.0f; | |||
int range = ranges[i]; | |||
if ( (i == 0 && (quantize & 0x1) != 0) || (i == 1 && (quantize > 1)) ) { | |||
cv = (knobVal * (float)(range * 2 + 1) - (float)range); | |||
cv = quantizeCV(cv); | |||
} | |||
else { | |||
int maxCV = (range == 0 ? 1 : (range * 5));// maxCV is [1, 5, 10] | |||
cv = knobVal * (float)(maxCV * 2) - (float)maxCV; | |||
} | |||
outputs[SEQ_OUTPUTS + i].value = cv; | |||
outputs[JUMP_OUTPUTS + i].value = jumpPulses[i].process((float)sampleTime); | |||
} | |||
// Blue and Yellow lights | |||
for (int i = 0; i < 16; i++) { | |||
lights[BLUE_LIGHTS + i].value = (stepIndexes[0] == i ? 1.0f : 0.0f); | |||
lights[YELLOW_LIGHTS + i].value = (stepIndexes[1] == i ? 1.0f : 0.0f); | |||
} | |||
// Reset light | |||
lights[RESET_LIGHT].value = resetLight; | |||
// Run light | |||
lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; | |||
// State lights | |||
for (int i = 0; i < 2; i++) { | |||
lights[GLOBAL_LIGHTS + i].value = (states[i] & 0x1) == 0 ? 0.5f : 0.0f; | |||
lights[LOCAL_LIGHTS + i].value = states[i] >= 1 ? 0.5f : 0.0f; | |||
} | |||
// Leap, Plank, uncertainty and ResetOnRun lights | |||
lights[LEAP_LIGHT].value = leap ? 1.0f : 0.0f; | |||
lights[PLANK_LIGHT + 0].value = (quantize == 1) ? 1.0f : 0.0f;// Blue | |||
lights[PLANK_LIGHT + 1].value = (quantize == 2) ? 1.0f : 0.0f;// Yellow | |||
lights[PLANK_LIGHT + 2].value = (quantize == 3) ? 1.0f : 0.0f;// White | |||
lights[uncertainty_LIGHT].value = uncertainty ? 1.0f : 0.0f; | |||
lights[RESETONRUN_LIGHT].value = resetOnRun ? 1.0f : 0.0f; | |||
// Range lights | |||
for (int i = 0; i < 3; i++) { | |||
lights[OCTA_LIGHTS + i].value = (i <= ranges[0] ? 1.0f : 0.0f); | |||
lights[OCTB_LIGHTS + i].value = (i <= ranges[1] ? 1.0f : 0.0f); | |||
} | |||
// Jump lights | |||
for (int i = 0; i < 2; i++) { | |||
lights[JUMP_LIGHTS + i].value = jumpLights[i]; | |||
jumpLights[i] -= (jumpLights[i] / lightLambda) * sampleTime; | |||
} | |||
// Step clocks light | |||
if (stepClocksTrig) | |||
stepClocksLight = 1.0f; | |||
else | |||
stepClocksLight -= (stepClocksLight / lightLambda) * sampleTime; | |||
lights[STEPCLOCKS_LIGHT].value = stepClocksLight; | |||
if (clockIgnoreOnReset > 0l) | |||
clockIgnoreOnReset--; | |||
}// step() | |||
int stepElectron(int i, bool leap) { | |||
int jumped = 0; | |||
int base = stepIndexes[i] & 0x8;// 0 or 8 | |||
int step8 = stepIndexes[i] & 0x7;// 0 to 7 | |||
if ( (step8 == 7 || leap) && jumpRandom() ) { | |||
jumped = 1; | |||
base = 8 - base;// change atom | |||
} | |||
step8++; | |||
if (step8 > 7) | |||
step8 = 0; | |||
stepIndexes[i] = base | step8; | |||
return jumped; | |||
} | |||
}; | |||
struct IonsWidget : ModuleWidget { | |||
struct PanelThemeItem : MenuItem { | |||
Ions *module; | |||
int theme; | |||
void onAction(EventAction &e) override { | |||
module->panelTheme = theme; | |||
} | |||
void step() override { | |||
rightText = (module->panelTheme == theme) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
Ions *module = dynamic_cast<Ions*>(this->module); | |||
assert(module); | |||
MenuLabel *themeLabel = new MenuLabel(); | |||
themeLabel->text = "Panel Theme"; | |||
menu->addChild(themeLabel); | |||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||
lightItem->text = lightPanelID;// Geodesics.hpp | |||
lightItem->module = module; | |||
lightItem->theme = 0; | |||
menu->addChild(lightItem); | |||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||
darkItem->text = darkPanelID;// Geodesics.hpp | |||
darkItem->module = module; | |||
darkItem->theme = 1; | |||
//menu->addChild(darkItem); | |||
return menu; | |||
} | |||
IonsWidget(Ions *module) : ModuleWidget(module) { | |||
// Main panel from Inkscape | |||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/IonsBG-01.svg"))); | |||
//panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/IonsBG-02.svg")));// no dark pannel for now | |||
box.size = panel->box.size; | |||
panel->mode = &module->panelTheme; | |||
addChild(panel); | |||
// Screws | |||
// part of svg panel, no code required | |||
float colRulerCenter = box.size.x / 2.0f; | |||
static constexpr float rowRulerAtomA = 125.5; | |||
static constexpr float rowRulerAtomB = 251.5f; | |||
static constexpr float radius1 = 21.0f; | |||
static constexpr float offset1 = 14.0f; | |||
static constexpr float radius2 = 35.0f; | |||
static constexpr float offset2 = 25.0f; | |||
static constexpr float radius3 = 61.0f; | |||
static constexpr float offset3 = 43.0f; | |||
// Outputs | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerAtomA), Port::OUTPUT, module, Ions::SEQ_OUTPUTS + 0, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerAtomB), Port::OUTPUT, module, Ions::SEQ_OUTPUTS + 1, &module->panelTheme)); | |||
// CV knobs | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerAtomA + radius3 + 2.0f), module, Ions::CV_PARAMS + 0, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomA + offset3), module, Ions::CV_PARAMS + 1, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + radius3, rowRulerAtomA), module, Ions::CV_PARAMS + 2, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomA - offset3), module, Ions::CV_PARAMS + 3, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerAtomA - radius3), module, Ions::CV_PARAMS + 4, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomA - offset3), module, Ions::CV_PARAMS + 5, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - radius3, rowRulerAtomA), module, Ions::CV_PARAMS + 6, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomA + offset3), module, Ions::CV_PARAMS + 7, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
// | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomB - offset3), module, Ions::CV_PARAMS + 8, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + radius3, rowRulerAtomB), module, Ions::CV_PARAMS + 9, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter + offset3, rowRulerAtomB + offset3), module, Ions::CV_PARAMS + 10, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter, rowRulerAtomB + radius3), module, Ions::CV_PARAMS + 11, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomB + offset3), module, Ions::CV_PARAMS + 12, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - radius3, rowRulerAtomB), module, Ions::CV_PARAMS + 13, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoKnob>(Vec(colRulerCenter - offset3, rowRulerAtomB - offset3), module, Ions::CV_PARAMS + 14, 0.0f, 1.0f, 0.5f, &module->panelTheme)); | |||
// Prob knob and CV inuput | |||
float probX = colRulerCenter + 2.0f * offset3; | |||
float probY = rowRulerAtomA + radius3 + 2.0f; | |||
addParam(createDynamicParam<GeoKnobLeft>(Vec(probX, probY), module, Ions::PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(probX + 32.0f, probY), Port::INPUT, module, Ions::PROB_INPUT, &module->panelTheme)); | |||
// Jump pulses | |||
addOutput(createDynamicPort<GeoPort>(Vec(probX + 18.0f, probY - 37.0f), Port::OUTPUT, module, Ions::JUMP_OUTPUTS + 0, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(probX + 18.0f, probY + 37.0f), Port::OUTPUT, module, Ions::JUMP_OUTPUTS + 1, &module->panelTheme)); | |||
// Jump lights | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(probX, probY - 46.0f), module, Ions::JUMP_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(probX, probY + 46.0f), module, Ions::JUMP_LIGHTS + 1)); | |||
// Leap light and button | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - 86.5f, 62.5f), module, Ions::LEAP_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - 77.5f, 50.5f), module, Ions::LEAP_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// Plank light and button | |||
addChild(createLightCentered<SmallLight<GeoBlueYellowWhiteLight>>(Vec(colRulerCenter + 86.5f, 62.5f), module, Ions::PLANK_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + 77.5f, 50.5f), module, Ions::PLANK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// Octave buttons and lights | |||
float octX = colRulerCenter + 107.0f; | |||
float octOffsetY = 10.0f; | |||
float octYA = rowRulerAtomA - octOffsetY; | |||
float octYB = rowRulerAtomB + octOffsetY; | |||
// top: | |||
addParam(createDynamicParam<GeoPushButton>(Vec(octX, octYA), module, Ions::OCT_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 15.0f, octYA + 2.5f), module, Ions::OCTA_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 12.0f, octYA - 8.0f), module, Ions::OCTA_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 10.0f, octYA + 11.5f), module, Ions::OCTA_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX - 3.0f, octYA - 13.5f), module, Ions::OCTA_LIGHTS + 2)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(octX + 0.0f, octYA + 15.0f), module, Ions::OCTA_LIGHTS + 2)); | |||
// bottom: | |||
addParam(createDynamicParam<GeoPushButton>(Vec(octX, octYB), module, Ions::OCT_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 15.0f, octYB - 2.5f), module, Ions::OCTB_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 12.0f, octYB + 8.0f), module, Ions::OCTB_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 10.0f, octYB - 11.5f), module, Ions::OCTB_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX - 3.0f, octYB + 13.5f), module, Ions::OCTB_LIGHTS + 2)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(octX + 0.0f, octYB - 15.0f), module, Ions::OCTB_LIGHTS + 2)); | |||
// Oct CV inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(octX - 9.0f, octYA - 34.5), Port::INPUT, module, Ions::OCTCV_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(octX - 9.0f, octYB + 34.5), Port::INPUT, module, Ions::OCTCV_INPUTS + 1, &module->panelTheme)); | |||
// Blue electron lights | |||
// top blue | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomA + radius2), module, Ions::BLUE_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset2, rowRulerAtomA + offset2), module, Ions::BLUE_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radius2, rowRulerAtomA), module, Ions::BLUE_LIGHTS + 2)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset2, rowRulerAtomA - offset2), module, Ions::BLUE_LIGHTS + 3)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomA - radius2), module, Ions::BLUE_LIGHTS + 4)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset2, rowRulerAtomA - offset2), module, Ions::BLUE_LIGHTS + 5)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radius2, rowRulerAtomA), module, Ions::BLUE_LIGHTS + 6)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset2, rowRulerAtomA + offset2), module, Ions::BLUE_LIGHTS + 7)); | |||
// bottom blue | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomB - radius1), module, Ions::BLUE_LIGHTS + 8)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset1, rowRulerAtomB - offset1), module, Ions::BLUE_LIGHTS + 9)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radius1, rowRulerAtomB), module, Ions::BLUE_LIGHTS + 10)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offset1, rowRulerAtomB + offset1), module, Ions::BLUE_LIGHTS + 11)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerAtomB + radius1), module, Ions::BLUE_LIGHTS + 12)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset1, rowRulerAtomB + offset1), module, Ions::BLUE_LIGHTS + 13)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radius1, rowRulerAtomB), module, Ions::BLUE_LIGHTS + 14)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offset1, rowRulerAtomB - offset1), module, Ions::BLUE_LIGHTS + 15)); | |||
// Yellow electron lights | |||
// bottom yellow | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomB - radius2), module, Ions::YELLOW_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset2, rowRulerAtomB - offset2), module, Ions::YELLOW_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + radius2, rowRulerAtomB), module, Ions::YELLOW_LIGHTS + 2)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset2, rowRulerAtomB + offset2), module, Ions::YELLOW_LIGHTS + 3)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomB + radius2), module, Ions::YELLOW_LIGHTS + 4)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset2, rowRulerAtomB + offset2), module, Ions::YELLOW_LIGHTS + 5)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - radius2, rowRulerAtomB), module, Ions::YELLOW_LIGHTS + 6)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset2, rowRulerAtomB - offset2), module, Ions::YELLOW_LIGHTS + 7)); | |||
// top yellow | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomA + radius1), module, Ions::YELLOW_LIGHTS + 8)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset1, rowRulerAtomA + offset1), module, Ions::YELLOW_LIGHTS + 9)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + radius1, rowRulerAtomA), module, Ions::YELLOW_LIGHTS + 10)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter + offset1, rowRulerAtomA - offset1), module, Ions::YELLOW_LIGHTS + 11)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter, rowRulerAtomA - radius1), module, Ions::YELLOW_LIGHTS + 12)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset1, rowRulerAtomA - offset1), module, Ions::YELLOW_LIGHTS + 13)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - radius1, rowRulerAtomA), module, Ions::YELLOW_LIGHTS + 14)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(colRulerCenter - offset1, rowRulerAtomA + offset1), module, Ions::YELLOW_LIGHTS + 15)); | |||
// Run jack, light and button | |||
static constexpr float rowRulerRunJack = 344.5f; | |||
static constexpr float offsetRunJackX = 119.5f; | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetRunJackX, rowRulerRunJack), Port::INPUT, module, Ions::RUN_INPUT, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRunJackX + 18.0f, rowRulerRunJack), module, Ions::RUN_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetRunJackX + 33.0f, rowRulerRunJack), module, Ions::RUN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// Reset jack, light and button | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetRunJackX, rowRulerRunJack), Port::INPUT, module, Ions::RESET_INPUT, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRunJackX - 18.0f, rowRulerRunJack), module, Ions::RESET_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetRunJackX - 33.0f, rowRulerRunJack), module, Ions::RESET_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
static constexpr float offsetMagneticButton = 42.5f; | |||
// Magnetic clock (step clocks) | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetMagneticButton - 15.0f, rowRulerRunJack), module, Ions::STEPCLOCKS_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetMagneticButton, rowRulerRunJack), module, Ions::STEPCLOCKS_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// Reset on Run light and button | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetMagneticButton + 15.0f, rowRulerRunJack), module, Ions::RESETONRUN_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetMagneticButton, rowRulerRunJack), module, Ions::RESETONRUN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// Globak clock | |||
float gclkX = colRulerCenter - 2.0f * offset3; | |||
float gclkY = rowRulerAtomA + radius3 + 2.0f; | |||
addInput(createDynamicPort<GeoPort>(Vec(gclkX, gclkY), Port::INPUT, module, Ions::CLK_INPUT, &module->panelTheme)); | |||
// global lights | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(gclkX - 12.0f, gclkY - 20.0f), module, Ions::GLOBAL_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(gclkX - 12.0f, gclkY + 20.0f), module, Ions::GLOBAL_LIGHTS + 1)); | |||
// state buttons | |||
addParam(createDynamicParam<GeoPushButton>(Vec(gclkX - 17.0f, gclkY - 34.0f), module, Ions::STATE_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(gclkX - 17.0f, gclkY + 34.0f), module, Ions::STATE_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// local lights | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(gclkX - 20.0f, gclkY - 48.5f), module, Ions::LOCAL_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoYellowLight>>(Vec(gclkX - 20.0f, gclkY + 48.5f), module, Ions::LOCAL_LIGHTS + 1)); | |||
// local inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(gclkX - 21.0f, gclkY - 72.0f), Port::INPUT, module, Ions::CLK_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(gclkX - 21.0f, gclkY + 72.0f), Port::INPUT, module, Ions::CLK_INPUTS + 1, &module->panelTheme)); | |||
// state inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(gclkX - 11.0f, gclkY - 107.0f), Port::INPUT, module, Ions::STATECV_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(gclkX - 11.0f, gclkY + 107.0f), Port::INPUT, module, Ions::STATECV_INPUTS + 1, &module->panelTheme)); | |||
// uncertainty light and button | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(gclkX - 20.0f, gclkY), module, Ions::uncertainty_LIGHT)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(gclkX - 34.0f, gclkY), module, Ions::uncertainty_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
} | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
RACK_PLUGIN_MODEL_INIT(Geodesics, Ions) { | |||
Model *modelIons = Model::create<Ions, IonsWidget>("Geodesics", "Ions", "Ions", SEQUENCER_TAG); | |||
return modelIons; | |||
} | |||
/*CHANGE LOG | |||
0.6.1: | |||
Ions reloaded (many changes) | |||
0.6.0: | |||
created | |||
*/ |
@@ -0,0 +1,569 @@ | |||
//*********************************************************************************************** | |||
//Neutron Powered Rotating Crossfader module for VCV Rack by Pierre Collard and Marc Boulé | |||
// | |||
//Based on code from the Fundamental plugins by Andrew Belt and graphics | |||
// from the Component Library by Wes Milholen. | |||
//See ./LICENSE.txt for all licenses | |||
//See ./res/fonts/ for font licenses | |||
// | |||
//*********************************************************************************************** | |||
#include "Geodesics.hpp" | |||
namespace rack_plugin_Geodesics { | |||
struct Pulsars : Module { | |||
enum ParamIds { | |||
ENUMS(VOID_PARAMS, 2),// push-button | |||
ENUMS(REV_PARAMS, 2),// push-button | |||
ENUMS(RND_PARAMS, 2),// push-button | |||
ENUMS(CVLEVEL_PARAMS, 2),// push-button | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
ENUMS(INA_INPUTS, 8), | |||
INB_INPUT, | |||
ENUMS(LFO_INPUTS, 2), | |||
ENUMS(VOID_INPUTS, 2), | |||
ENUMS(REV_INPUTS, 2), | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
OUTA_OUTPUT, | |||
ENUMS(OUTB_OUTPUTS, 8), | |||
NUM_OUTPUTS | |||
}; | |||
enum LightIds { | |||
ENUMS(LFO_LIGHTS, 2), | |||
ENUMS(MIXA_LIGHTS, 8), | |||
ENUMS(MIXB_LIGHTS, 8), | |||
ENUMS(VOID_LIGHTS, 2), | |||
ENUMS(REV_LIGHTS, 2), | |||
ENUMS(RND_LIGHTS, 2), | |||
ENUMS(CVALEVEL_LIGHTS, 2),// White, but two lights (light 0 is cvMode bit = 0, light 1 is cvMode bit = 1) | |||
ENUMS(CVBLEVEL_LIGHTS, 2),// White, but two lights | |||
NUM_LIGHTS | |||
}; | |||
// Constants | |||
static constexpr float epsilon = 0.0001f;// pulsar crossovers at epsilon and 1-epsilon in 0.0f to 1.0f space | |||
// Need to save, with reset | |||
bool isVoid[2]; | |||
bool isReverse[2]; | |||
bool isRandom[2]; | |||
int cvMode;// 0 is -5v to 5v, 1 is -10v to 10v; bit 0 is upper Pulsar, bit 1 is lower Pulsar | |||
// Need to save, no reset | |||
int panelTheme; | |||
// No need to save, with reset | |||
int posA;// always between 0 and 7 | |||
int posB;// always between 0 and 7 | |||
int posAnext;// always between 0 and 7 | |||
int posBnext;// always between 0 and 7 | |||
bool topCross[2]; | |||
// No need to save, no reset | |||
SchmittTrigger voidTriggers[2]; | |||
SchmittTrigger revTriggers[2]; | |||
SchmittTrigger rndTriggers[2]; | |||
SchmittTrigger cvLevelTriggers[2]; | |||
float lfoLights[2]; | |||
Pulsars() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
// Need to save, no reset | |||
panelTheme = 0; | |||
// No need to save, no reset | |||
for (int i = 0; i < 2; i++) { | |||
lfoLights[i] = 0.0f; | |||
voidTriggers[i].reset(); | |||
revTriggers[i].reset(); | |||
rndTriggers[i].reset(); | |||
} | |||
onReset(); | |||
} | |||
// widgets are not yet created when module is created | |||
// even if widgets not created yet, can use params[] and should handle 0.0f value since step may call | |||
// this before widget creation anyways | |||
// called from the main thread if by constructor, called by engine thread if right-click initialization | |||
// when called by constructor, module is created before the first step() is called | |||
void onReset() override { | |||
// Need to save, with reset | |||
cvMode = 0; | |||
for (int i = 0; i < 2; i++) { | |||
topCross[i] = false; | |||
isVoid[i] = false; | |||
isReverse[i] = false; | |||
isRandom[i] = false; | |||
} | |||
// No need to save, with reset | |||
posA = 0;// no need to check isVoid here, will be checked in step() | |||
posB = 0;// no need to check isVoid here, will be checked in step() | |||
posAnext = 1;// no need to check isVoid here, will be checked in step() | |||
posBnext = 1;// no need to check isVoid here, will be checked in step() | |||
} | |||
// widgets randomized before onRandomize() is called | |||
// called by engine thread if right-click randomize | |||
void onRandomize() override { | |||
// Need to save, with reset | |||
for (int i = 0; i < 2; i++) { | |||
isVoid[i] = (randomu32() % 2) > 0; | |||
isReverse[i] = (randomu32() % 2) > 0; | |||
isRandom[i] = (randomu32() % 2) > 0; | |||
} | |||
// No need to save, with reset | |||
posA = randomu32() % 8;// no need to check isVoid here, will be checked in step() | |||
posB = randomu32() % 8;// no need to check isVoid here, will be checked in step() | |||
posAnext = (posA + (isReverse[0] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step() | |||
posBnext = (posB + (isReverse[1] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step() | |||
} | |||
// called by main thread | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
// Need to save (reset or not) | |||
// isVoid | |||
json_object_set_new(rootJ, "isVoid0", json_real(isVoid[0])); | |||
json_object_set_new(rootJ, "isVoid1", json_real(isVoid[1])); | |||
// isReverse | |||
json_object_set_new(rootJ, "isReverse0", json_real(isReverse[0])); | |||
json_object_set_new(rootJ, "isReverse1", json_real(isReverse[1])); | |||
// isRandom | |||
json_object_set_new(rootJ, "isRandom0", json_real(isRandom[0])); | |||
json_object_set_new(rootJ, "isRandom1", json_real(isRandom[1])); | |||
// panelTheme | |||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||
// cvMode | |||
json_object_set_new(rootJ, "cvMode", json_integer(cvMode)); | |||
return rootJ; | |||
} | |||
// widgets have their fromJson() called before this fromJson() is called | |||
// called by main thread | |||
void fromJson(json_t *rootJ) override { | |||
// Need to save (reset or not) | |||
// isVoid | |||
json_t *isVoid0J = json_object_get(rootJ, "isVoid0"); | |||
if (isVoid0J) | |||
isVoid[0] = json_real_value(isVoid0J); | |||
json_t *isVoid1J = json_object_get(rootJ, "isVoid1"); | |||
if (isVoid1J) | |||
isVoid[1] = json_real_value(isVoid1J); | |||
// isReverse | |||
json_t *isReverse0J = json_object_get(rootJ, "isReverse0"); | |||
if (isReverse0J) | |||
isReverse[0] = json_real_value(isReverse0J); | |||
json_t *isReverse1J = json_object_get(rootJ, "isReverse1"); | |||
if (isReverse1J) | |||
isReverse[1] = json_real_value(isReverse1J); | |||
// isRandom | |||
json_t *isRandom0J = json_object_get(rootJ, "isRandom0"); | |||
if (isRandom0J) | |||
isRandom[0] = json_real_value(isRandom0J); | |||
json_t *isRandom1J = json_object_get(rootJ, "isRandom1"); | |||
if (isRandom1J) | |||
isRandom[1] = json_real_value(isRandom1J); | |||
// panelTheme | |||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||
if (panelThemeJ) | |||
panelTheme = json_integer_value(panelThemeJ); | |||
// cvMode | |||
json_t *cvModeJ = json_object_get(rootJ, "cvMode"); | |||
if (cvModeJ) | |||
cvMode = json_integer_value(cvModeJ); | |||
// No need to save, with reset | |||
posA = 0;// no need to check isVoid here, will be checked in step() | |||
posB = 0;// no need to check isVoid here, will be checked in step() | |||
posAnext = (posA + (isReverse[0] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step() | |||
posBnext = (posB + (isReverse[1] ? 7 : 1)) % 8;// no need to check isVoid here, will be checked in step() | |||
} | |||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||
void step() override { | |||
// Void, Reverse and Random buttons | |||
for (int i = 0; i < 2; i++) { | |||
if (voidTriggers[i].process(params[VOID_PARAMS + i].value + inputs[VOID_INPUTS + i].value)) { | |||
isVoid[i] = !isVoid[i]; | |||
} | |||
if (revTriggers[i].process(params[REV_PARAMS + i].value + inputs[REV_INPUTS + i].value)) { | |||
isReverse[i] = !isReverse[i]; | |||
} | |||
if (rndTriggers[i].process(params[RND_PARAMS + i].value)) {// + inputs[RND_INPUTS + i].value)) { | |||
isRandom[i] = !isRandom[i]; | |||
} | |||
} | |||
// CV Level buttons | |||
for (int i = 0; i < 2; i++) { | |||
if (cvLevelTriggers[i].process(params[CVLEVEL_PARAMS + i].value)) | |||
cvMode ^= (0x1 << i); | |||
} | |||
// LFO values (normalized to 0.0f to 1.0f space, clamped and offset adjusted depending cvMode) | |||
float lfoVal[2]; | |||
lfoVal[0] = inputs[LFO_INPUTS + 0].value; | |||
lfoVal[1] = inputs[LFO_INPUTS + 1].active ? inputs[LFO_INPUTS + 1].value : lfoVal[0]; | |||
for (int i = 0; i < 2; i++) | |||
lfoVal[i] = clamp( (lfoVal[i] + ((cvMode & (0x1 << i)) == 0 ? 5.0f : 0.0f)) / 10.0f , 0.0f , 1.0f); | |||
// Pulsar A | |||
bool active8[8]; | |||
bool atLeastOneActive = false; | |||
for (int i = 0; i < 8; i++) { | |||
active8[i] = inputs[INA_INPUTS + i].active; | |||
if (active8[i]) | |||
atLeastOneActive = true; | |||
} | |||
if (atLeastOneActive) { | |||
if (!isVoid[0]) { | |||
if (!active8[posA])// ensure start on valid input when no void | |||
posA = getNextClosestActive(posA, active8, false, false, false); | |||
if (!active8[posAnext]) | |||
posAnext = getNextClosestActive(posA, active8, false, isReverse[0], isRandom[0]); | |||
} | |||
float posPercent = topCross[0] ? (1.0f - lfoVal[0]) : lfoVal[0]; | |||
float nextPosPercent = 1.0f - posPercent; | |||
outputs[OUTA_OUTPUT].value = posPercent * inputs[INA_INPUTS + posA].value + nextPosPercent * inputs[INA_INPUTS + posAnext].value; | |||
for (int i = 0; i < 8; i++) | |||
lights[MIXA_LIGHTS + i].setBrightness(0.0f + ((i == posA) ? posPercent : 0.0f) + ((i == posAnext) ? nextPosPercent : 0.0f)); | |||
// PulsarA crossover (LFO detection) | |||
if ( (topCross[0] && lfoVal[0] > (1.0f - epsilon)) || (!topCross[0] && lfoVal[0] < epsilon) ) { | |||
topCross[0] = !topCross[0];// switch to opposite detection | |||
posA = posAnext; | |||
posAnext = getNextClosestActive(posA, active8, isVoid[0], isReverse[0], isRandom[0]); | |||
lfoLights[0] = 1.0f; | |||
} | |||
} | |||
else { | |||
outputs[OUTA_OUTPUT].value = 0.0f; | |||
for (int i = 0; i < 8; i++) | |||
lights[MIXA_LIGHTS + i].value = 0.0f; | |||
} | |||
// Pulsar B | |||
atLeastOneActive = false; | |||
for (int i = 0; i < 8; i++) { | |||
active8[i] = outputs[OUTB_OUTPUTS + i].active; | |||
if (active8[i]) | |||
atLeastOneActive = true; | |||
} | |||
if (atLeastOneActive) { | |||
if (!isVoid[1]) { | |||
if (!active8[posB])// ensure start on valid output when no void | |||
posB = getNextClosestActive(posB, active8, false, false, false); | |||
if (!active8[posBnext]) | |||
posBnext = getNextClosestActive(posB, active8, false, isReverse[1], isRandom[1]); | |||
} | |||
float posPercent = topCross[1] ? (1.0f - lfoVal[1]) : lfoVal[1]; | |||
float nextPosPercent = 1.0f - posPercent; | |||
for (int i = 0; i < 8; i++) { | |||
if (inputs[INB_INPUT].active) | |||
outputs[OUTB_OUTPUTS + i].value = 0.0f + ((i == posB) ? (posPercent * inputs[INB_INPUT].value) : 0.0f) + ((i == posBnext) ? (nextPosPercent * inputs[INB_INPUT].value) : 0.0f); | |||
else// mutidimentional trick | |||
outputs[OUTB_OUTPUTS + i].value = 0.0f + ((i == posB) ? (posPercent * inputs[INA_INPUTS + i].value) : 0.0f) + ((i == posBnext) ? (nextPosPercent * inputs[INA_INPUTS + i].value) : 0.0f); | |||
lights[MIXB_LIGHTS + i].setBrightness(0.0f + ((i == posB) ? posPercent : 0.0f) + ((i == posBnext) ? nextPosPercent : 0.0f)); | |||
} | |||
// PulsarB crossover (LFO detection) | |||
if ( (topCross[1] && lfoVal[1] > (1.0f - epsilon)) || (!topCross[1] && lfoVal[1] < epsilon) ) { | |||
topCross[1] = !topCross[1];// switch to opposite detection | |||
posB = posBnext; | |||
posBnext = getNextClosestActive(posB, active8, isVoid[1], isReverse[1], isRandom[1]); | |||
lfoLights[1] = 1.0f; | |||
} | |||
} | |||
else { | |||
for (int i = 0; i < 8; i++) { | |||
outputs[OUTB_OUTPUTS + i].value = 0.0f; | |||
lights[MIXB_LIGHTS + i].value = 0.0f; | |||
} | |||
} | |||
// Void, Reverse and Random lights | |||
for (int i = 0; i < 2; i++) { | |||
lights[VOID_LIGHTS + i].value = isVoid[i] ? 1.0f : 0.0f; | |||
lights[REV_LIGHTS + i].value = isReverse[i] ? 1.0f : 0.0f; | |||
lights[RND_LIGHTS + i].value = isRandom[i] ? 1.0f : 0.0f; | |||
} | |||
// CV Level lights | |||
lights[CVALEVEL_LIGHTS + 0].value = (cvMode & 0x1) == 0 ? 1.0f : 0.0f; | |||
lights[CVALEVEL_LIGHTS + 1].value = 1.0f - lights[CVALEVEL_LIGHTS + 0].value; | |||
lights[CVBLEVEL_LIGHTS + 0].value = (cvMode & 0x2) == 0 ? 1.0f : 0.0f; | |||
lights[CVBLEVEL_LIGHTS + 1].value = 1.0f - lights[CVBLEVEL_LIGHTS + 0].value; | |||
// LFO lights | |||
for (int i = 0; i < 2; i++) { | |||
lights[LFO_LIGHTS + i].value = lfoLights[i]; | |||
lfoLights[i] -= (lfoLights[i] / lightLambda) * (float)engineGetSampleTime(); | |||
} | |||
}// step() | |||
int getNextClosestActive(int pos, bool* active8, bool voidd, bool reverse, bool random) { | |||
// finds the next closest active position (excluding current if active) | |||
// assumes at least one active, but may not be given pos; will always return an active pos | |||
// scans all 8 positions | |||
int posNext = -1;// should never be returned | |||
if (random) { | |||
if (voidd) | |||
posNext = (pos + 1 + randomu32() % 7) % 8; | |||
else { | |||
posNext = pos; | |||
int activeIndexes[8];// room for all indexes of active positions except current if active(max size is guaranteed to be < 8) | |||
int activeIndexesI = 0; | |||
for (int i = 0; i < 8; i++) { | |||
if (active8[i] && i != pos) { | |||
activeIndexes[activeIndexesI] = i; | |||
activeIndexesI++; | |||
} | |||
} | |||
if (activeIndexesI > 0) | |||
posNext = activeIndexes[randomu32()%activeIndexesI]; | |||
} | |||
} | |||
else { | |||
posNext = (pos + (reverse ? 7 : 1)) % 8;// void approach by default (choose slot whether active of not) | |||
if (!voidd) { | |||
if (reverse) { | |||
for (int i = posNext + 8; i > posNext; i--) { | |||
if (active8[i % 8]) { | |||
posNext = i % 8; | |||
break; | |||
} | |||
} | |||
} | |||
else { | |||
for (int i = posNext; i < posNext + 8; i++) { | |||
if (active8[i % 8]) { | |||
posNext = i % 8; | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
return posNext; | |||
} | |||
}; | |||
struct PulsarsWidget : ModuleWidget { | |||
struct PanelThemeItem : MenuItem { | |||
Pulsars *module; | |||
int theme; | |||
void onAction(EventAction &e) override { | |||
module->panelTheme = theme; | |||
} | |||
void step() override { | |||
rightText = (module->panelTheme == theme) ? "âś”" : ""; | |||
} | |||
}; | |||
Menu *createContextMenu() override { | |||
Menu *menu = ModuleWidget::createContextMenu(); | |||
MenuLabel *spacerLabel = new MenuLabel(); | |||
menu->addChild(spacerLabel); | |||
Pulsars *module = dynamic_cast<Pulsars*>(this->module); | |||
assert(module); | |||
MenuLabel *themeLabel = new MenuLabel(); | |||
themeLabel->text = "Panel Theme"; | |||
menu->addChild(themeLabel); | |||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||
lightItem->text = lightPanelID;// Geodesics.hpp | |||
lightItem->module = module; | |||
lightItem->theme = 0; | |||
menu->addChild(lightItem); | |||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||
darkItem->text = darkPanelID;// Geodesics.hpp | |||
darkItem->module = module; | |||
darkItem->theme = 1; | |||
//menu->addChild(darkItem); | |||
return menu; | |||
} | |||
PulsarsWidget(Pulsars *module) : ModuleWidget(module) { | |||
// Main panel from Inkscape | |||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PulsarsBG-01.svg"))); | |||
//panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PulsarsBG-02.svg")));// no dark pannel for now | |||
box.size = panel->box.size; | |||
panel->mode = &module->panelTheme; | |||
addChild(panel); | |||
// Screws | |||
// part of svg panel, no code required | |||
float colRulerCenter = box.size.x / 2.0f; | |||
static constexpr float rowRulerPulsarA = 127.5; | |||
static constexpr float rowRulerPulsarB = 261.5f; | |||
float rowRulerLFOlights = (rowRulerPulsarA + rowRulerPulsarB) / 2.0f; | |||
static constexpr float offsetLFOlightsX = 25.0f; | |||
static constexpr float radiusLeds = 23.0f; | |||
static constexpr float radiusJacks = 46.0f; | |||
static constexpr float offsetLeds = 17.0f; | |||
static constexpr float offsetJacks = 33.0f; | |||
static constexpr float offsetLFO = 24.0f;// used also for void and reverse CV input jacks | |||
static constexpr float offsetLedX = 13.0f;// adds/subs to offsetLFO for void and reverse lights | |||
static constexpr float offsetButtonX = 26.0f;// adds/subs to offsetLFO for void and reverse buttons | |||
static constexpr float offsetLedY = 11.0f;// adds/subs to offsetLFO for void and reverse lights | |||
static constexpr float offsetButtonY = 18.0f;// adds/subs to offsetLFO for void and reverse buttons | |||
static constexpr float offsetRndButtonX = 58.0f;// from center of pulsar | |||
static constexpr float offsetRndButtonY = 24.0f;// from center of pulsar | |||
static constexpr float offsetRndLedX = 63.0f;// from center of pulsar | |||
static constexpr float offsetRndLedY = 11.0f;// from center of pulsar | |||
static constexpr float offsetLedVsButBX = 8.0f;// BI | |||
static constexpr float offsetLedVsButBY = 10.0f; | |||
static constexpr float offsetLedVsButUX = 13.0f;// UNI | |||
static constexpr float offsetLedVsButUY = 1.0f; | |||
// PulsarA center output | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarA), Port::OUTPUT, module, Pulsars::OUTA_OUTPUT, &module->panelTheme)); | |||
// PulsarA inputs | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarA - radiusJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 0, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarA - offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 1, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusJacks, rowRulerPulsarA), Port::INPUT, module, Pulsars::INA_INPUTS + 2, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarA + offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 3, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarA + radiusJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 4, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarA + offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 5, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusJacks, rowRulerPulsarA), Port::INPUT, module, Pulsars::INA_INPUTS + 6, &module->panelTheme)); | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarA - offsetJacks), Port::INPUT, module, Pulsars::INA_INPUTS + 7, &module->panelTheme)); | |||
// PulsarA lights | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarA - radiusLeds), module, Pulsars::MIXA_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarA - offsetLeds), module, Pulsars::MIXA_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radiusLeds, rowRulerPulsarA), module, Pulsars::MIXA_LIGHTS + 2)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarA + offsetLeds), module, Pulsars::MIXA_LIGHTS + 3)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarA + radiusLeds), module, Pulsars::MIXA_LIGHTS + 4)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarA + offsetLeds), module, Pulsars::MIXA_LIGHTS + 5)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radiusLeds, rowRulerPulsarA), module, Pulsars::MIXA_LIGHTS + 6)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarA - offsetLeds), module, Pulsars::MIXA_LIGHTS + 7)); | |||
// PulsarA void (jack, light and button) | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks - offsetLFO, rowRulerPulsarA - offsetJacks - offsetLFO), Port::INPUT, module, Pulsars::VOID_INPUTS + 0, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetLedX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetLedY), module, Pulsars::VOID_LIGHTS + 0)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetButtonX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetButtonY), module, Pulsars::VOID_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PulsarA reverse (jack, light and button) | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks + offsetLFO, rowRulerPulsarA - offsetJacks - offsetLFO), Port::INPUT, module, Pulsars::REV_INPUTS + 0, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetLedX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetLedY), module, Pulsars::REV_LIGHTS + 0)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetButtonX, rowRulerPulsarA - offsetJacks - offsetLFO - offsetButtonY), module, Pulsars::REV_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PulsarA random (light and button) | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRndLedX, rowRulerPulsarA + offsetRndLedY), module, Pulsars::RND_LIGHTS + 0)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetRndButtonX, rowRulerPulsarA + offsetRndButtonY), module, Pulsars::RND_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PulsarA CV level (lights and button) | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetRndButtonX, rowRulerPulsarA + offsetRndButtonY), module, Pulsars::CVLEVEL_PARAMS + 0, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRndButtonX - offsetLedVsButBX, rowRulerPulsarA + offsetRndButtonY + offsetLedVsButBY), module, Pulsars::CVALEVEL_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRndButtonX - offsetLedVsButUX, rowRulerPulsarA + offsetRndButtonY - offsetLedVsButUY), module, Pulsars::CVALEVEL_LIGHTS + 1)); | |||
// PulsarA LFO input and light | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks - offsetLFO, rowRulerPulsarA + offsetJacks + offsetLFO), Port::INPUT, module, Pulsars::LFO_INPUTS + 0, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetLFOlightsX, rowRulerLFOlights), module, Pulsars::LFO_LIGHTS + 0)); | |||
// PulsarB center input | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarB), Port::INPUT, module, Pulsars::INB_INPUT, &module->panelTheme)); | |||
// PulsarB outputs | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarB - radiusJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 0, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarB - offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 1, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + radiusJacks, rowRulerPulsarB), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 2, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks, rowRulerPulsarB + offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 3, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter, rowRulerPulsarB + radiusJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 4, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarB + offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 5, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - radiusJacks, rowRulerPulsarB), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 6, &module->panelTheme)); | |||
addOutput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks, rowRulerPulsarB - offsetJacks), Port::OUTPUT, module, Pulsars::OUTB_OUTPUTS + 7, &module->panelTheme)); | |||
// PulsarB lights | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarB - radiusLeds), module, Pulsars::MIXB_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarB - offsetLeds), module, Pulsars::MIXB_LIGHTS + 1)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + radiusLeds, rowRulerPulsarB), module, Pulsars::MIXB_LIGHTS + 2)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter + offsetLeds, rowRulerPulsarB + offsetLeds), module, Pulsars::MIXB_LIGHTS + 3)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter, rowRulerPulsarB + radiusLeds), module, Pulsars::MIXB_LIGHTS + 4)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarB + offsetLeds), module, Pulsars::MIXB_LIGHTS + 5)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - radiusLeds, rowRulerPulsarB), module, Pulsars::MIXB_LIGHTS + 6)); | |||
addChild(createLightCentered<SmallLight<GeoBlueLight>>(Vec(colRulerCenter - offsetLeds, rowRulerPulsarB - offsetLeds), module, Pulsars::MIXB_LIGHTS + 7)); | |||
// PulsarB void (jack, light and button) | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter - offsetJacks - offsetLFO, rowRulerPulsarB + offsetJacks + offsetLFO), Port::INPUT, module, Pulsars::VOID_INPUTS + 1, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetLedX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetLedY), module, Pulsars::VOID_LIGHTS + 1)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetJacks - offsetLFO + offsetButtonX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetButtonY), module, Pulsars::VOID_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PulsarB reverse (jack, light and button) | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks + offsetLFO, rowRulerPulsarB + offsetJacks + offsetLFO), Port::INPUT, module, Pulsars::REV_INPUTS + 1, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetLedX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetLedY), module, Pulsars::REV_LIGHTS + 1)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetJacks + offsetLFO - offsetButtonX, rowRulerPulsarB + offsetJacks + offsetLFO + offsetButtonY), module, Pulsars::REV_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PulsarB random (light and button) | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter - offsetRndLedX, rowRulerPulsarB - offsetRndLedY), module, Pulsars::RND_LIGHTS + 1)); | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter - offsetRndButtonX, rowRulerPulsarB - offsetRndButtonY), module, Pulsars::RND_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
// PulsarB CV level (lights and button) | |||
addParam(createDynamicParam<GeoPushButton>(Vec(colRulerCenter + offsetRndButtonX, rowRulerPulsarB - offsetRndButtonY), module, Pulsars::CVLEVEL_PARAMS + 1, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRndButtonX + offsetLedVsButBX, rowRulerPulsarB - offsetRndButtonY - offsetLedVsButBY), module, Pulsars::CVBLEVEL_LIGHTS + 0)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetRndButtonX + offsetLedVsButUX, rowRulerPulsarB - offsetRndButtonY + offsetLedVsButUY), module, Pulsars::CVBLEVEL_LIGHTS + 1)); | |||
// PulsarA LFO input and light | |||
addInput(createDynamicPort<GeoPort>(Vec(colRulerCenter + offsetJacks + offsetLFO, rowRulerPulsarB - offsetJacks - offsetLFO), Port::INPUT, module, Pulsars::LFO_INPUTS + 1, &module->panelTheme)); | |||
addChild(createLightCentered<SmallLight<GeoWhiteLight>>(Vec(colRulerCenter + offsetLFOlightsX, rowRulerLFOlights), module, Pulsars::LFO_LIGHTS + 1)); | |||
} | |||
}; | |||
} // namespace rack_plugin_Geodesics | |||
using namespace rack_plugin_Geodesics; | |||
RACK_PLUGIN_MODEL_INIT(Geodesics, Pulsars) { | |||
Model *modelPulsars = Model::create<Pulsars, PulsarsWidget>("Geodesics", "Pulsars", "Pulsars", MIXER_TAG); | |||
return modelPulsars; | |||
} | |||
/*CHANGE LOG | |||
0.6.1: | |||
add CV level modes buttons and lights, remove from right-click menu | |||
0.6.0: | |||
created | |||
*/ |
@@ -5,6 +5,7 @@ ALL_OBJ= \ | |||
src/dsp/Lockhart.o \ | |||
src/dsp/MS20zdf.o \ | |||
src/dsp/Oscillator.o \ | |||
src/dsp/Saturator.o \ | |||
src/dsp/Serge.o \ | |||
src/dsp/WaveShaper.o \ | |||
src/widgets/LRCVIndicator.o \ | |||
@@ -22,4 +23,5 @@ ALL_OBJ= \ | |||
src/ReShaper.o \ | |||
src/SimpleFilter.o \ | |||
src/VCO.o \ | |||
src/Westcoast_v1.o \ | |||
src/Westcoast.o |