diff --git a/plugins/CVfunk/.github/workflows/Build_plugin.yml b/plugins/CVfunk/.github/workflows/Build_plugin.yml
new file mode 100644
index 0000000..d26be7d
--- /dev/null
+++ b/plugins/CVfunk/.github/workflows/Build_plugin.yml
@@ -0,0 +1,169 @@
+name: Build VCV Rack Plugin
+permissions:
+ contents: write
+
+on:
+ push:
+ paths-ignore:
+ - 'README*'
+ - 'doc/**'
+ - 'design/**'
+ - 'dev/**'
+ pull_request:
+
+env:
+ rack-sdk-version: latest
+ rack-plugin-toolchain-dir: /home/build/rack-plugin-toolchain
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+
+ modify-plugin-version:
+ name: Modify plugin version
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/cache@v4
+ id: plugin-version-cache
+ with:
+ path: plugin.json
+ key: ${{ github.sha }}-${{ github.run_id }}
+ - run: |
+ gitrev=`git rev-parse --short HEAD`
+ pluginversion=`jq -r '.version' plugin.json`
+ echo "Set plugin version from $pluginversion to $pluginversion-$gitrev"
+ cat <<< `jq --arg VERSION "$pluginversion-$gitrev" '.version=$VERSION' plugin.json` > plugin.json
+ # only modify plugin version if no tag was created
+ if: "! startsWith(github.ref, 'refs/tags/v')"
+
+ build:
+ name: ${{ matrix.platform }}
+ needs: modify-plugin-version
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/qno/rack-plugin-toolchain-win-linux
+ options: --user root
+ strategy:
+ matrix:
+ platform: [win-x64, lin-x64]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - uses: actions/cache@v4
+ id: plugin-version-cache
+ with:
+ path: plugin.json
+ key: ${{ github.sha }}-${{ github.run_id }}
+ - name: Build plugin
+ run: |
+ export PLUGIN_DIR=$GITHUB_WORKSPACE
+ pushd ${{ env.rack-plugin-toolchain-dir }}
+ make plugin-build-${{ matrix.platform }}
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ path: ${{ env.rack-plugin-toolchain-dir }}/plugin-build
+ name: ${{ matrix.platform }}
+ overwrite: true
+
+ build-mac:
+ name: mac
+ needs: modify-plugin-version
+ runs-on: macos-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: [x64, arm64]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - uses: actions/cache@v4
+ id: plugin-version-cache
+ with:
+ path: plugin.json
+ key: ${{ github.sha }}-${{ github.run_id }}
+ - name: Get Rack-SDK
+ run: |
+ pushd $HOME
+ wget -O Rack-SDK.zip https://vcvrack.com/downloads/Rack-SDK-${{ env.rack-sdk-version }}-mac-${{ matrix.platform }}.zip
+ unzip Rack-SDK.zip
+ - name: Build plugin
+ run: |
+ CROSS_COMPILE_TARGET_x64=x86_64-apple-darwin
+ CROSS_COMPILE_TARGET_arm64=arm64-apple-darwin
+ export RACK_DIR=$HOME/Rack-SDK
+ export CROSS_COMPILE=$CROSS_COMPILE_TARGET_${{ matrix.platform }}
+ make dep
+ make dist
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ path: dist/*.vcvplugin
+ name: mac-${{ matrix.platform }}
+ overwrite: true
+
+ publish:
+ name: Publish plugin
+ # only create a release if a tag was created that is called e.g. v1.2.3
+ # see also https://vcvrack.com/manual/Manifest#version
+ if: startsWith(github.ref, 'refs/tags/v')
+ runs-on: ubuntu-latest
+ needs: [build, build-mac]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: FranzDiebold/github-env-vars-action@v2
+ - name: Check if plugin version matches tag
+ run: |
+ pluginversion=`jq -r '.version' plugin.json`
+ if [ "v$pluginversion" != "${{ env.CI_REF_NAME }}" ]; then
+ echo "Plugin version from plugin.json 'v$pluginversion' doesn't match with tag version '${{ env.CI_REF_NAME }}'"
+ exit 1
+ fi
+ - name: Create Release
+ uses: softprops/action-gh-release@v1
+ with:
+ tag_name: ${{ github.ref }}
+ name: Release ${{ env.CI_REF_NAME }}
+ body: |
+ ${{ env.CI_REPOSITORY_NAME }} VCV Rack Plugin ${{ env.CI_REF_NAME }}
+ draft: false
+ prerelease: false
+ - uses: actions/download-artifact@v4
+ with:
+ path: _artifacts
+ - name: Upload release assets
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: _artifacts/**/*.vcvplugin
+ tag: ${{ github.ref }}
+ file_glob: true
+
+ publish-nightly:
+ name: Publish nightly
+ if: ${{ github.ref == 'refs/heads/main' }}
+ runs-on: ubuntu-latest
+ needs: [build, build-mac]
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ path: _artifacts
+ - name: Delete old release assets
+ uses: mknejp/delete-release-assets@v1
+ with:
+ token: ${{ github.token }}
+ tag: Nightly # This may also be of the form 'refs/tags/staging'
+ fail-if-no-assets: false
+ assets: '*'
+ - name: Upload release assets
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: _artifacts/**/*.vcvplugin
+ tag: Nightly
+ file_glob: true
diff --git a/plugins/CVfunk/.gitignore b/plugins/CVfunk/.gitignore
new file mode 100644
index 0000000..978f813
--- /dev/null
+++ b/plugins/CVfunk/.gitignore
@@ -0,0 +1,76 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+#VS stuff
+*.ilk
+*.log
+*.pdb
+*.tlog
+*.ipch
+
+**/.vs/
+**/Debug/
+**/Release/
+**/dist/
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
diff --git a/plugins/CVfunk/LICENSE b/plugins/CVfunk/LICENSE
new file mode 100644
index 0000000..a105295
--- /dev/null
+++ b/plugins/CVfunk/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 CV funk
+
+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.
diff --git a/plugins/CVfunk/Makefile b/plugins/CVfunk/Makefile
new file mode 100644
index 0000000..07b5733
--- /dev/null
+++ b/plugins/CVfunk/Makefile
@@ -0,0 +1,23 @@
+# If RACK_DIR is not defined when calling the Makefile, default to two directories above
+RACK_DIR ?= ../..
+
+# 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, but they should be added to this plugin's build system.
+LDFLAGS +=
+
+# Add .cpp files to the build
+SOURCES += $(wildcard src/*.cpp)
+
+# Add files to the ZIP package when running `make dist`
+# The compiled plugin and "plugin.json" are automatically added.
+DISTRIBUTABLES += res
+DISTRIBUTABLES += $(wildcard LICENSE*)
+DISTRIBUTABLES += $(wildcard presets)
+
+# Include the Rack plugin Makefile framework
+include $(RACK_DIR)/plugin.mk
diff --git a/plugins/CVfunk/README.md b/plugins/CVfunk/README.md
new file mode 100644
index 0000000..ce0df68
--- /dev/null
+++ b/plugins/CVfunk/README.md
@@ -0,0 +1,28 @@
+# CV funk Module Collection for VCV Rack
+
+Explore the vast possibilities of modular synthesis with the CV Funk Module Collection, designed for VCV Rack. This suite of modules simplifies generating a complex spectrum of modulation from simple inputs. The modules are each carefully engineered to inject dynamic modulation, intricate sequencing, and immersive soundscapes into your musical creations. From the precise steps of the Penta Sequencer to the wide range of control of the Envelope Array, each module is designed to explore patch-programmable modular synthesis, inviting you to delve into the complexities of sound design with intuitive macro controls.
+
+
+
+
+## Modules Overview
+
+### Steps
+A fusion of comparison logic and step sequencing, this module allows for dynamic pattern creation, influenced by external voltage levels. It provides hands-on control over bias, range, and step characteristics, complemented by clear visual feedback on the current state and progression.
+
+### Envelope Array
+An envelope generation powerhouse, the Envelope Array offers unparalleled control over the shape and dynamics of your sound. With six related envelope stages featuring adjustable slant and curve parameters, it opens up a realm of expressive modulation possibilities. Creates single-shot or cycling envelopes ranging from ms to minutes.
+
+### Penta Sequencer
+A 5-step sequencer that redefines rhythmic and melodic structuring, offering Circular and Star modes for diverse sequencing, complete with directional control and adjustable slew for smooth transitions, all visually guided by intuitive step indicators.
+
+### Impulse Controller
+Simulate the mesmerizing movement of waves through a network of nodes with the Impulse Controller module. This module not only brings a visual spectacle to your rack but also offers a unique approach to spatial sound design and dynamic modulation, with 24 outputs representing the ebb and flow of energy through the network.
+
+### Signals
+Observe and compare six signal inputs. Range from ms to sec. Includes a trigger reset toggle.
+
+### Ranges
+Split two signals into a range of 0-12 fractional mixes. Easily generate musical intervals.
+
+See the [User's Manual](/img/CV_funk_Manual.pdf) for more information.
diff --git a/plugins/CVfunk/img/CV_funk_Manual.pdf b/plugins/CVfunk/img/CV_funk_Manual.pdf
new file mode 100644
index 0000000..fdb516d
Binary files /dev/null and b/plugins/CVfunk/img/CV_funk_Manual.pdf differ
diff --git a/plugins/CVfunk/img/EnvelopeArrayDark.png b/plugins/CVfunk/img/EnvelopeArrayDark.png
new file mode 100644
index 0000000..04266f1
Binary files /dev/null and b/plugins/CVfunk/img/EnvelopeArrayDark.png differ
diff --git a/plugins/CVfunk/img/EnvelopeArrayLight.png b/plugins/CVfunk/img/EnvelopeArrayLight.png
new file mode 100644
index 0000000..1d78372
Binary files /dev/null and b/plugins/CVfunk/img/EnvelopeArrayLight.png differ
diff --git a/plugins/CVfunk/img/Envelopes_ref.png b/plugins/CVfunk/img/Envelopes_ref.png
new file mode 100644
index 0000000..f940c67
Binary files /dev/null and b/plugins/CVfunk/img/Envelopes_ref.png differ
diff --git a/plugins/CVfunk/img/ImpulseControlDark.png b/plugins/CVfunk/img/ImpulseControlDark.png
new file mode 100644
index 0000000..8e57f91
Binary files /dev/null and b/plugins/CVfunk/img/ImpulseControlDark.png differ
diff --git a/plugins/CVfunk/img/ImpulseControlLight.png b/plugins/CVfunk/img/ImpulseControlLight.png
new file mode 100644
index 0000000..48e1c80
Binary files /dev/null and b/plugins/CVfunk/img/ImpulseControlLight.png differ
diff --git a/plugins/CVfunk/img/ImpulseControllerDark.png b/plugins/CVfunk/img/ImpulseControllerDark.png
new file mode 100644
index 0000000..8e57f91
Binary files /dev/null and b/plugins/CVfunk/img/ImpulseControllerDark.png differ
diff --git a/plugins/CVfunk/img/ImpulseControllerLight.png b/plugins/CVfunk/img/ImpulseControllerLight.png
new file mode 100644
index 0000000..48e1c80
Binary files /dev/null and b/plugins/CVfunk/img/ImpulseControllerLight.png differ
diff --git a/plugins/CVfunk/img/PentaSequencer.png b/plugins/CVfunk/img/PentaSequencer.png
new file mode 100644
index 0000000..c867258
Binary files /dev/null and b/plugins/CVfunk/img/PentaSequencer.png differ
diff --git a/plugins/CVfunk/img/PentaSequencerDark.png b/plugins/CVfunk/img/PentaSequencerDark.png
new file mode 100644
index 0000000..3d5ab1b
Binary files /dev/null and b/plugins/CVfunk/img/PentaSequencerDark.png differ
diff --git a/plugins/CVfunk/img/PentaSequencerLight.png b/plugins/CVfunk/img/PentaSequencerLight.png
new file mode 100644
index 0000000..a3f7552
Binary files /dev/null and b/plugins/CVfunk/img/PentaSequencerLight.png differ
diff --git a/plugins/CVfunk/img/RangesDark.png b/plugins/CVfunk/img/RangesDark.png
new file mode 100644
index 0000000..8e6c7dc
Binary files /dev/null and b/plugins/CVfunk/img/RangesDark.png differ
diff --git a/plugins/CVfunk/img/RangesLight.png b/plugins/CVfunk/img/RangesLight.png
new file mode 100644
index 0000000..8263fd8
Binary files /dev/null and b/plugins/CVfunk/img/RangesLight.png differ
diff --git a/plugins/CVfunk/img/SignalsDark.png b/plugins/CVfunk/img/SignalsDark.png
new file mode 100644
index 0000000..722752f
Binary files /dev/null and b/plugins/CVfunk/img/SignalsDark.png differ
diff --git a/plugins/CVfunk/img/SignalsLight.png b/plugins/CVfunk/img/SignalsLight.png
new file mode 100644
index 0000000..265c2d9
Binary files /dev/null and b/plugins/CVfunk/img/SignalsLight.png differ
diff --git a/plugins/CVfunk/img/StepsDark.png b/plugins/CVfunk/img/StepsDark.png
new file mode 100644
index 0000000..91c829a
Binary files /dev/null and b/plugins/CVfunk/img/StepsDark.png differ
diff --git a/plugins/CVfunk/img/StepsLight.png b/plugins/CVfunk/img/StepsLight.png
new file mode 100644
index 0000000..c93837f
Binary files /dev/null and b/plugins/CVfunk/img/StepsLight.png differ
diff --git a/plugins/CVfunk/img/darkmodules.png b/plugins/CVfunk/img/darkmodules.png
new file mode 100644
index 0000000..97dde27
Binary files /dev/null and b/plugins/CVfunk/img/darkmodules.png differ
diff --git a/plugins/CVfunk/img/lightmodules.png b/plugins/CVfunk/img/lightmodules.png
new file mode 100644
index 0000000..3ade22b
Binary files /dev/null and b/plugins/CVfunk/img/lightmodules.png differ
diff --git a/plugins/CVfunk/plugin.json b/plugins/CVfunk/plugin.json
new file mode 100644
index 0000000..381673f
--- /dev/null
+++ b/plugins/CVfunk/plugin.json
@@ -0,0 +1,72 @@
+{
+ "slug": "CVfunk",
+ "name": "CV funk Modules",
+ "version": "2.0.1",
+ "license": "MIT",
+ "brand": "CV funk",
+ "author": "Cody Geary",
+ "authorEmail": "codyge@gmail.com",
+ "authorUrl": "",
+ "pluginUrl": "https://github.com/codygeary/CVfunk-Modules",
+ "manualUrl": "https://github.com/codygeary/CVfunk-Modules/blob/main/img/CV_funk_Manual.pdf",
+ "sourceUrl": "https://github.com/codygeary/CVfunk-Modules",
+ "donateUrl": "https://www.paypal.com/donate/?hosted_button_id=P69BU6HMBTYYY",
+ "changelogUrl": "",
+ "modules": [
+ {
+ "slug": "Steps",
+ "name": "Steps",
+ "description": "Fusion of a comparator and a step generator",
+ "tags": [
+ "Function generator",
+ "Sample and hold",
+ "Utility"
+ ]
+ },
+ {
+ "slug": "EnvelopeArray",
+ "name": "Envelope Array",
+ "description": "Six related envelopes and gates",
+ "tags": [
+ "Envelope generator",
+ "Polyphonic",
+ "Function generator"
+ ]
+ },
+ {
+ "slug": "PentaSequencer",
+ "name": "Penta Sequencer",
+ "description": "A five step sequencer with five simultaneous outputs and tempo-synced slew",
+ "tags": [
+ "Sequencer"
+ ]
+ },
+ {
+ "slug": "ImpulseController",
+ "name": "Impulse Controller",
+ "description": "Decay envelopes propagate across a network of 24 nodes",
+ "tags": [
+ "Envelope generator",
+ "Function generator",
+ "Visual"
+ ]
+ },
+ {
+ "slug": "Signals",
+ "name": "Signals",
+ "description": "A small 6-channel oscilloscope for visualizing rack signals.",
+ "tags": [
+ "Visual"
+ ]
+ },
+ {
+ "slug": "Ranges",
+ "name": "Ranges",
+ "description": "Convert two input voltages into upto 12 equal intervals.",
+ "tags": [
+ "Utility"
+ ]
+ }
+
+ ]
+}
diff --git a/plugins/CVfunk/res/EnvelopeArray-dark.svg b/plugins/CVfunk/res/EnvelopeArray-dark.svg
new file mode 100644
index 0000000..b153b37
--- /dev/null
+++ b/plugins/CVfunk/res/EnvelopeArray-dark.svg
@@ -0,0 +1,1048 @@
+
+
diff --git a/plugins/CVfunk/res/EnvelopeArray.svg b/plugins/CVfunk/res/EnvelopeArray.svg
new file mode 100644
index 0000000..ef6f389
--- /dev/null
+++ b/plugins/CVfunk/res/EnvelopeArray.svg
@@ -0,0 +1,986 @@
+
+
diff --git a/plugins/CVfunk/res/ImpulseController-dark.svg b/plugins/CVfunk/res/ImpulseController-dark.svg
new file mode 100644
index 0000000..85677b3
--- /dev/null
+++ b/plugins/CVfunk/res/ImpulseController-dark.svg
@@ -0,0 +1,922 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/ImpulseController.svg b/plugins/CVfunk/res/ImpulseController.svg
new file mode 100644
index 0000000..154f936
--- /dev/null
+++ b/plugins/CVfunk/res/ImpulseController.svg
@@ -0,0 +1,936 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/PentaSequencer-dark.svg b/plugins/CVfunk/res/PentaSequencer-dark.svg
new file mode 100644
index 0000000..63f0f79
--- /dev/null
+++ b/plugins/CVfunk/res/PentaSequencer-dark.svg
@@ -0,0 +1,777 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/PentaSequencer.svg b/plugins/CVfunk/res/PentaSequencer.svg
new file mode 100644
index 0000000..2c4703a
--- /dev/null
+++ b/plugins/CVfunk/res/PentaSequencer.svg
@@ -0,0 +1,778 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/Ranges-dark.svg b/plugins/CVfunk/res/Ranges-dark.svg
new file mode 100644
index 0000000..e5f2bb2
--- /dev/null
+++ b/plugins/CVfunk/res/Ranges-dark.svg
@@ -0,0 +1,136 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/Ranges.svg b/plugins/CVfunk/res/Ranges.svg
new file mode 100644
index 0000000..df2c591
--- /dev/null
+++ b/plugins/CVfunk/res/Ranges.svg
@@ -0,0 +1,136 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/Signals-dark.svg b/plugins/CVfunk/res/Signals-dark.svg
new file mode 100644
index 0000000..a35bfc8
--- /dev/null
+++ b/plugins/CVfunk/res/Signals-dark.svg
@@ -0,0 +1,140 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/Signals.svg b/plugins/CVfunk/res/Signals.svg
new file mode 100644
index 0000000..9dde1d6
--- /dev/null
+++ b/plugins/CVfunk/res/Signals.svg
@@ -0,0 +1,158 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/Steps-dark.svg b/plugins/CVfunk/res/Steps-dark.svg
new file mode 100644
index 0000000..4f07175
--- /dev/null
+++ b/plugins/CVfunk/res/Steps-dark.svg
@@ -0,0 +1,733 @@
+
+
+
+
diff --git a/plugins/CVfunk/res/Steps.svg b/plugins/CVfunk/res/Steps.svg
new file mode 100644
index 0000000..ed406c5
--- /dev/null
+++ b/plugins/CVfunk/res/Steps.svg
@@ -0,0 +1,919 @@
+
+
+
+
diff --git a/plugins/CVfunk/src/EnvelopeArray.cpp b/plugins/CVfunk/src/EnvelopeArray.cpp
new file mode 100644
index 0000000..478b24c
--- /dev/null
+++ b/plugins/CVfunk/src/EnvelopeArray.cpp
@@ -0,0 +1,550 @@
+////////////////////////////////////////////////////////////
+//
+// Envelope Array
+//
+// written by Cody Geary
+// Copyright 2024, MIT License
+//
+// Six related envelopes with end of function gates
+//
+////////////////////////////////////////////////////////////
+
+
+#include "plugin.hpp"
+using simd::float_4;
+
+//Envelope shape adapted from Befaco shapeDelta function
+static float_4 Envelope(float_4 delta, float_4 tau, float shape) {
+ float_4 lin = simd::sgn(delta) * 10.f / tau;
+ if (shape > 0.f) {
+ float_4 log = simd::sgn(delta) * 40.f / tau / (simd::fabs(delta) + 1.f);
+ return simd::crossfade(lin, log, shape * 1.49f); //1.49 pushes this var to extreme
+ }
+ else {
+ float_4 exp = M_E * delta / tau;
+ return simd::crossfade(lin, exp, -shape * 0.99f); //0.99 is the max we can go also.
+ }
+}
+
+struct EnvelopeArray : Module {
+ enum ParamId {
+ SLANT_PARAM,
+ CURVE_PARAM,
+ TIME1_PARAM,
+ TIME6_PARAM,
+ SLANT_ATTEN_PARAM,
+ CURVE_ATTEN_PARAM,
+ TIME1_ATTEN_PARAM,
+ TIME6_ATTEN_PARAM,
+ TIME1_RANGE_BUTTON,
+ TIME6_RANGE_BUTTON,
+
+ /////////////////////
+ //SECRET_PARAM, //hidden param for tuning variables
+ /////////////////////
+
+ PARAMS_LEN
+ };
+ enum InputId {
+ SLANT_INPUT,
+ CURVE_INPUT,
+ TIME1_INPUT,
+ TIME6_INPUT,
+ _1_INPUT,
+ _2_INPUT,
+ _3_INPUT,
+ _4_INPUT,
+ _5_INPUT,
+ _6_INPUT,
+ INPUTS_LEN
+ };
+ enum OutputId {
+ _1_OUTPUT,
+ _2_OUTPUT,
+ _3_OUTPUT,
+ _4_OUTPUT,
+ _5_OUTPUT,
+ _6_OUTPUT,
+ EOF1_OUTPUT,
+ EOF2_OUTPUT,
+ EOF3_OUTPUT,
+ EOF4_OUTPUT,
+ EOF5_OUTPUT,
+ EOF6_OUTPUT,
+ OUTPUTS_LEN
+ };
+ enum LightId {
+ _1_LIGHT,
+ _2_LIGHT,
+ _3_LIGHT,
+ _4_LIGHT,
+ _5_LIGHT,
+ _6_LIGHT,
+ _7_LIGHT,
+ _8_LIGHT,
+ _9_LIGHT,
+ _10_LIGHT,
+ _11_LIGHT,
+ _12_LIGHT,
+ TIME1_LED1_LIGHT,
+ TIME1_LED2_LIGHT,
+ TIME1_LED3_LIGHT,
+ TIME6_LED1_LIGHT,
+ TIME6_LED2_LIGHT,
+ TIME6_LED3_LIGHT,
+ LIGHTS_LEN
+ };
+
+ enum SpeedRange {
+ HIGH,
+ MID,
+ LOW
+ };
+
+ SpeedRange time1Range;
+ SpeedRange time6Range;
+
+ // Define an array to store time variables
+ float time_x[6] = {0.0f}; // Initialize all elements to 0.0f
+ float_4 out[6][4] = {};
+ float_4 gate[6][4] = {}; // use simd __m128 logic instead of bool
+
+ float_4 gate_no_output[6][4] = {{0.0f}}; // Initialize with all elements set to true
+
+ dsp::TSchmittTrigger trigger_4[6][4];
+
+ int processSkipCounter = 0;
+ int processSkipRate = 2; // Update the envelope every 2 process cycles to save CPU
+
+
+ // Serialization method to save module state
+ json_t* dataToJson() override {
+ json_t* rootJ = json_object();
+
+ // Save the state of time1Range and time6Range
+ json_object_set_new(rootJ, "time1Range", json_integer(time1Range));
+ json_object_set_new(rootJ, "time6Range", json_integer(time6Range));
+
+ return rootJ;
+ }
+
+ // Deserialization method to load module state
+ void dataFromJson(json_t* rootJ) override {
+ // Load the state of time1Range
+ json_t* time1RangeJ = json_object_get(rootJ, "time1Range");
+ if (time1RangeJ) time1Range = static_cast(json_integer_value(time1RangeJ));
+
+ // Load the state of time6Range
+ json_t* time6RangeJ = json_object_get(rootJ, "time6Range");
+ if (time6RangeJ) time6Range = static_cast(json_integer_value(time6RangeJ));
+ }
+
+
+ EnvelopeArray() {
+ config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
+ configParam(SLANT_PARAM, -1.f, 1.f, -.75f, "Slant");
+ configParam(CURVE_PARAM, -1.f, 1.f, -.75f, "Curve");
+ configParam(TIME1_PARAM, 0.0f, 1.0f, 0.4f, "First Width");
+ configParam(TIME6_PARAM, 0.0f, 1.0f, 0.75f, "Last Width");
+ configParam(SLANT_ATTEN_PARAM, -1.0f, 1.0f, 1.f, "");
+ configParam(CURVE_ATTEN_PARAM, -1.f, 1.f, 1.F, "");
+ configParam(TIME1_ATTEN_PARAM, -1.f, 1.f, 1.f, "");
+ configParam(TIME6_ATTEN_PARAM, -1.f, 1.f, 1.f, "");
+
+ /////////////////////
+ //configParam(SECRET_PARAM,-5.0, 10.0f, 4.7f, "Mapping to a test knob"); //only used for calibration
+ /////////////////////
+
+ configInput(SLANT_INPUT, "Slant IN");
+ configInput(CURVE_INPUT, "Curve IN");
+ configInput(TIME1_INPUT, "First Width IN");
+ configInput(TIME6_INPUT, "Last Width IN");
+ configInput(_1_INPUT, "IN 1");
+ configInput(_2_INPUT, "IN 2");
+ configInput(_3_INPUT, "IN 3");
+ configInput(_4_INPUT, "IN 4");
+ configInput(_5_INPUT, "IN 5");
+ configInput(_6_INPUT, "IN 6");
+ configOutput(_1_OUTPUT, "OUT 1");
+ configOutput(_2_OUTPUT, "OUT 2");
+ configOutput(_3_OUTPUT, "OUT 3");
+ configOutput(_4_OUTPUT, "OUT 4");
+ configOutput(_5_OUTPUT, "OUT 5");
+ configOutput(_6_OUTPUT, "OUT 6");
+ configOutput(EOF1_OUTPUT, "GATE 1");
+ configOutput(EOF2_OUTPUT, "GATE 2");
+ configOutput(EOF3_OUTPUT, "GATE 3");
+ configOutput(EOF4_OUTPUT, "GATE 4");
+ configOutput(EOF5_OUTPUT, "GATE 5");
+ configOutput(EOF6_OUTPUT, "GATE 6");
+
+
+ // Default initialization of time ranges to MID
+ time1Range = MID;
+ time6Range = MID;
+
+
+ }
+
+ void process(const ProcessArgs &args) override {
+
+ // Button logic for Time1
+ if (params[TIME1_RANGE_BUTTON].getValue() > 0) {
+ time1Range = static_cast((time1Range + 1) % 3);
+ params[TIME1_RANGE_BUTTON].setValue(0);
+ }
+
+ // Button logic for Time6
+ if (params[TIME6_RANGE_BUTTON].getValue() > 0) {
+ time6Range = static_cast((time6Range + 1) % 3);
+ params[TIME6_RANGE_BUTTON].setValue(0);
+ }
+
+ // Update LED lights for Time1
+ lights[TIME1_LED1_LIGHT].setBrightness(time1Range == HIGH ? 1.0f : 0.0f);
+ lights[TIME1_LED2_LIGHT].setBrightness(time1Range == MID ? 1.0f : 0.0f);
+ lights[TIME1_LED3_LIGHT].setBrightness(time1Range == LOW ? 1.0f : 0.0f);
+
+ // Update LED lights for Time6
+ lights[TIME6_LED1_LIGHT].setBrightness(time6Range == HIGH ? 1.0f : 0.0f);
+ lights[TIME6_LED2_LIGHT].setBrightness(time6Range == MID ? 1.0f : 0.0f);
+ lights[TIME6_LED3_LIGHT].setBrightness(time6Range == LOW ? 1.0f : 0.0f);
+
+ // Read inputs and parameters...
+ float slant = params[SLANT_PARAM].getValue();
+ float curve = params[CURVE_PARAM].getValue();
+
+ /////////////////////
+ //float secret = params[SECRET_PARAM].getValue(); //Get our calibration parameter here
+ /////////////////////
+
+ time_x[0] = params[TIME1_PARAM].getValue(); //Time interval for the first generator
+ time_x[5] = params[TIME6_PARAM].getValue(); //Time interval for the last generator
+
+ if (inputs[SLANT_INPUT].isConnected())
+ slant += inputs[SLANT_INPUT].getVoltage()*params[SLANT_ATTEN_PARAM].getValue()*0.2; //scaled so 10V envelope spans the whole range
+ if (inputs[CURVE_INPUT].isConnected())
+ curve += inputs[CURVE_INPUT].getVoltage()*params[CURVE_ATTEN_PARAM].getValue()*0.2;
+ if (inputs[TIME1_INPUT].isConnected())
+ time_x[0] += inputs[TIME1_INPUT].getVoltage()*params[TIME1_ATTEN_PARAM].getValue()*0.1;
+ if (inputs[TIME6_INPUT].isConnected())
+ time_x[5] += inputs[TIME6_INPUT].getVoltage()*params[TIME6_ATTEN_PARAM].getValue()*0.1;
+
+
+ //Clamp voltages after adding CV params
+ slant = clamp(slant, -1.0f, 1.0f);
+ curve = clamp(curve, -1.0f, 1.0f);
+ // time_x[0] = clamp(time_x[0], 0.0f,1.0f); //let the CV push the time to the max limits of range, regardless of range settings.
+ // time_x[5] = clamp(time_x[5], 0.0f,1.0f);
+
+
+ //Apply non-linear scaling to the slant knob to make it scale more naturally
+ slant = copysign(pow(fabs(slant), 2), slant); // Apply the power rule while preserving the sign
+
+
+ //Set time ranges based on the range selectors.
+ //Updated so that all channels use the same, smaller minTime setting
+ if (time1Range==HIGH){
+ time_x[0] *= 1.5f;
+ } else if (time1Range==MID){
+ time_x[0] = time_x[0]*1.5f + 1.4f;
+ } else if (time1Range==LOW){
+ time_x[0] = time_x[0]*1.5f + 2.8f;
+ }
+
+ if (time6Range==HIGH){
+ time_x[5] *= 1.5f;
+ } else if (time6Range==MID){
+ time_x[5] = time_x[5]*1.5f + 1.4f;
+ } else if (time6Range==LOW){
+ time_x[5] = time_x[5]*1.5f + 2.8f;
+ }
+
+ // Clamp time_x[0] and time_x[5] to be positive only
+ time_x[0] = std::max(time_x[0], 0.0f);
+ time_x[5] = std::max(time_x[5], 0.0f);
+
+
+ //Adjust for non-linearity of the slant control
+ float f_slant;
+ if (abs(slant) <= 0.6f) {
+ f_slant = (5.0f / 3.0f) * abs(slant); // From 0 to 0.6
+ } else {
+ f_slant = -(5.0f / 2.0f) * (abs(slant) - 0.6f) + 1; // From 0.6 to 1
+ }
+ float slant_abs = abs(slant) + f_slant * 0.1f;
+
+ time_x[0] -= slant_abs / (2.4760985f * pow(time_x[0], -1.17f));
+ time_x[5] -= slant_abs / (2.4760985f * pow(time_x[5], -1.17f));
+
+
+ // Adjust for non-linearity of the curve control
+ float f_curve;
+ if (abs(curve) <= 0.75f) {
+ f_curve = (4.0f / 3.0f) * abs(curve); // From 0 to 0.75
+ } else {
+ f_curve = -4.0f * (abs(curve) - 0.75f) + 1; // From 0.75 to 1
+ }
+ float curve_abs = abs(curve) + f_curve * -0.66f;
+ float curve_abs2 = abs(curve) + f_curve * -0.33f;
+
+
+ //Scale the time_x inputs to compensate for the increase in cycle time for different curve values.
+ //For large curve values the slant compensation needs to be readjusted again
+ float curve_scalefactor = 4.6; //hand-calibrated
+ float curve_scalefactor2 = 2.85; //hand-calibrated
+ float slant_scalefactor1 = .45; //hand-calibrated
+ float slant_scalefactor2 = .4; //hand-calibrated
+ if (curve<0) { //this is split into two parts because the log side of curve can scale further
+ time_x[0] = time_x[0]-(curve_abs/curve_scalefactor)*(1 - (slant_abs*slant_scalefactor1) );
+ time_x[5] = time_x[5]-(curve_abs/curve_scalefactor)*(1 - (slant_abs*slant_scalefactor1) );
+ } else {
+ time_x[0] = time_x[0]-(curve_abs2/curve_scalefactor2)*(1 - (slant_abs*slant_scalefactor2) );
+ time_x[5] = time_x[5]-(curve_abs2/curve_scalefactor2)*(1 - (slant_abs*slant_scalefactor2) );
+ }
+
+
+ //clamp time to a max range where the decay is at least one sample value
+ //(otherwise it will hold forever if the decay is too slow)
+ time_x[0] = clamp(time_x[0], 0.0f,4.3f);
+ time_x[5] = clamp(time_x[5], 0.0f,4.3f);
+
+
+
+ //Adjust slant to be 0-1V range, for the Envelope function.
+ slant = (slant+1.0f)/2;
+
+ // Calculate the step size between each time_x value
+ float time_step = (time_x[5] - time_x[0]) / 2.236f;
+
+ // Calculate the intermediate values using square root function interpolation
+ if (time_x[5] >= time_x[0]) {
+ time_x[1] = time_x[0] + 0.92f * time_step;
+ time_x[2] = time_x[0] + 1.414f * time_step;
+ time_x[3] = time_x[0] + 1.732f * time_step;
+ time_x[4] = time_x[0] + 2.0f * time_step;
+ } else {
+ time_x[4] = time_x[5] - 0.92f * time_step;
+ time_x[3] = time_x[5] - 1.414f * time_step;
+ time_x[2] = time_x[5] - 1.732f * time_step;
+ time_x[1] = time_x[5] - 2.0f * time_step;
+ }
+
+
+
+ int channels_in[6] = {};
+ int channels_trig[6] = {};
+ int channels[6] = {}; // the larger of in or trig (per-part)
+
+ // determine number of channels:
+ for (int part = 0; part < 6; part++) {
+
+ channels_in[part] = 0.0f;
+ channels_trig[part] = inputs[_1_INPUT + part].getChannels();
+ channels[part] = std::max(channels_in[part], channels_trig[part]);
+ channels[part] = std::max(1, channels[part]);
+
+ outputs[_1_OUTPUT + part].setChannels(channels[part]);
+ outputs[EOF1_OUTPUT + part].setChannels(channels[part]);
+ }
+
+ // total number of active polyphony engines, accounting for all parts
+ int channels_max = channels[0]; // Initialize with the first value
+ for (int i = 1; i < 4; ++i) {
+ channels_max = std::max(channels_max, channels[i]);
+ }
+
+
+ //SKIP process computations ever other cycle to save CPU:
+ if (++processSkipCounter >= processSkipRate) {
+ processSkipCounter = 0; // Reset counter
+
+ // loop over six stage parts:
+ for (int part = 0; part < 6; part++) {
+
+ float_4 in[4] = {};
+ float_4 in_trig[6][4] = {};
+ float_4 riseCV[4] = {};
+ float_4 fallCV[4] = {};
+ float_4 cycle[4] = {};
+
+
+ float minTime = .0001f; //set a very small minTime
+
+ float_4 param_rise = time_x[part] * (slant) * 10.0f;
+
+ float_4 param_fall = time_x[part] * (1.0f - slant) * 10.0f;
+ float_4 param_trig = 0.0f;
+ float_4 param_cycle = 0.0f;
+
+ for (int c = 0; c < channels[part]; c += 4) {
+ riseCV[c / 4] = param_rise;
+ fallCV[c / 4] = param_fall;
+ cycle[c / 4] = param_cycle;
+ in_trig[part][c / 4] = param_trig;
+ }
+
+ // Read inputs:
+ if (inputs[_1_INPUT + part].isConnected()) {
+ for (int c = 0; c < channels[part]; c += 4)
+ in_trig[part][c / 4] += inputs[_1_INPUT + part].getPolyVoltageSimd(c);
+ } else {
+ // Look for a trigger input in previous parts
+ for (int prevPart = part - 1; prevPart >= 0; prevPart--) {
+ if (inputs[_1_INPUT + prevPart].isConnected()) {
+ // Found a trigger input in a previous part
+ // Use its voltage for the current part's trigger input
+ for (int c = 0; c < channels[prevPart]; c += 4)
+ in_trig[part][c / 4] += inputs[_1_INPUT + prevPart].getPolyVoltageSimd(c);
+ break; // Exit the loop since we found a trigger input
+ }
+ }
+ }
+
+ // start processing:
+ for (int c = 0; c < channels[part]; c += 4) {
+
+ // process SchmittTriggers
+ // Convert triggers to processed triggers
+ float_4 trig_mask = trigger_4[part][c / 4].process(in_trig[part][c / 4] / 2.0, 0.1, 2.0);
+ //store the gate, but only of the gate_no_output is low
+ gate[part][c / 4] = ifelse(trig_mask & (gate_no_output[part][c / 4] == 10.0f), float_4::mask(), gate[part][c / 4]);
+
+ in[c / 4] = ifelse(gate[part][c / 4], 10.0f, in[c / 4]);
+ float_4 delta = in[c / 4] - out[part][c / 4];
+
+ // rise / fall branching
+ float_4 delta_gt_0 = delta > 0.f;
+ float_4 delta_lt_0 = delta < 0.f;
+ float_4 delta_eq_0 = ~(delta_lt_0 | delta_gt_0);
+
+ float_4 rising = simd::ifelse(delta_gt_0, (in[c / 4] - out[part][c / 4]) > 1e-6f, float_4::zero());
+ float_4 falling = simd::ifelse(delta_lt_0, (in[c / 4] - out[part][c / 4]) < -1e-6f, float_4::zero());
+ float_4 end_of_cycle = simd::andnot(falling, delta_lt_0);
+
+ float_4 rateCV = ifelse(delta_gt_0, riseCV[c / 4], 0.f);
+ rateCV = ifelse(delta_lt_0, fallCV[c / 4], rateCV);
+ rateCV = clamp(rateCV, 0.f, 1e9f);
+
+ float_4 rate = minTime * simd::pow(2.0f, rateCV);
+
+ //Compute the change in output value
+ out[part][c / 4] += Envelope(delta, rate, curve) * args.sampleTime;
+
+ // Clamp the output to ensure it stays between 0 and 10.0V
+ out[part][c / 4] = simd::clamp(out[part][c / 4], simd::float_4(0.0f), simd::float_4(10.0f));
+
+ gate[part][c / 4] = ifelse(simd::andnot(rising, delta_gt_0), 0.f, gate[part][c / 4]);
+ gate[part][c / 4] = ifelse(end_of_cycle & (cycle[c / 4] >= 4.0f), float_4::mask(), gate[part][c / 4]);
+ gate[part][c / 4] = ifelse(delta_eq_0, 0.f, gate[part][c / 4]);
+
+ out[part][c / 4] = ifelse(rising | falling, out[part][c / 4], in[c / 4]);
+
+ // Determine the new gate output based on the voltage of out[part][c / 4]
+ gate_no_output[part][c / 4] = ifelse(out[part][c / 4] == 0.0f, 10.0f, 0.0f);
+
+ // Set the voltage for the outputs
+ outputs[_1_OUTPUT + part].setVoltageSimd(out[part][c / 4], c);
+ outputs[_1_OUTPUT + part + 6].setVoltageSimd(gate_no_output[part][c / 4], c);
+
+ } // for(int c, ...)
+
+ if (channels[part] == 1) {
+ lights[_1_LIGHT + part ].setSmoothBrightness(out[part][0].s[0] / 10.0, args.sampleTime);
+ lights[_1_LIGHT + 6 + part ].setSmoothBrightness(gate_no_output[part][0].s[0] / 10.0, args.sampleTime);
+ }
+
+ } // for (int part, ... )
+ }
+ }//void
+};//module
+
+struct EnvelopeArrayWidget : ModuleWidget {
+ EnvelopeArrayWidget(EnvelopeArray* module) {
+ setModule(module);
+
+ setPanel(createPanel(
+ asset::plugin(pluginInstance, "res/EnvelopeArray.svg"),
+ asset::plugin(pluginInstance, "res/EnvelopeArray-dark.svg")
+ ));
+
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+ addParam(createParamCentered(mm2px(Vec(29.337, 24.514)), module, EnvelopeArray::SLANT_PARAM));
+ addParam(createParamCentered(mm2px(Vec(47.525, 24.514)), module, EnvelopeArray::CURVE_PARAM));
+ addParam(createParamCentered(mm2px(Vec(11.228, 28.738)), module, EnvelopeArray::TIME1_PARAM));
+ addParam(createParamCentered(mm2px(Vec(65.323, 28.738)), module, EnvelopeArray::TIME6_PARAM));
+ addParam(createParamCentered(mm2px(Vec(29.337, 41.795)), module, EnvelopeArray::SLANT_ATTEN_PARAM));
+ addParam(createParamCentered(mm2px(Vec(47.525, 41.795)), module, EnvelopeArray::CURVE_ATTEN_PARAM));
+
+
+ /////////////////////
+ //addParam(createParamCentered(mm2px(Vec(38.277, 45)), module, EnvelopeArray::SECRET_PARAM));
+ /////////////////////
+
+
+ addParam(createParamCentered(mm2px(Vec(11.228, 45.315)), module, EnvelopeArray::TIME1_ATTEN_PARAM));
+ addParam(createParamCentered(mm2px(Vec(65.323, 45.315)), module, EnvelopeArray::TIME6_ATTEN_PARAM));
+
+ addInput(createInputCentered(mm2px(Vec(29.337, 55.194)), module, EnvelopeArray::SLANT_INPUT));
+ addInput(createInputCentered(mm2px(Vec(47.525, 55.194)), module, EnvelopeArray::CURVE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(11.228, 58.715)), module, EnvelopeArray::TIME1_INPUT));
+ addInput(createInputCentered(mm2px(Vec(65.323, 58.715)), module, EnvelopeArray::TIME6_INPUT));
+ addInput(createInputCentered(mm2px(Vec(7.1, 78.815)), module, EnvelopeArray::_1_INPUT));
+ addInput(createInputCentered(mm2px(Vec(19.459, 78.815)), module, EnvelopeArray::_2_INPUT));
+ addInput(createInputCentered(mm2px(Vec(31.818, 78.815)), module, EnvelopeArray::_3_INPUT));
+ addInput(createInputCentered(mm2px(Vec(44.178, 78.815)), module, EnvelopeArray::_4_INPUT));
+ addInput(createInputCentered(mm2px(Vec(56.537, 78.815)), module, EnvelopeArray::_5_INPUT));
+ addInput(createInputCentered(mm2px(Vec(68.896, 78.815)), module, EnvelopeArray::_6_INPUT));
+
+ // For Time1 Group
+ float groupStartXTime1 = 11.228 - 11.5; // Starting x-coordinate for the Time1 group
+ addParam(createParamCentered(mm2px(Vec(groupStartXTime1 + 6.5, 15)), module, EnvelopeArray::TIME1_RANGE_BUTTON)); // Button is 5 mm wide, so +2.5 mm to center it
+
+ // LEDs for Time1, positioned right of the button with 1 mm gaps
+ addChild(createLightCentered>(mm2px(Vec(groupStartXTime1 + 12, 15)), module, EnvelopeArray::TIME1_LED1_LIGHT)); // First LED
+ addChild(createLightCentered>(mm2px(Vec(groupStartXTime1 + 15, 15)), module, EnvelopeArray::TIME1_LED2_LIGHT)); // Second LED
+ addChild(createLightCentered>(mm2px(Vec(groupStartXTime1 + 18, 15)), module, EnvelopeArray::TIME1_LED3_LIGHT)); // Third LED
+
+ // For Time6 Group
+ float groupStartXTime6 = 65.323 - 11.5; // Starting x-coordinate for the Time6 group
+ addParam(createParamCentered(mm2px(Vec(groupStartXTime6 + 6.5, 15)), module, EnvelopeArray::TIME6_RANGE_BUTTON)); // Button centered
+
+ // LEDs for Time6, positioned right of the button with 1 mm gaps
+ addChild(createLightCentered>(mm2px(Vec(groupStartXTime6 + 12, 15)), module, EnvelopeArray::TIME6_LED1_LIGHT)); // First LED
+ addChild(createLightCentered>(mm2px(Vec(groupStartXTime6 + 15, 15)), module, EnvelopeArray::TIME6_LED2_LIGHT)); // Second LED
+ addChild(createLightCentered>(mm2px(Vec(groupStartXTime6 + 18, 15)), module, EnvelopeArray::TIME6_LED3_LIGHT)); // Third LED
+
+
+ addOutput(createOutputCentered(mm2px(Vec(7.1, 93.125)), module, EnvelopeArray::_1_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(19.459, 93.125)), module, EnvelopeArray::_2_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(31.818, 93.125)), module, EnvelopeArray::_3_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(44.178, 93.125)), module, EnvelopeArray::_4_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(56.537, 93.125)), module, EnvelopeArray::_5_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(68.896, 93.125)), module, EnvelopeArray::_6_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(7.1, 112.33)), module, EnvelopeArray::EOF1_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(19.459, 112.33)), module, EnvelopeArray::EOF2_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(31.818, 112.33)), module, EnvelopeArray::EOF3_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(44.178, 112.33)), module, EnvelopeArray::EOF4_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(56.537, 112.33)), module, EnvelopeArray::EOF5_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(68.896, 112.33)), module, EnvelopeArray::EOF6_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(7.1, 86.153)), module, EnvelopeArray::_1_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(19.518, 86.153)), module, EnvelopeArray::_2_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(31.819, 86.153)), module, EnvelopeArray::_3_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(44.119, 86.153)), module, EnvelopeArray::_4_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(56.42, 86.153)), module, EnvelopeArray::_5_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(68.896, 86.153)), module, EnvelopeArray::_6_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(7.1, 105.867)), module, EnvelopeArray::_7_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(19.518, 105.867)), module, EnvelopeArray::_8_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(31.819, 105.867)), module, EnvelopeArray::_9_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(44.119, 105.867)), module, EnvelopeArray::_10_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(56.42, 105.867)), module, EnvelopeArray::_11_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(68.896, 105.937)), module, EnvelopeArray::_12_LIGHT));
+ }
+};
+
+Model* modelEnvelopeArray = createModel("EnvelopeArray");
\ No newline at end of file
diff --git a/plugins/CVfunk/src/ImpulseController.cpp b/plugins/CVfunk/src/ImpulseController.cpp
new file mode 100644
index 0000000..2bba691
--- /dev/null
+++ b/plugins/CVfunk/src/ImpulseController.cpp
@@ -0,0 +1,463 @@
+////////////////////////////////////////////////////////////
+//
+// Impulse Controller
+//
+// written by Cody Geary
+// Copyright 2024, MIT License
+//
+// Decay envelopes propagate across a network of 24 nodes
+//
+////////////////////////////////////////////////////////////
+
+#include "plugin.hpp"
+
+struct ImpulseController : Module {
+ enum ParamId {
+ LAG_PARAM,
+ DECAY_PARAM,
+ SPREAD_PARAM,
+ LAG_ATT_PARAM, // Attenuverter for LAG_PARAM
+ SPREAD_ATT_PARAM, // Attenuverter for DECAY_PARAM
+ DECAY_ATT_PARAM, // Attenuverter for SPREAD_PARAM
+ TRIGGER_BUTTON,
+ PARAMS_LEN
+ };
+ enum InputId {
+ _00_INPUT,
+ LAG_INPUT,
+ DECAY_INPUT,
+ SPREAD_INPUT,
+ INPUTS_LEN
+ };
+ enum OutputId {
+ _01_OUTPUT, _02_OUTPUT, _03_OUTPUT, _04_OUTPUT,
+ _05_OUTPUT, _06_OUTPUT, _07_OUTPUT, _08_OUTPUT,
+ _09_OUTPUT, _10_OUTPUT, _11_OUTPUT, _12_OUTPUT,
+ _13_OUTPUT, _14_OUTPUT, _15_OUTPUT, _16_OUTPUT,
+ _17_OUTPUT, _18_OUTPUT, _19_OUTPUT, _20_OUTPUT,
+ _21_OUTPUT, _22_OUTPUT, _23_OUTPUT, _24_OUTPUT,
+ OUTPUTS_LEN
+ };
+ enum LightId {
+ _00A_LIGHT, _00B_LIGHT,
+ _01A_LIGHT, _01B_LIGHT,
+ _02A_LIGHT, _02B_LIGHT, _02C_LIGHT, _02D_LIGHT, _02E_LIGHT,
+ _03A_LIGHT, _03B_LIGHT, _03C_LIGHT, _03D_LIGHT, _03E_LIGHT,
+ _04A_LIGHT, _04B_LIGHT, _04C_LIGHT,
+ _05A_LIGHT, _05B_LIGHT, _05C_LIGHT, _05D_LIGHT, _05E_LIGHT,
+ _06A_LIGHT, _06B_LIGHT, _06C_LIGHT, _06D_LIGHT, _06E_LIGHT,
+ _07A_LIGHT, _07B_LIGHT, _07C_LIGHT,
+ _08A_LIGHT, _08B_LIGHT, _08C_LIGHT, _08D_LIGHT, _08E_LIGHT,
+ _09A_LIGHT, _09B_LIGHT, _09C_LIGHT, _09D_LIGHT, _09E_LIGHT,
+ _10A_LIGHT, _10B_LIGHT,
+ _11A_LIGHT, _11B_LIGHT,
+ _12A_LIGHT, _12B_LIGHT, _12C_LIGHT, _12D_LIGHT, _12E_LIGHT,
+ _13A_LIGHT, _13B_LIGHT, _13C_LIGHT, _13D_LIGHT, _13E_LIGHT,
+ _14A_LIGHT, _14B_LIGHT,
+ _15A_LIGHT, _15B_LIGHT,
+ _00OUT_LIGHT,
+ _01OUT_LIGHT, _02OUT_LIGHT, _03OUT_LIGHT, _04OUT_LIGHT,
+ _05OUT_LIGHT, _06OUT_LIGHT, _07OUT_LIGHT, _08OUT_LIGHT,
+ _09OUT_LIGHT, _10OUT_LIGHT, _11OUT_LIGHT, _12OUT_LIGHT,
+ _13OUT_LIGHT, _14OUT_LIGHT, _15OUT_LIGHT, _16OUT_LIGHT,
+ _17OUT_LIGHT, _18OUT_LIGHT, _19OUT_LIGHT, _20OUT_LIGHT,
+ _21OUT_LIGHT, _22OUT_LIGHT, _23OUT_LIGHT, _24OUT_LIGHT,
+ LIGHTS_LEN
+ };
+
+ // Define the maximum number of nodes
+ static constexpr int MAX_NODES = 24;
+
+ // Define an array to store time variables
+ float lag[24] = {0.0f}; // Time interval for each light group
+ float groupElapsedTime[24] = {}; // Elapsed time since the last activation for each light group
+
+ float accumulatedTime = 0.0f; // Accumulator for elapsed time, now an instance variable
+
+
+ // Boolean array for active nodes management
+ bool activeNodes[MAX_NODES] = {};
+
+ //Keep track of input states so that we can avoid retriggering on gates
+ bool previousInputState = false;
+
+ //Keep track of the time the one input is above the threshold
+ float inputAboveThresholdTime = 0.0f; // Time in seconds
+
+ // Define groups of lights
+ const std::vector> lightGroups = {
+ {_01OUT_LIGHT, _00A_LIGHT, _00B_LIGHT, _01A_LIGHT},
+ {_02OUT_LIGHT, _01B_LIGHT, _02A_LIGHT, _02C_LIGHT, _02D_LIGHT},
+ {_03OUT_LIGHT, _02B_LIGHT, _03A_LIGHT, _03C_LIGHT, _03D_LIGHT},
+ {_04OUT_LIGHT, _02E_LIGHT, _04A_LIGHT, _04B_LIGHT},
+ {_05OUT_LIGHT, _03B_LIGHT, _05A_LIGHT, _05C_LIGHT, _05D_LIGHT},
+ {_06OUT_LIGHT, _04C_LIGHT, _06A_LIGHT, _06C_LIGHT, _06D_LIGHT},
+ {_07OUT_LIGHT, _03E_LIGHT, _07A_LIGHT, _07B_LIGHT},
+ {_08OUT_LIGHT, _05B_LIGHT, _08A_LIGHT, _08C_LIGHT, _08D_LIGHT},
+ {_09OUT_LIGHT, _06B_LIGHT, _09A_LIGHT, _09C_LIGHT, _09D_LIGHT},
+ {_10OUT_LIGHT, _07C_LIGHT, _10A_LIGHT},
+ {_11OUT_LIGHT, _05E_LIGHT, _11A_LIGHT},
+ {_12OUT_LIGHT, _08E_LIGHT, _12A_LIGHT, _12C_LIGHT, _12D_LIGHT},
+ {_13OUT_LIGHT, _08B_LIGHT, _13A_LIGHT, _13C_LIGHT, _13D_LIGHT},
+ {_14OUT_LIGHT, _09E_LIGHT, _14A_LIGHT},
+ {_15OUT_LIGHT, _09B_LIGHT, _15A_LIGHT},
+ {_16OUT_LIGHT, _11B_LIGHT},
+ {_17OUT_LIGHT, _10B_LIGHT},
+ {_18OUT_LIGHT, _13E_LIGHT},
+ {_19OUT_LIGHT, _06E_LIGHT},
+ {_20OUT_LIGHT, _12B_LIGHT},
+ {_21OUT_LIGHT, _13B_LIGHT},
+ {_22OUT_LIGHT, _12E_LIGHT},
+ {_23OUT_LIGHT, _14B_LIGHT},
+ {_24OUT_LIGHT, _15B_LIGHT},
+ };
+
+ //Define the node-connected graph structure
+ const std::map> nodeConnections = {
+ {0, {1}},
+ {1, {2, 3}},
+ {2, {4, 6}},
+ {3, {5}},
+ {4, {7, 10}},
+ {5, {8, 18}},
+ {6, {9}},
+ {7, {11, 12}},
+ {8, {13, 14}},
+ {9, {16}},
+ {10, {15}},
+ {11, {19, 21}},
+ {12, {17, 20}},
+ {13, {22}},
+ {14, {23}}
+ };
+
+ ImpulseController() {
+ config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
+ configParam(LAG_PARAM, 0.0f, 1.0f, 0.1f, "Lag");
+ configParam(SPREAD_PARAM, -1.0f, 1.f, 0.8f, "Spread");
+ configParam(DECAY_PARAM, 0.0f, 1.0f, 0.6f, "Decay");
+
+ configParam(LAG_ATT_PARAM, -1.0f, 1.0f, 0.5f, "Lag Attenuverter");
+ configParam(SPREAD_ATT_PARAM, -1.0f, 1.0f, 0.5f, "Spread Attenuverter");
+ configParam(DECAY_ATT_PARAM, -1.0f, 1.0f, 0.5f, "Decay Attenuverter");
+
+ configInput(_00_INPUT, "IN");
+ configInput(LAG_INPUT, "Lag");
+ configInput(SPREAD_INPUT, "Spread");
+ configInput(DECAY_INPUT, "Decay");
+ configOutput(_01_OUTPUT, ""); configOutput(_02_OUTPUT, "");
+ configOutput(_03_OUTPUT, ""); configOutput(_04_OUTPUT, "");
+ configOutput(_05_OUTPUT, ""); configOutput(_06_OUTPUT, "");
+ configOutput(_07_OUTPUT, ""); configOutput(_08_OUTPUT, "");
+ configOutput(_09_OUTPUT, ""); configOutput(_10_OUTPUT, "");
+ configOutput(_11_OUTPUT, ""); configOutput(_12_OUTPUT, "");
+ configOutput(_13_OUTPUT, ""); configOutput(_14_OUTPUT, "");
+ configOutput(_15_OUTPUT, ""); configOutput(_16_OUTPUT, "");
+ configOutput(_17_OUTPUT, ""); configOutput(_18_OUTPUT, "");
+ configOutput(_19_OUTPUT, ""); configOutput(_20_OUTPUT, "");
+ configOutput(_21_OUTPUT, ""); configOutput(_22_OUTPUT, "");
+ configOutput(_23_OUTPUT, ""); configOutput(_24_OUTPUT, "");
+ }
+
+ void process(const ProcessArgs& args) override {
+ const float baseSampleTime = 2.0f / 44100.0f; // Base sample time for 44.1 kHz //cut the CPU by 50%
+
+ // Accumulate elapsed time
+ accumulatedTime += args.sampleTime;
+
+ // Only update at an equivalent frequency of 44.1 kHz
+ if (accumulatedTime >= baseSampleTime) {
+ //Process inputs to paramaters
+ float decay = params[DECAY_PARAM].getValue();
+ float spread = params[SPREAD_PARAM].getValue();
+
+ lag[0] = params[LAG_PARAM].getValue(); //Time interval for the first generator
+
+ if (inputs[LAG_INPUT].isConnected())
+ lag[0] += inputs[LAG_INPUT].getVoltage()*0.2*params[LAG_ATT_PARAM].getValue();
+ if (inputs[SPREAD_INPUT].isConnected())
+ spread += inputs[SPREAD_INPUT].getVoltage()*0.4*params[SPREAD_ATT_PARAM].getValue();
+ if (inputs[DECAY_INPUT].isConnected())
+ decay += inputs[DECAY_INPUT].getVoltage()*0.2*params[DECAY_ATT_PARAM].getValue();
+
+ // Clamp the param values after adding voltages
+ decay = clamp(decay, 0.00f, 1.0f);
+ spread = clamp(spread, -1.00f, 1.0f);
+ lag[0] = clamp(lag[0], 0.0f, 1.0f);
+
+
+ // Apply non-linear re-scaling to parameters to make them feel better
+ lag[0] = pow(lag[0], 0.5); //
+ spread = (spread >= 0 ? 1 : -1) * pow(abs(spread), 4);
+
+ decay = 1 - pow(1 - decay, 1.5);
+ decay = decay*0.005f + 0.99499f; //since the decay is so exponential, only values .99...1.0 are really useful for scaling.
+
+
+ // Re-Clamp the param values after non-linear scaling
+ decay = clamp(decay, 0.0f, 1.0f);
+ spread = clamp(spread, -0.9999f, 1.0f);
+ lag[0] = clamp(lag[0], 0.001f, 1.0f);
+ lag[0] *= 0.35f; //rescale the lag values
+
+ lag[23] = 1.2f*spread * lag[0] + 1.2f*lag[0];
+
+ // Scaling factor for the power scale
+ float scalingFactor = 0.20f; // Adjust this to tune the scaling curve
+
+ // Interpolate lag[1] to lag[22] using a power scale
+ for (int i = 1; i < 23; i++) {
+ float factor = powf(i / 23.0f, scalingFactor);
+ lag[i] = lag[0] + (lag[23] - lag[0]) * factor;
+ }
+
+
+ //Set threshold to drop to before propagating to the next node, based on the spread input
+ //This function is an ellipse with the left at -1,1 and the right at 0.5,0
+
+ float propagate_thresh = 0.0f;
+ if (spread <0.25){
+ propagate_thresh = 10.0f - 10.0f * sqrtf(1.0f - powf((spread + 0.25f) / 0.75f, 2.0f));
+ }
+
+ propagate_thresh = clamp (propagate_thresh, 0.01f, 10.0f);
+
+ // Check for manual trigger or input trigger to activate the first node
+ bool manualTriggerPressed = params[TRIGGER_BUTTON].getValue() > 0.0f;
+ bool currentInputState = (inputs[_00_INPUT].isConnected() && inputs[_00_INPUT].getVoltage() > 1.0f) || manualTriggerPressed;
+ if (currentInputState && !previousInputState && outputs[_01_OUTPUT].getVoltage() <= propagate_thresh) {
+ activeNodes[0] = true; // Activate node 0
+ groupElapsedTime[0] = 0.f; // Reset elapsed time for node 0
+ for (LightId light : lightGroups[0]) {
+ lights[light].setBrightness(1.0f); // Turn on all lights for node 0's group
+ }
+ }
+ previousInputState = currentInputState;
+
+ // Reset the trigger button state after processing to ensure it is ready for the next press
+ if (manualTriggerPressed) {
+ params[TRIGGER_BUTTON].setValue(0.0f);
+ }
+
+
+ if (inputs[_00_INPUT].isConnected()){
+ float brightness = inputs[_00_INPUT].getVoltage() / 10.0f;
+ lights[_00OUT_LIGHT].setSmoothBrightness(brightness, args.sampleTime);
+ }
+
+ // Activate and Deactivate Nodes, Activate Child Nodes
+ // Iterate over all possible nodes
+ for (int node = 0; node < 24; ++node) {
+ // Check if the node is active
+ if (activeNodes[node]) {
+ // Increment the elapsed time for each active node
+ groupElapsedTime[node] += args.sampleTime; // This ensures the elapsed time is updated every cycle
+
+ // Check if the output voltage is below the propagation threshold
+ if (outputs[_01_OUTPUT + node].getVoltage() < propagate_thresh && groupElapsedTime[node]>0.8*lag[node]) {
+ activeNodes[node] = false;
+ // Check and activate child nodes if they are considered inactive
+ if (nodeConnections.count(node) > 0) { // Ensure the node has connections defined
+ for (int childNode : nodeConnections.at(node)) {
+ // Check if the child node is inactive
+ if (!activeNodes[childNode] ) {
+ activeNodes[childNode] = true; // Activate the child node
+ groupElapsedTime[childNode] = 0.f; // Reset the child node's elapsed time // outputs[_01_OUTPUT + childNode].setVoltage(10.0f); // Set an initial voltage for the child node
+ for (LightId light : lightGroups[childNode]) {
+ lights[light].setBrightness(1.0f); // Turn on all lights for the child node's group
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ float slewRate = 0.1f;
+ // Map OUT light brightness to OUTPUT voltages
+ for (int i = 0; i < 24; ++i) {
+ // Directly map the light brightness to output voltage
+ float brightness = lights[_01OUT_LIGHT + i].getBrightness(); // Get the brightness of the corresponding light
+ float current_out = outputs[_01_OUTPUT + i].getVoltage(); // Get the voltage of the current output
+ float difference = (brightness * 10.0f) - current_out;
+ float voltageChange = difference;
+
+ // If the voltage is increasing, apply slew limiting
+ if (difference > 0) {
+ voltageChange = fmin(difference, slewRate);
+ }
+
+ outputs[_01_OUTPUT + i].setVoltage( current_out + voltageChange ); // transition to new output voltage based on the light brightness
+ }
+
+
+ // Dim lights slowly for each light group
+ for (int groupIndex = 0; groupIndex < int(lightGroups.size()); ++groupIndex) {
+ // Calculate the dimming factor for the current group
+ float dimmingFactor = decay + (0.99993f-decay)*2*(lag[groupIndex]);
+ dimmingFactor = clamp(dimmingFactor,0.0f,0.99993f);
+
+ // Apply the dimming factor to each light within the current group
+ for (LightId lightId : lightGroups[groupIndex]) {
+ float how_bright = lights[lightId].getBrightness();
+ how_bright *= dimmingFactor;
+ lights[lightId].setBrightness(how_bright);
+ }
+ }
+
+
+ // After processing, reset the accumulated time
+ accumulatedTime -= baseSampleTime; // Subtract to maintain precision and handle any excess
+
+ }//if (accumulated_time...
+ } // void process
+}; //struct
+
+struct ImpulseControllerWidget : ModuleWidget {
+ ImpulseControllerWidget(ImpulseController* module) {
+ setModule(module);
+
+ setPanel(createPanel(
+ asset::plugin(pluginInstance, "res/ImpulseController.svg"),
+ asset::plugin(pluginInstance, "res/ImpulseController-dark.svg")
+ ));
+
+
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+
+ addParam(createParamCentered(mm2px(Vec(10.916, 65)), module, ImpulseController::TRIGGER_BUTTON));
+
+
+ addInput(createInputCentered(mm2px(Vec(10.916, 72.73)), module, ImpulseController::_00_INPUT));
+
+ // Attenuverter Knobs
+ addParam(createParamCentered(mm2px(Vec(11.064, 35.728)), module, ImpulseController::LAG_ATT_PARAM));
+ addParam(createParamCentered(mm2px(Vec(29.756, 35.728)), module, ImpulseController::SPREAD_ATT_PARAM));
+ addParam(createParamCentered(mm2px(Vec(48.449, 35.728)), module, ImpulseController::DECAY_ATT_PARAM));
+
+
+ addParam(createParamCentered(mm2px(Vec(10.957, 24)), module, ImpulseController::LAG_PARAM));
+ addParam(createParamCentered(mm2px(Vec(29.649, 24)), module, ImpulseController::SPREAD_PARAM));
+ addParam(createParamCentered(mm2px(Vec(48.342, 24)), module, ImpulseController::DECAY_PARAM));
+ addInput(createInputCentered(mm2px(Vec(11.171, 45.049)), module, ImpulseController::LAG_INPUT));
+ addInput(createInputCentered(mm2px(Vec(29.864, 45.049)), module, ImpulseController::SPREAD_INPUT));
+ addInput(createInputCentered(mm2px(Vec(48.556, 45.049)), module, ImpulseController::DECAY_INPUT));
+
+
+ addOutput(createOutputCentered(mm2px(Vec(29.445, 72.73)), module, ImpulseController::_01_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(47.974, 72.73)), module, ImpulseController::_02_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(66.503, 72.73)), module, ImpulseController::_03_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(66.503, 54.201)), module, ImpulseController::_04_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(85.031, 72.73)), module, ImpulseController::_05_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(85.031, 35.672)), module, ImpulseController::_06_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(85.031, 91.258)), module, ImpulseController::_07_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(103.56, 72.73)), module, ImpulseController::_08_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(103.56, 35.672)), module, ImpulseController::_09_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(103.56, 109.656)), module, ImpulseController::_10_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(103.56, 54.201)), module, ImpulseController::_11_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(122.089, 91.258)), module, ImpulseController::_12_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(122.089, 72.73)), module, ImpulseController::_13_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(122.089, 17.144)), module, ImpulseController::_14_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(122.089, 35.672)), module, ImpulseController::_15_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(122.089, 54.201)), module, ImpulseController::_16_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(122.089, 109.656)), module, ImpulseController::_17_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(140.618, 54.201)), module, ImpulseController::_18_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(103.56, 17.144)), module, ImpulseController::_19_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(140.618, 91.258)), module, ImpulseController::_20_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(140.618, 72.73)), module, ImpulseController::_21_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(140.618, 109.656)), module, ImpulseController::_22_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(140.618, 17.144)), module, ImpulseController::_23_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(140.618, 35.672)), module, ImpulseController::_24_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(20.181, 72.73)), module, ImpulseController::_00A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(23.139, 72.73)), module, ImpulseController::_00B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(38.706, 72.73)), module, ImpulseController::_01A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(41.664, 72.73)), module, ImpulseController::_01B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(57.235, 72.73)), module, ImpulseController::_02A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(60.193, 72.73)), module, ImpulseController::_02B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(54.284, 66.42)), module, ImpulseController::_02C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(57.236, 63.468)), module, ImpulseController::_02D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(60.193, 60.511)), module, ImpulseController::_02E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(75.764, 72.73)), module, ImpulseController::_03A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(78.721, 72.73)), module, ImpulseController::_03B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(72.813, 79.04)), module, ImpulseController::_03C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(75.765, 81.992)), module, ImpulseController::_03D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(78.722, 84.949)), module, ImpulseController::_03E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(72.813, 47.891)), module, ImpulseController::_04A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(75.765, 44.939)), module, ImpulseController::_04B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(78.722, 41.982)), module, ImpulseController::_04C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(94.292, 72.73)), module, ImpulseController::_05A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(97.25, 72.73)), module, ImpulseController::_05B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(91.342, 66.42)), module, ImpulseController::_05C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(94.294, 63.468)), module, ImpulseController::_05D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(97.25, 60.511)), module, ImpulseController::_05E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(94.292, 35.672)), module, ImpulseController::_06A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(97.25, 35.672)), module, ImpulseController::_06B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(91.342, 29.362)), module, ImpulseController::_06C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(94.294, 26.41)), module, ImpulseController::_06D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(97.25, 23.454)), module, ImpulseController::_06E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(91.342, 97.569)), module, ImpulseController::_07A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(94.294, 100.521)), module, ImpulseController::_07B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(97.25, 103.478)), module, ImpulseController::_07C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(112.821, 72.73)), module, ImpulseController::_08A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(115.779, 72.73)), module, ImpulseController::_08B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 79.04)), module, ImpulseController::_08C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(112.823, 81.992)), module, ImpulseController::_08D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(115.779, 84.949)), module, ImpulseController::_08E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(112.821, 35.672)), module, ImpulseController::_09A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(115.779, 35.672)), module, ImpulseController::_09B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 29.362)), module, ImpulseController::_09C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(112.823, 26.41)), module, ImpulseController::_09D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(115.779, 23.454)), module, ImpulseController::_09E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(112.821, 109.656)), module, ImpulseController::_10A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(115.779, 109.656)), module, ImpulseController::_10B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(112.821, 54.201)), module, ImpulseController::_11A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(115.779, 54.201)), module, ImpulseController::_11B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(131.35, 91.258)), module, ImpulseController::_12A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(134.308, 91.258)), module, ImpulseController::_12B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.4, 97.569)), module, ImpulseController::_12C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(131.352, 100.521)), module, ImpulseController::_12D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(134.308, 103.478)), module, ImpulseController::_12E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(131.35, 72.73)), module, ImpulseController::_13A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(134.308, 72.73)), module, ImpulseController::_13B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.4, 66.42)), module, ImpulseController::_13C_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(131.352, 63.468)), module, ImpulseController::_13D_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(134.308, 60.511)), module, ImpulseController::_13E_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(131.35, 17.144)), module, ImpulseController::_14A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(134.308, 17.144)), module, ImpulseController::_14B_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(131.35, 35.672)), module, ImpulseController::_15A_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(134.308, 35.672)), module, ImpulseController::_15B_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(17.23, 72.73)), module, ImpulseController::_00OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(35.756, 72.73)), module, ImpulseController::_01OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(54.284, 72.73)), module, ImpulseController::_02OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(72.813, 72.73)), module, ImpulseController::_03OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(72.813, 54.426)), module, ImpulseController::_04OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(91.342, 72.73)), module, ImpulseController::_05OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(91.342, 35.672)), module, ImpulseController::_06OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(91.342, 91.258)), module, ImpulseController::_07OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 72.73)), module, ImpulseController::_08OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 35.672)), module, ImpulseController::_09OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 109.656)), module, ImpulseController::_10OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 54.201)), module, ImpulseController::_11OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.4, 91.258)), module, ImpulseController::_12OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.4, 72.73)), module, ImpulseController::_13OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.4, 17.144)), module, ImpulseController::_14OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.4, 35.672)), module, ImpulseController::_15OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(129.392, 54.096)), module, ImpulseController::_16OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(128.53, 109.824)), module, ImpulseController::_17OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(146.928, 54.201)), module, ImpulseController::_18OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(109.871, 17.144)), module, ImpulseController::_19OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(146.928, 91.258)), module, ImpulseController::_20OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(146.928, 72.73)), module, ImpulseController::_21OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(146.928, 109.656)), module, ImpulseController::_22OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(146.928, 17.144)), module, ImpulseController::_23OUT_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(146.928, 35.672)), module, ImpulseController::_24OUT_LIGHT));
+ }
+};
+
+Model* modelImpulseController = createModel("ImpulseController");
\ No newline at end of file
diff --git a/plugins/CVfunk/src/PentaSequencer.cpp b/plugins/CVfunk/src/PentaSequencer.cpp
new file mode 100644
index 0000000..0cbccf4
--- /dev/null
+++ b/plugins/CVfunk/src/PentaSequencer.cpp
@@ -0,0 +1,448 @@
+////////////////////////////////////////////////////////////
+//
+// Penta Sequencer
+//
+// written by Cody Geary
+// Copyright 2024, MIT License
+//
+// A five stage sequencer with five outputs and slew.
+//
+////////////////////////////////////////////////////////////
+
+
+#include "plugin.hpp"
+
+struct PentaSequencer : Module {
+
+ // Initialize variables for trigger detection
+ dsp::SchmittTrigger resetTrigger;
+
+ // Initialize timer dsps
+ dsp::Timer triggerIntervalTimer;
+
+ // Sequencer operation modes
+ enum Mode {
+ CW_CIRC,
+ CCW_CIRC,
+ CW_STAR,
+ CCW_STAR
+ };
+
+ // Mode toggles
+ bool circMode = true, starMode = false, cwMode = true, ccwMode = false; // Default modes
+
+ enum ParamId {
+ SLEW_PARAM,
+ KNOB1_PARAM, KNOB2_PARAM, KNOB3_PARAM, KNOB4_PARAM, KNOB5_PARAM,
+ MANUAL_TRIGGER_PARAM,
+ PARAMS_LEN
+ };
+ enum InputId {
+ TRIG_INPUT, SHAPE_INPUT, SHIFT_INPUT, DIR_INPUT, RESET_INPUT,
+ INPUTS_LEN
+ };
+ enum OutputId {
+ A_OUTPUT, B_OUTPUT, C_OUTPUT, D_OUTPUT, E_OUTPUT,
+ OUTPUTS_LEN
+ };
+ enum LightId {
+ STEP1_LIGHT, STEP2_LIGHT, STEP3_LIGHT, STEP4_LIGHT, STEP5_LIGHT,
+
+ AA_LIGHT, AB_LIGHT, AC_LIGHT, AD_LIGHT, AE_LIGHT,
+ BA_LIGHT, BB_LIGHT, BC_LIGHT, BD_LIGHT, BE_LIGHT,
+ CA_LIGHT, CB_LIGHT, CC_LIGHT, CD_LIGHT, CE_LIGHT,
+ DA_LIGHT, DB_LIGHT, DC_LIGHT, DD_LIGHT, DE_LIGHT,
+ EA_LIGHT, EB_LIGHT, EC_LIGHT, ED_LIGHT, EE_LIGHT,
+
+ INNERA_LIGHT, INNERB_LIGHT, INNERC_LIGHT, INNERD_LIGHT, INNERE_LIGHT,
+ OUTERA_LIGHT, OUTERB_LIGHT, OUTERC_LIGHT, OUTERD_LIGHT, OUTERE_LIGHT,
+ LIGHTS_LEN
+ };
+
+ // Variables for internal logic
+ int step = 0; // Current step in the sequence
+ int mode = 0; // Operation mode: 0 = CW_CIRC, 1 = CCW_CIRC, 2 = CW_STAR, 3 = CCW_STAR
+ float lastTriggerTime = 0; // Time of the last trigger input
+ float triggerInterval = 100;
+ float lastTargetVoltages[5] = {0.f, 0.f, 0.f, 0.f, 0.f}; // Initialize with default voltages, assuming start at 0V
+ int dimmingCounter = 0;
+ const int dimmingRate = 100; // Number of process calls before dimming, adjust for desired timing
+ bool previousTriggerState = false;
+ int prevMapping[5] = {0, 1, 2, 3, 4}; // Initialize with default mapping or actual initial mapping
+
+ bool onTarget = true;
+
+ dsp::SlewLimiter slewLimiters[5]; // One per output (A-E)
+
+ PentaSequencer() {
+ config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
+ configParam(SLEW_PARAM, 0.f, 1.f, 0.f, "Slew");
+ configParam(KNOB1_PARAM, -5.f, 5.f, 0.f, "I");
+ configParam(KNOB2_PARAM, -5.f, 5.f, 0.f, "II");
+ configParam(KNOB3_PARAM, -5.f, 5.f, 0.f, "III");
+ configParam(KNOB4_PARAM, -5.f, 5.f, 0.f, "IV");
+ configParam(KNOB5_PARAM, -5.f, 5.f, 0.f, "V");
+ configInput(TRIG_INPUT, "Trigger IN");
+ configInput(SHAPE_INPUT, "Shape IN");
+ configInput(SHIFT_INPUT, "Shift IN");
+ configInput(DIR_INPUT, "Dir IN");
+ configInput(RESET_INPUT, "Reset IN");
+ configOutput(A_OUTPUT, "A");
+ configOutput(B_OUTPUT, "B");
+ configOutput(C_OUTPUT, "C");
+ configOutput(D_OUTPUT, "D");
+ configOutput(E_OUTPUT, "E");
+
+ }
+
+ void process(const ProcessArgs& args) override {
+
+ // Accumulate time in the timer
+ triggerIntervalTimer.process(args.sampleTime);
+
+ // Read knob values and update outputs
+ float knobValues[5] = {
+ params[KNOB1_PARAM].getValue(),
+ params[KNOB2_PARAM].getValue(),
+ params[KNOB3_PARAM].getValue(),
+ params[KNOB4_PARAM].getValue(),
+ params[KNOB5_PARAM].getValue()
+ };
+
+
+ // Process reset input
+ if (resetTrigger.process(inputs[RESET_INPUT].getVoltage())) {
+ step = 0; // Reset to the first step
+
+ // Measure the trigger interval here
+ triggerInterval = triggerIntervalTimer.time; // Get the accumulated time since the last reset
+ triggerIntervalTimer.reset(); // Reset the timer for the next trigger interval measurement
+
+ }
+
+
+ // Handle CIRC and STAR modes based on SHAPE_INPUT voltage
+ if (inputs[SHAPE_INPUT].getVoltage() > 1.0f) {
+ // Voltage > 1.0f indicates STAR mode
+ starMode = true;
+ circMode = false;
+ } else {
+ // Voltage 0.0f indicates CIRC mode
+ circMode = true;
+ starMode = false;
+ }
+
+ // Handle CW and CCW modes based on DIR_INPUT voltage
+ if (inputs[DIR_INPUT].getVoltage() > 1.0f) {
+ // Voltage > 1.0f indicates CCW mode
+ ccwMode = true;
+ cwMode = false;
+ } else {
+ // Voltage 0.0f indicates CW mode
+ cwMode = true;
+ ccwMode = false;
+ }
+
+
+ // Define Knob to Output maps
+ // A B C D E
+ int CIRC_CW_map[5] = {0, 1, 2, 3, 4};
+ int STAR_CW_map[5] = {0, 3, 1, 4, 2};
+ int CIRC_CCW_map[5] = {0, 4, 3, 2, 1};
+ int STAR_CCW_map[5] = {0, 2, 4, 1, 3};
+
+
+ int* currentMapping;
+ int* newMapping = nullptr; // Pointer to hold the new mapping based on the current mode
+
+ // Determine the new mapping based on the mode
+ if (cwMode && circMode) {
+ newMapping = CIRC_CW_map;
+ } else if (cwMode && starMode) {
+ newMapping = STAR_CW_map;
+ } else if (ccwMode && circMode) {
+ newMapping = CIRC_CCW_map;
+ } else if (ccwMode && starMode) {
+ newMapping = STAR_CCW_map;
+ }
+
+ // Check if the mapping has changed
+ bool mappingChanged = false;
+ for (int i = 0; i < 5; ++i) {
+ if (newMapping[i] != prevMapping[i]) {
+ mappingChanged = true;
+ break; // No need to continue if a change is found
+ }
+ }
+
+ // Update currentMapping and prevMapping if there's a change
+ if (mappingChanged) {
+ currentMapping = newMapping; // Update the current mapping
+ for (int i = 0; i < 5; ++i) {
+ prevMapping[i] = newMapping[i]; // Update prevMapping for the next comparison
+ }
+
+ // Reset the timer due to mapping change
+ triggerInterval = triggerIntervalTimer.time; // Get the accumulated time since the last reset
+ triggerIntervalTimer.reset(); // Reset the timer for the next trigger interval measurement
+ }
+
+
+ // Detect a rising signal on TRIG_INPUT or manual trigger button press
+ bool manualTriggerPressed = params[MANUAL_TRIGGER_PARAM].getValue() > 0.0f;
+ bool currentTriggerState = (inputs[TRIG_INPUT].isConnected() && inputs[TRIG_INPUT].getVoltage() > 1.0f) || manualTriggerPressed; // Include manual trigger
+
+
+ if (currentTriggerState && !previousTriggerState) {
+ for (int i = 0; i < 5; ++i) {
+ // Update the last target voltage for the next cycle
+ lastTargetVoltages[i] = knobValues[currentMapping[(step + i) % 5]];
+ }
+
+ step = (step + 1) % 5 ; // Increment step by 1 for CCW
+
+ // Measure the trigger interval here
+ triggerInterval = triggerIntervalTimer.time; // Get the accumulated time since the last reset
+ triggerIntervalTimer.reset(); // Reset the timer for the next trigger interval measurement
+ }
+
+ // Update previousTriggerState at the end of the process cycle
+ previousTriggerState = currentTriggerState;
+
+
+ // Map the knobs and set output voltages with dynamic slew limiting based on trigger interval
+ int knobMapping[5];
+ for (int i = 0; i < 5; ++i) {
+
+ int knobIndex;
+ // For CW mode, use the standard incrementing mapping
+ knobIndex = currentMapping[(step + i) % 5];
+ if (cwMode) {
+ } else {
+ // For CCW mode, adjust the mapping to decrement
+ knobIndex = currentMapping[(step + i) % 5]; // Adjust index for CCW rotation
+ }
+ float targetVoltage = knobValues[knobIndex];
+
+ knobMapping[i] = knobIndex; // Store the knob mapping for later use
+
+ targetVoltage = knobValues[knobIndex];
+ float slewRate = params[SLEW_PARAM].getValue(); // This gives a value between 0 and 1
+
+
+ // Calculate the absolute voltage difference from the last target
+ float voltageDifference = fabs(targetVoltage - lastTargetVoltages[i]);
+
+
+ // Adjust slewSpeed based on the voltage difference and trigger interval
+ // Ensure triggerInterval is non-zero to avoid division by zero
+ float adjustedTriggerInterval = fmax(triggerInterval, 1e-6f);
+ float slewSpeed = voltageDifference / adjustedTriggerInterval; // Voltage difference per second
+
+ // Apply the SLEW_PARAM knob to scale the slewSpeed, adding 1e-6 to avoid division by zero
+ slewSpeed *= 1.0f / (slewRate + 1e-6f);
+
+ // Set the rise and fall speeds of the slew limiter to the calculated slew speed
+ slewLimiters[i].setRiseFall(slewSpeed, slewSpeed);
+
+ // Process the target voltage through the slew limiter
+ float slewedVoltage = slewLimiters[i].process(args.sampleTime, targetVoltage);
+
+ //Transpose the output voltage by the transpose input
+ float transpose = inputs[SHIFT_INPUT].getVoltage();
+ float outputVoltage = slewedVoltage+transpose;
+
+ //Clamp the output voltage to normal range
+ outputVoltage = clamp(outputVoltage, -10.0f, 10.0f);
+
+ // Set the output voltage to the slewed voltage
+ outputs[A_OUTPUT + i].setVoltage(outputVoltage);
+ }
+
+
+ // Increment dimming counter and check if it's time to dim the lights
+ if (++dimmingCounter >= dimmingRate) {
+ for (int i = 0; i < LIGHTS_LEN; ++i) {
+ float currentBrightness = lights[i].getBrightness();
+ // Apply a less aggressive dimming factor for inner and outer lights
+ if (i == INNERA_LIGHT || i == INNERB_LIGHT || i == INNERC_LIGHT || i == INNERD_LIGHT || i == INNERE_LIGHT ||
+ i == OUTERA_LIGHT || i == OUTERB_LIGHT || i == OUTERC_LIGHT || i == OUTERD_LIGHT || i == OUTERE_LIGHT) {
+ lights[i].setBrightness(currentBrightness * 0.98f); // Slower dimming for inner and outer lights
+ } else {
+ // More aggressive dimming for the rest
+ lights[i].setBrightness(currentBrightness * 0.90f);
+ }
+ }
+ dimmingCounter = 0; // Reset counter after dimming
+ }
+
+
+ // Update output group lights logic and step lights...
+ for (int output = 0; output < 5; ++output) {
+ int knob = knobMapping[output]; // The current knob mapped to this output
+
+ // Update the output group lights
+ for (int subLight = 0; subLight <= knob; ++subLight) {
+ // Boost the brightness if not currently in a dimming cycle
+ if (dimmingCounter == 0) {
+ lights[AA_LIGHT + output * 5 + subLight].setBrightness(1.0f);
+ }
+ }
+
+ // Update the step lights to indicate which knob corresponds to Node A
+ if (output == 0) { // Node A corresponds to the first output
+ for (int i = 0; i < 5; ++i) {
+ if (i == knob) {
+ // Light up the step light under the knob controlling Node A
+ lights[STEP1_LIGHT + i].setBrightness(1.0f);
+ } else {
+ // Turn off other step lights
+ lights[STEP1_LIGHT + i].setBrightness(0.0f);
+ }
+ }
+ }
+ }
+
+
+ // Inner Lights for STAR Track Movements
+ if (starMode) {
+ switch (step) {
+ case 0: //E->A->B
+ lights[INNERA_LIGHT].setBrightness(1.0f);
+ lights[INNERE_LIGHT].setBrightness(1.0f);
+ break;
+ case 1: // E->A->B
+ lights[INNERE_LIGHT].setBrightness(1.0f);
+ lights[INNERD_LIGHT].setBrightness(1.0f);
+ break;
+ case 2: // C->D->E
+ lights[INNERD_LIGHT].setBrightness(1.0f);
+ lights[INNERC_LIGHT].setBrightness(1.0f);
+ break;
+ case 3: // B->C->D
+ lights[INNERC_LIGHT].setBrightness(1.0f);
+ lights[INNERB_LIGHT].setBrightness(1.0f);
+ break;
+ case 4: //A->B->C
+ lights[INNERB_LIGHT].setBrightness(1.0f);
+ lights[INNERA_LIGHT].setBrightness(1.0f);
+ break;
+ }
+ }
+
+ // Outer Lights for CIRC Track Movements
+ if (circMode) {
+ switch (step) {
+ case 0: //E->A->B
+ lights[OUTERA_LIGHT].setBrightness(1.0f);
+ lights[OUTERE_LIGHT].setBrightness(1.0f);
+ break;
+ case 1: // E->A->B
+ lights[OUTERE_LIGHT].setBrightness(1.0f);
+ lights[OUTERD_LIGHT].setBrightness(1.0f);
+ break;
+ case 2: // C->D->E
+ lights[OUTERD_LIGHT].setBrightness(1.0f);
+ lights[OUTERC_LIGHT].setBrightness(1.0f);
+ break;
+ case 3: // B->C->D
+ lights[OUTERC_LIGHT].setBrightness(1.0f);
+ lights[OUTERB_LIGHT].setBrightness(1.0f);
+ break;
+ case 4: //A->B->C
+ lights[OUTERB_LIGHT].setBrightness(1.0f);
+ lights[OUTERA_LIGHT].setBrightness(1.0f);
+ break;
+ }
+ }
+
+
+ }//void
+};//module
+
+
+struct PentaSequencerWidget : ModuleWidget {
+ PentaSequencerWidget(PentaSequencer* module) {
+ setModule(module);
+
+ setPanel(createPanel(
+ asset::plugin(pluginInstance, "res/PentaSequencer.svg"),
+ asset::plugin(pluginInstance, "res/PentaSequencer-dark.svg")
+ ));
+
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+ addParam(createParamCentered(mm2px(Vec(38.001, 44.06)), module, PentaSequencer::SLEW_PARAM));
+ addParam(createParamCentered(mm2px(Vec(9.281, 77.271)), module, PentaSequencer::KNOB3_PARAM));
+ addParam(createParamCentered(mm2px(Vec(20.23, 92.394)), module, PentaSequencer::KNOB2_PARAM));
+ addParam(createParamCentered(mm2px(Vec(38.213, 96.263)), module, PentaSequencer::KNOB1_PARAM));
+ addParam(createParamCentered(mm2px(Vec(56.197, 92.394)), module, PentaSequencer::KNOB5_PARAM));
+ addParam(createParamCentered(mm2px(Vec(67.146, 77.271)), module, PentaSequencer::KNOB4_PARAM));
+
+ addParam(createParamCentered(mm2px(Vec(7.235, 105)), module, PentaSequencer::MANUAL_TRIGGER_PARAM));
+ addInput(createInputCentered(mm2px(Vec(7.235, 112.373)), module, PentaSequencer::TRIG_INPUT));
+ addInput(createInputCentered(mm2px(Vec(22.67, 112.373)), module, PentaSequencer::SHAPE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(38.105, 112.373)), module, PentaSequencer::SHIFT_INPUT));
+ addInput(createInputCentered(mm2px(Vec(53.54, 112.373)), module, PentaSequencer::DIR_INPUT));
+ addInput(createInputCentered(mm2px(Vec(68.975, 112.373)), module, PentaSequencer::RESET_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(38.287, 70.309)), module, PentaSequencer::A_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(13.478, 52.214)), module, PentaSequencer::B_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(22.639, 23.158)), module, PentaSequencer::C_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(53.652, 23.333)), module, PentaSequencer::D_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(62.813, 52.274)), module, PentaSequencer::E_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(9.143, 84.666)), module, PentaSequencer::STEP3_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(20.172, 99.422)), module, PentaSequencer::STEP2_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(38.379, 103.301)), module, PentaSequencer::STEP1_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(56.476, 99.422)), module, PentaSequencer::STEP5_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(67.423, 84.336)), module, PentaSequencer::STEP4_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(38.287, 77.713)), module, PentaSequencer::AA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(31.262, 72.607)), module, PentaSequencer::AB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(33.938, 64.355)), module, PentaSequencer::AC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(42.636, 64.305)), module, PentaSequencer::AD_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(45.294, 72.56)), module, PentaSequencer::AE_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(13.478, 59.618)), module, PentaSequencer::BA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(6.454, 54.512)), module, PentaSequencer::BB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(9.129, 46.261)), module, PentaSequencer::BC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(17.827, 46.211)), module, PentaSequencer::BD_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(20.485, 54.466)), module, PentaSequencer::BE_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(22.639, 30.563)), module, PentaSequencer::CA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(15.614, 25.457)), module, PentaSequencer::CB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(18.29, 17.205)), module, PentaSequencer::CC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(26.987, 17.156)), module, PentaSequencer::CD_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(29.645, 25.41)), module, PentaSequencer::CE_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(53.652, 30.737)), module, PentaSequencer::DA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(46.628, 25.631)), module, PentaSequencer::DB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(49.304, 17.38)), module, PentaSequencer::DC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(58.001, 17.33)), module, PentaSequencer::DD_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(60.66, 25.585)), module, PentaSequencer::DE_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(62.813, 59.679)), module, PentaSequencer::EA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(55.788, 54.573)), module, PentaSequencer::EB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(58.464, 46.321)), module, PentaSequencer::EC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(67.162, 46.272)), module, PentaSequencer::ED_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(69.82, 54.526)), module, PentaSequencer::EE_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(30.438, 54.683)), module, PentaSequencer::INNERA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(25.619, 40.522)), module, PentaSequencer::INNERB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(38.101, 31.358)), module, PentaSequencer::INNERC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(50.999, 40.762)), module, PentaSequencer::INNERD_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(46.124, 54.783)), module, PentaSequencer::INNERE_LIGHT));
+
+ addChild(createLightCentered>(mm2px(Vec(21.274, 67.501)), module, PentaSequencer::OUTERA_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(10.548, 35.306)), module, PentaSequencer::OUTERB_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(38.201, 14.859)), module, PentaSequencer::OUTERC_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(65.979, 36.066)), module, PentaSequencer::OUTERD_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(55.34, 67.448)), module, PentaSequencer::OUTERE_LIGHT));
+ }
+};
+
+Model* modelPentaSequencer = createModel("PentaSequencer");
\ No newline at end of file
diff --git a/plugins/CVfunk/src/Ranges.cpp b/plugins/CVfunk/src/Ranges.cpp
new file mode 100644
index 0000000..e461781
--- /dev/null
+++ b/plugins/CVfunk/src/Ranges.cpp
@@ -0,0 +1,127 @@
+////////////////////////////////////////////////////////////
+//
+// Ranges
+//
+// written by Cody Geary
+// Copyright 2024, MIT License
+//
+// Divides two input sequences into a range of voltages
+//
+////////////////////////////////////////////////////////////
+
+
+#include "plugin.hpp"
+
+struct Ranges : Module {
+ enum ParamId {
+ TOP_PARAM,
+ BOTTOM_PARAM,
+ TOP_ATT_PARAM,
+ BOTTOM_ATT_PARAM,
+ DIVISIONS_PARAM,
+ NUM_PARAMS
+ };
+ enum InputId {
+ TOP_INPUT,
+ BOTTOM_INPUT,
+ DIVISIONS_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputId {
+ OUT1_OUTPUT, OUT2_OUTPUT, OUT3_OUTPUT,
+ OUT4_OUTPUT, OUT5_OUTPUT, OUT6_OUTPUT,
+ OUT7_OUTPUT, OUT8_OUTPUT, OUT9_OUTPUT,
+ OUT10_OUTPUT, OUT11_OUTPUT, OUT12_OUTPUT,
+ OUT13_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightId {
+ OUT1_LIGHT, OUT2_LIGHT, OUT3_LIGHT,
+ OUT4_LIGHT, OUT5_LIGHT, OUT6_LIGHT,
+ OUT7_LIGHT, OUT8_LIGHT, OUT9_LIGHT,
+ OUT10_LIGHT, OUT11_LIGHT, OUT12_LIGHT,
+ OUT13_LIGHT,
+ NUM_LIGHTS
+ };
+
+ Ranges() {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+ configParam(TOP_PARAM, -10.f, 10.f, 0.f, "Top");
+ configParam(BOTTOM_PARAM, -10.f, 10.f, 0.f, "Bottom");
+ configParam(TOP_ATT_PARAM, -1.f, 1.f, 0.f, "Top Attenuation");
+ configParam(BOTTOM_ATT_PARAM, -1.f, 1.f, 0.f, "Botom Attenuation");
+ configParam(DIVISIONS_PARAM, 0.f, 11.f, 1.f, "Divisions");
+
+
+ configInput(TOP_INPUT, "Top IN");
+ configInput(BOTTOM_INPUT, "Bottom IN");
+ configInput(DIVISIONS_INPUT, "Divisions IN");
+
+
+ // Initialize lights if needed
+ for (int i = 0; i < 13; ++i) {
+ configLight(OUT1_LIGHT + i, "Output Active Indicator");
+ }
+ }
+
+ void process(const ProcessArgs& args) override {
+ float start = params[TOP_PARAM].getValue() + params[TOP_ATT_PARAM].getValue() * inputs[TOP_INPUT].getVoltage();
+ float end = params[BOTTOM_PARAM].getValue() + params[BOTTOM_ATT_PARAM].getValue() * inputs[BOTTOM_INPUT].getVoltage();
+
+ // Clamp the start and end values
+ start = clamp(start, -10.f, 10.f);
+ end = clamp(end, -10.f, 10.f);
+
+ // Calculate and clamp divisions
+ int divisions = 1+static_cast(floor(params[DIVISIONS_PARAM].getValue() + 2.4 * inputs[DIVISIONS_INPUT].getVoltage()));
+ divisions = clamp(divisions, 0, 12);
+
+ // Calculate step size
+ float step = divisions > 0 ? (end - start) / (divisions) : 0.f;
+
+ // Set outputs and lights
+ for (int i = 0; i < 13; ++i) {
+ if (i < divisions+1) {
+ float voltage = start + step * i;
+ outputs[OUT1_OUTPUT + i].setVoltage(voltage);
+ lights[OUT1_LIGHT + i].setBrightness(1.f); // Active
+ } else {
+ outputs[OUT1_OUTPUT + i].setVoltage(0.f);
+ lights[OUT1_LIGHT + i].setBrightness(0.f); // Inactive
+ }
+ }
+ }
+};
+
+struct RangesWidget : ModuleWidget {
+ RangesWidget(Ranges* module) {
+ setModule(module);
+ setPanel(createPanel(
+ asset::plugin(pluginInstance, "res/Ranges.svg"),
+ asset::plugin(pluginInstance, "res/Ranges-dark.svg")
+ ));
+
+ box.size = Vec(8 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
+
+ // Left Section
+ addParam(createParam(mm2px(Vec(5, 12)), module, Ranges::TOP_PARAM));
+ addParam(createParam(mm2px(Vec(7, 24)), module, Ranges::TOP_ATT_PARAM));
+ addInput(createInput(mm2px(Vec(6, 32)), module, Ranges::TOP_INPUT));
+
+ addParam(createParam