Browse Source

add AmalgamatedHarmonics, Geodesics, mscHack (update) modules

pull/1639/head
bsp2 6 years ago
parent
commit
60b25dd72e
100 changed files with 48740 additions and 87 deletions
  1. +27
    -2
      README.md
  2. +130
    -0
      include/helpers.hpp
  3. +1
    -85
      include/rack.hpp
  4. +29
    -0
      plugins/community/repos/AmalgamatedHarmonics/LICENSE
  5. +27
    -0
      plugins/community/repos/AmalgamatedHarmonics/Makefile
  6. +14
    -0
      plugins/community/repos/AmalgamatedHarmonics/README.md
  7. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/54.jpg
  8. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/all.jpg
  9. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/arp.jpg
  10. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/arp2.jpg
  11. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/imperfect.jpg
  12. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/imperfect2.jpg
  13. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/progress.jpg
  14. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/quant1.jpg
  15. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/quant2.jpg
  16. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/ruckus.jpg
  17. BIN
      plugins/community/repos/AmalgamatedHarmonics/doc/sln.jpg
  18. +14
    -0
      plugins/community/repos/AmalgamatedHarmonics/make.objects
  19. +7
    -0
      plugins/community/repos/AmalgamatedHarmonics/makefile.msvc
  20. +1936
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Arpeggiator.svg
  21. +1943
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Arpeggiator2.svg
  22. +2146
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Circle.svg
  23. +81
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/ComponentLibrary/AHButton.svg
  24. +101
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/ComponentLibrary/AHKnob.svg
  25. +433
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/ComponentLibrary/AHTrimpot.svg
  26. +95
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/DSEG-LICENSE.txt
  27. BIN
      plugins/community/repos/AmalgamatedHarmonics/res/DSEG14ClassicMini-BoldItalic.ttf
  28. BIN
      plugins/community/repos/AmalgamatedHarmonics/res/EurostileBold.ttf
  29. +1615
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Imperfect.svg
  30. +1740
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Imperfect2.svg
  31. +2130
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Progress.svg
  32. BIN
      plugins/community/repos/AmalgamatedHarmonics/res/Roboto-Light.ttf
  33. +1490
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/Ruckus.svg
  34. +1559
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/SLN.svg
  35. +2313
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/ScaleQuantizer.svg
  36. +2230
    -0
      plugins/community/repos/AmalgamatedHarmonics/res/ScaleQuantizerMkII.svg
  37. +30
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/AH.cpp
  38. +7
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/AH.hpp
  39. +580
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Arpeggiator.cpp
  40. +1049
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/ArpeggiatorMkII.cpp
  41. +306
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Circle.cpp
  42. +205
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Core.cpp
  43. +527
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Core.hpp
  44. +213
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Imperfect.cpp
  45. +416
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Imperfect2.cpp
  46. +578
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Progress.cpp
  47. +314
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/Ruckus.cpp
  48. +173
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/SLN.cpp
  49. +167
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/ScaleQuantizer.cpp
  50. +215
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/ScaleQuantizerMkII.cpp
  51. +80
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/UI.cpp
  52. +206
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/UI.hpp
  53. +29
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/dsp/Bogaudio-LICENSE.txt
  54. +107
    -0
      plugins/community/repos/AmalgamatedHarmonics/src/dsp/noise.hpp
  55. +1899
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Arpeggiator2_src.svg
  56. +1862
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Arpeggiator_src.svg
  57. +1932
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Circle_src.svg
  58. +1668
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Imperfect2_src.svg
  59. +1593
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Imperfect_src.svg
  60. +2024
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Progress_src.svg
  61. +1486
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/Ruckus_src.svg
  62. +1539
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/SLN_src.svg
  63. +2079
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/ScaleQuantizerMkII_src.svg
  64. +2328
    -0
      plugins/community/repos/AmalgamatedHarmonics/ui/ScaleQuantizer_src.svg
  65. +3
    -0
      plugins/community/repos/Geodesics/.gitignore
  66. BIN
      plugins/community/repos/Geodesics/GeodesicsUserManual061.pdf
  67. BIN
      plugins/community/repos/Geodesics/GeodesicsWhatsNew061.pdf
  68. +85
    -0
      plugins/community/repos/Geodesics/LICENSE.txt
  69. +27
    -0
      plugins/community/repos/Geodesics/Makefile
  70. +59
    -0
      plugins/community/repos/Geodesics/README.md
  71. +9
    -0
      plugins/community/repos/Geodesics/make.objects
  72. +7
    -0
      plugins/community/repos/Geodesics/makefile.msvc
  73. +7
    -0
      plugins/community/repos/Geodesics/res/comp/C-01.svg
  74. +4
    -0
      plugins/community/repos/Geodesics/res/comp/Otrsp-01.svg
  75. BIN
      plugins/community/repos/Geodesics/res/img/BlackHoles.jpg
  76. BIN
      plugins/community/repos/Geodesics/res/img/Blanks.jpg
  77. BIN
      plugins/community/repos/Geodesics/res/img/Branes.jpg
  78. BIN
      plugins/community/repos/Geodesics/res/img/Ions.jpg
  79. BIN
      plugins/community/repos/Geodesics/res/img/Pulsars.jpg
  80. +382
    -0
      plugins/community/repos/Geodesics/res/light/BlackHolesBG-01.svg
  81. +115
    -0
      plugins/community/repos/Geodesics/res/light/BlankInfo-01.svg
  82. +325
    -0
      plugins/community/repos/Geodesics/res/light/BlankLogoBG-01.svg
  83. +271
    -0
      plugins/community/repos/Geodesics/res/light/BranesBG-01.svg
  84. +522
    -0
      plugins/community/repos/Geodesics/res/light/IonsBG-01.svg
  85. +217
    -0
      plugins/community/repos/Geodesics/res/light/PulsarsBG-01.svg
  86. +8
    -0
      plugins/community/repos/Geodesics/res/light/comp/Jack.svg
  87. +8
    -0
      plugins/community/repos/Geodesics/res/light/comp/Knob.svg
  88. +6
    -0
      plugins/community/repos/Geodesics/res/light/comp/PushButton1_0.svg
  89. +6
    -0
      plugins/community/repos/Geodesics/res/light/comp/PushButton1_1.svg
  90. +412
    -0
      plugins/community/repos/Geodesics/src/BlackHoles.cpp
  91. +109
    -0
      plugins/community/repos/Geodesics/src/BlankInfo.cpp
  92. +178
    -0
      plugins/community/repos/Geodesics/src/BlankLogo.cpp
  93. +427
    -0
      plugins/community/repos/Geodesics/src/Branes.cpp
  94. +210
    -0
      plugins/community/repos/Geodesics/src/GeoWidgets.cpp
  95. +117
    -0
      plugins/community/repos/Geodesics/src/GeoWidgets.hpp
  96. +33
    -0
      plugins/community/repos/Geodesics/src/Geodesics.cpp
  97. +162
    -0
      plugins/community/repos/Geodesics/src/Geodesics.hpp
  98. +787
    -0
      plugins/community/repos/Geodesics/src/Ions.cpp
  99. +569
    -0
      plugins/community/repos/Geodesics/src/Pulsars.cpp
  100. +2
    -0
      plugins/community/repos/LindenbergResearch/make.objects

+ 27
- 2
README.md View File

@@ -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


+ 130
- 0
include/helpers.hpp View File

@@ -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

+ 1
- 85
include/rack.hpp View File

@@ -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"

+ 29
- 0
plugins/community/repos/AmalgamatedHarmonics/LICENSE View File

@@ -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.

+ 27
- 0
plugins/community/repos/AmalgamatedHarmonics/Makefile View File

@@ -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

+ 14
- 0
plugins/community/repos/AmalgamatedHarmonics/README.md View File

@@ -0,0 +1,14 @@
Welcome to the Amalgamated Harmonics; your one-stop shop for barely usable modules for [VCVRack](www.vcvrack.com).

![All](./doc/all.jpg)

* [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.


BIN
plugins/community/repos/AmalgamatedHarmonics/doc/54.jpg View File

Before After
Width: 253  |  Height: 400  |  Size: 37KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/all.jpg View File

Before After
Width: 568  |  Height: 400  |  Size: 121KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/arp.jpg View File

Before After
Width: 253  |  Height: 400  |  Size: 40KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/arp2.jpg View File

Before After
Width: 249  |  Height: 400  |  Size: 40KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/imperfect.jpg View File

Before After
Width: 332  |  Height: 400  |  Size: 52KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/imperfect2.jpg View File

Before After
Width: 473  |  Height: 400  |  Size: 56KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/progress.jpg View File

Before After
Width: 410  |  Height: 400  |  Size: 64KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/quant1.jpg View File

Before After
Width: 253  |  Height: 400  |  Size: 47KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/quant2.jpg View File

Before After
Width: 317  |  Height: 400  |  Size: 61KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/ruckus.jpg View File

Before After
Width: 409  |  Height: 400  |  Size: 54KB

BIN
plugins/community/repos/AmalgamatedHarmonics/doc/sln.jpg View File

Before After
Width: 48  |  Height: 400  |  Size: 12KB

+ 14
- 0
plugins/community/repos/AmalgamatedHarmonics/make.objects View File

@@ -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

+ 7
- 0
plugins/community/repos/AmalgamatedHarmonics/makefile.msvc View File

@@ -0,0 +1,7 @@
SLUG=AmalgamatedHarmonics

include ../../../build_plugin_pre.mk

include make.objects

include ../../../build_plugin_post.mk

+ 1936
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Arpeggiator.svg
File diff suppressed because it is too large
View File


+ 1943
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Arpeggiator2.svg
File diff suppressed because it is too large
View File


+ 2146
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Circle.svg
File diff suppressed because it is too large
View File


+ 81
- 0
plugins/community/repos/AmalgamatedHarmonics/res/ComponentLibrary/AHButton.svg View File

@@ -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>

+ 101
- 0
plugins/community/repos/AmalgamatedHarmonics/res/ComponentLibrary/AHKnob.svg View File

@@ -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>

+ 433
- 0
plugins/community/repos/AmalgamatedHarmonics/res/ComponentLibrary/AHTrimpot.svg View File

@@ -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>

+ 95
- 0
plugins/community/repos/AmalgamatedHarmonics/res/DSEG-LICENSE.txt View File

@@ -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.

BIN
plugins/community/repos/AmalgamatedHarmonics/res/DSEG14ClassicMini-BoldItalic.ttf View File


BIN
plugins/community/repos/AmalgamatedHarmonics/res/EurostileBold.ttf View File


+ 1615
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Imperfect.svg
File diff suppressed because it is too large
View File


+ 1740
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Imperfect2.svg
File diff suppressed because it is too large
View File


+ 2130
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Progress.svg
File diff suppressed because it is too large
View File


BIN
plugins/community/repos/AmalgamatedHarmonics/res/Roboto-Light.ttf View File


+ 1490
- 0
plugins/community/repos/AmalgamatedHarmonics/res/Ruckus.svg
File diff suppressed because it is too large
View File


+ 1559
- 0
plugins/community/repos/AmalgamatedHarmonics/res/SLN.svg
File diff suppressed because it is too large
View File


+ 2313
- 0
plugins/community/repos/AmalgamatedHarmonics/res/ScaleQuantizer.svg
File diff suppressed because it is too large
View File


+ 2230
- 0
plugins/community/repos/AmalgamatedHarmonics/res/ScaleQuantizerMkII.svg
File diff suppressed because it is too large
View File


+ 30
- 0
plugins/community/repos/AmalgamatedHarmonics/src/AH.cpp View File

@@ -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);
}

+ 7
- 0
plugins/community/repos/AmalgamatedHarmonics/src/AH.hpp View File

@@ -0,0 +1,7 @@
#pragma once

#include "rack.hpp"

using namespace rack;

#define plugin "AmalgamatedHarmonics"

+ 580
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Arpeggiator.cpp View File

@@ -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;
}

+ 1049
- 0
plugins/community/repos/AmalgamatedHarmonics/src/ArpeggiatorMkII.cpp
File diff suppressed because it is too large
View File


+ 306
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Circle.cpp View File

@@ -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, &deg);
}
} 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;
}

// ♯♭

+ 205
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Core.cpp View File

@@ -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

+ 527
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Core.hpp View File

@@ -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

+ 213
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Imperfect.cpp View File

@@ -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;
}


+ 416
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Imperfect2.cpp View File

@@ -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;
}


+ 578
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Progress.cpp View File

@@ -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;
}

+ 314
- 0
plugins/community/repos/AmalgamatedHarmonics/src/Ruckus.cpp View File

@@ -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;
}


+ 173
- 0
plugins/community/repos/AmalgamatedHarmonics/src/SLN.cpp View File

@@ -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;
}


+ 167
- 0
plugins/community/repos/AmalgamatedHarmonics/src/ScaleQuantizer.cpp View File

@@ -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;
}


+ 215
- 0
plugins/community/repos/AmalgamatedHarmonics/src/ScaleQuantizerMkII.cpp View File

@@ -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;
}


+ 80
- 0
plugins/community/repos/AmalgamatedHarmonics/src/UI.cpp View File

@@ -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

+ 206
- 0
plugins/community/repos/AmalgamatedHarmonics/src/UI.hpp View File

@@ -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

+ 29
- 0
plugins/community/repos/AmalgamatedHarmonics/src/dsp/Bogaudio-LICENSE.txt View File

@@ -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.

+ 107
- 0
plugins/community/repos/AmalgamatedHarmonics/src/dsp/noise.hpp View File

@@ -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

+ 1899
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Arpeggiator2_src.svg
File diff suppressed because it is too large
View File


+ 1862
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Arpeggiator_src.svg
File diff suppressed because it is too large
View File


+ 1932
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Circle_src.svg
File diff suppressed because it is too large
View File


+ 1668
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Imperfect2_src.svg
File diff suppressed because it is too large
View File


+ 1593
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Imperfect_src.svg
File diff suppressed because it is too large
View File


+ 2024
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Progress_src.svg
File diff suppressed because it is too large
View File


+ 1486
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/Ruckus_src.svg
File diff suppressed because it is too large
View File


+ 1539
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/SLN_src.svg
File diff suppressed because it is too large
View File


+ 2079
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/ScaleQuantizerMkII_src.svg
File diff suppressed because it is too large
View File


+ 2328
- 0
plugins/community/repos/AmalgamatedHarmonics/ui/ScaleQuantizer_src.svg
File diff suppressed because it is too large
View File


+ 3
- 0
plugins/community/repos/Geodesics/.gitignore View File

@@ -0,0 +1,3 @@
/build
/dist
plugin.*

BIN
plugins/community/repos/Geodesics/GeodesicsUserManual061.pdf View File


BIN
plugins/community/repos/Geodesics/GeodesicsWhatsNew061.pdf View File


+ 85
- 0
plugins/community/repos/Geodesics/LICENSE.txt View File

@@ -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.

+ 27
- 0
plugins/community/repos/Geodesics/Makefile View File

@@ -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

+ 59
- 0
plugins/community/repos/Geodesics/README.md View File

@@ -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)

![Geodesics](res/img/Blanks.jpg)



## 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>

![Geodesics](res/img/BlackHoles.jpg)

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>

![Geodesics](res/img/Pulsars.jpg)

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>

![Geodesics](res/img/Branes.jpg)

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>

![Geodesics](res/img/Ions.jpg)

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.

+ 9
- 0
plugins/community/repos/Geodesics/make.objects View File

@@ -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

+ 7
- 0
plugins/community/repos/Geodesics/makefile.msvc View File

@@ -0,0 +1,7 @@
SLUG=Geodesics

include ../../../build_plugin_pre.mk

include make.objects

include ../../../build_plugin_post.mk

+ 7
- 0
plugins/community/repos/Geodesics/res/comp/C-01.svg View File

@@ -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>

+ 4
- 0
plugins/community/repos/Geodesics/res/comp/Otrsp-01.svg View File

@@ -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>

BIN
plugins/community/repos/Geodesics/res/img/BlackHoles.jpg View File

Before After
Width: 198  |  Height: 456  |  Size: 48KB

BIN
plugins/community/repos/Geodesics/res/img/Blanks.jpg View File

Before After
Width: 288  |  Height: 456  |  Size: 35KB

BIN
plugins/community/repos/Geodesics/res/img/Branes.jpg View File

Before After
Width: 198  |  Height: 456  |  Size: 37KB

BIN
plugins/community/repos/Geodesics/res/img/Ions.jpg View File

Before After
Width: 342  |  Height: 456  |  Size: 69KB

BIN
plugins/community/repos/Geodesics/res/img/Pulsars.jpg View File

Before After
Width: 198  |  Height: 456  |  Size: 40KB

+ 382
- 0
plugins/community/repos/Geodesics/res/light/BlackHolesBG-01.svg
File diff suppressed because it is too large
View File


+ 115
- 0
plugins/community/repos/Geodesics/res/light/BlankInfo-01.svg
File diff suppressed because it is too large
View File


+ 325
- 0
plugins/community/repos/Geodesics/res/light/BlankLogoBG-01.svg
File diff suppressed because it is too large
View File


+ 271
- 0
plugins/community/repos/Geodesics/res/light/BranesBG-01.svg
File diff suppressed because it is too large
View File


+ 522
- 0
plugins/community/repos/Geodesics/res/light/IonsBG-01.svg
File diff suppressed because it is too large
View File


+ 217
- 0
plugins/community/repos/Geodesics/res/light/PulsarsBG-01.svg
File diff suppressed because it is too large
View File


+ 8
- 0
plugins/community/repos/Geodesics/res/light/comp/Jack.svg View File

@@ -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>

+ 8
- 0
plugins/community/repos/Geodesics/res/light/comp/Knob.svg View File

@@ -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>

+ 6
- 0
plugins/community/repos/Geodesics/res/light/comp/PushButton1_0.svg View File

@@ -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>

+ 6
- 0
plugins/community/repos/Geodesics/res/light/comp/PushButton1_1.svg View File

@@ -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>

+ 412
- 0
plugins/community/repos/Geodesics/src/BlackHoles.cpp View File

@@ -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

*/

+ 109
- 0
plugins/community/repos/Geodesics/src/BlankInfo.cpp View File

@@ -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;
}

+ 178
- 0
plugins/community/repos/Geodesics/src/BlankLogo.cpp View File

@@ -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;
}

+ 427
- 0
plugins/community/repos/Geodesics/src/Branes.cpp View File

@@ -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

*/

+ 210
- 0
plugins/community/repos/Geodesics/src/GeoWidgets.cpp View File

@@ -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

+ 117
- 0
plugins/community/repos/Geodesics/src/GeoWidgets.hpp View File

@@ -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

+ 33
- 0
plugins/community/repos/Geodesics/src/Geodesics.cpp View File

@@ -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);
}

+ 162
- 0
plugins/community/repos/Geodesics/src/Geodesics.hpp View File

@@ -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

+ 787
- 0
plugins/community/repos/Geodesics/src/Ions.cpp View File

@@ -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

*/

+ 569
- 0
plugins/community/repos/Geodesics/src/Pulsars.cpp View File

@@ -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

*/

+ 2
- 0
plugins/community/repos/LindenbergResearch/make.objects View File

@@ -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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save