From 66750affaa932572f94f9ca12346a5f908f9ef41 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:28:42 +0200 Subject: [PATCH 01/11] Fix getBankForIndex() bounds check Was clamping to programsPerBank (10) instead of numBanks (5). The banks array only has numBanks elements, so an out-of-range index would access beyond the array. Co-Authored-By: Claude Opus 4.6 (1M context) --- Banks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Banks.cpp b/Banks.cpp index 28cee21..202f1c3 100644 --- a/Banks.cpp +++ b/Banks.cpp @@ -113,6 +113,6 @@ static std::array banks { bank1, bank2, bank3, bank4, bank5 }; Bank& getBankForIndex(int i) { if (i < 0) i = 0; - if (i >= programsPerBank) i = (programsPerBank - 1); + if (i >= numBanks) i = (numBanks - 1); return banks[i]; } From 614c515067b9ca2557a1784d0ee9151d23307b44 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:29:17 +0200 Subject: [PATCH 02/11] Add AudioStreamCustom and bank registration for D, E, F MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AudioStreamCustom: base class for plugins with per-sample DSP that bypasses Teensy AudioConnection routing. Subclasses implement fillBlock() to generate 128 audio samples. Banks.hpp: numBanks 5 → 6 Banks_Def.hpp: add BANKS_DEF_4 (Resonant Bodies), BANKS_DEF_5 (Chaos Machines), BANKS_DEF_6 (Stochastic) Banks.cpp: add includes and bank6 to array Co-Authored-By: Claude Opus 4.6 (1M context) --- AudioStreamCustom.hpp | 29 +++++++++++++++++++++++++ Banks.cpp | 49 +++++++++++++++++++++++++++++++++---------- Banks.hpp | 2 +- Banks_Def.hpp | 44 +++++++++++++++++++++++++++++++------- 4 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 AudioStreamCustom.hpp diff --git a/AudioStreamCustom.hpp b/AudioStreamCustom.hpp new file mode 100644 index 0000000..b51d3d9 --- /dev/null +++ b/AudioStreamCustom.hpp @@ -0,0 +1,29 @@ +// AudioStreamCustom: Base class for plugins that implement per-sample DSP +// instead of using the Teensy Audio Library's AudioConnection graph. +// +// Subclasses implement fillBlock() to generate 128 samples of audio. +// The Teensy audio engine calls update() automatically at block rate, +// which allocates a block, calls fillBlock(), and transmits the result. + +#pragma once + +#include + +class AudioStreamCustom : public AudioStream { +public: + AudioStreamCustom() : AudioStream(0, NULL) {} + virtual ~AudioStreamCustom() {} + + // Subclass implements this: fill block->data[0..AUDIO_BLOCK_SAMPLES-1] + // with int16_t samples. Called once per audio block (~2.9ms at 44.1kHz). + virtual void fillBlock(audio_block_t *block) = 0; + + void update() override { + audio_block_t *block = allocate(); + if (block) { + fillBlock(block); + transmit(block); + release(block); + } + } +}; diff --git a/Banks.cpp b/Banks.cpp index 202f1c3..da2fcd0 100644 --- a/Banks.cpp +++ b/Banks.cpp @@ -84,8 +84,7 @@ int Bank::getSize() { #include "P_Rwalk_BitCrushPW.hpp" #include "P_Rwalk_LFree.hpp" -//Maybe Later: (Unused or incomplet) - +//Maybe Later: (Unused or incomplete) #include "P_Rwalk_SineFM.hpp" #include "P_VarWave.hpp" #include "P_RwalkVarWave.hpp" @@ -94,7 +93,41 @@ int Bank::getSize() { #include "P_Rwalk_LBit.hpp" #include "P_delayPonzo.hpp" - +//Bank D: Resonant Bodies +#include "P_CombNoise.hpp" +#include "P_PluckCloud.hpp" +#include "P_TubeResonance.hpp" +#include "P_FormantNoise.hpp" +#include "P_BowedMetal.hpp" +#include "P_FlangeNoise.hpp" +#include "P_NoiseBells.hpp" +#include "P_NoiseHarmonics.hpp" +#include "P_IceRain.hpp" +#include "P_DroneBody.hpp" + +//Bank E: Chaos Machines +#include "P_LogisticNoise.hpp" +#include "P_HenonDust.hpp" +#include "P_CellularSynth.hpp" +#include "P_StochasticPulse.hpp" +#include "P_BitShift.hpp" +#include "P_FeedbackFM.hpp" +#include "P_RunawayFilter.hpp" +#include "P_GlitchLoop.hpp" +#include "P_SubHarmonic.hpp" +#include "P_DualAttractor.hpp" + +//Bank F: Stochastic +#include "P_PulseWander.hpp" +#include "P_TwinPulse.hpp" +#include "P_QuantPulse.hpp" +#include "P_ShapedPulse.hpp" +#include "P_ShiftPulse.hpp" +#include "P_MetallicNoise.hpp" +#include "P_NoiseSlew.hpp" +#include "P_NoiseBurst.hpp" +#include "P_LFSRNoise.hpp" +#include "P_DualPulse.hpp" static const Bank bank1 BANKS_DEF_1; // Banks_Def.hpp @@ -102,14 +135,8 @@ static const Bank bank2 BANKS_DEF_2; static const Bank bank3 BANKS_DEF_3; static const Bank bank4 BANKS_DEF_4; static const Bank bank5 BANKS_DEF_5; -static std::array banks { bank1, bank2, bank3, bank4, bank5 }; - -// static const Bank bank6 BANKS_DEF_6; -// static const Bank bank7 BANKS_DEF_7; -// static const Bank bank8 BANKS_DEF_8; -// static const Bank bank9 BANKS_DEF_9; -// static const Bank bank10 BANKS_DEF_10; -// static std::array banks { bank1, bank2, bank3, bank4, bank5, bank6, bank7, bank8, bank9, bank10 }; +static const Bank bank6 BANKS_DEF_6; +static std::array banks { bank1, bank2, bank3, bank4, bank5, bank6 }; Bank& getBankForIndex(int i) { if (i < 0) i = 0; diff --git a/Banks.hpp b/Banks.hpp index d4eb4c3..071b31e 100644 --- a/Banks.hpp +++ b/Banks.hpp @@ -10,7 +10,7 @@ #include static const int programsPerBank = 10; -static const int numBanks = 5; +static const int numBanks = 6; struct Bank { diff --git a/Banks_Def.hpp b/Banks_Def.hpp index 136f187..047f3be 100644 --- a/Banks_Def.hpp +++ b/Banks_Def.hpp @@ -45,11 +45,41 @@ { "Rwalk_LFree", 1.0 } \ } -#define BANKS_DEF_4 -#define BANKS_DEF_5 +#define BANKS_DEF_4 { \ + { "CombNoise", 1.0 }, \ + { "PluckCloud", 1.0 }, \ + { "TubeResonance", 1.0 }, \ + { "FormantNoise", 1.0 }, \ + { "BowedMetal", 1.0 }, \ + { "FlangeNoise", 1.0 }, \ + { "NoiseBells", 0.8 }, \ + { "NoiseHarmonics", 1.0 }, \ + { "IceRain", 1.0 }, \ + { "DroneBody", 1.0 } \ +} + +#define BANKS_DEF_5 { \ + { "LogisticNoise", 1.0 }, \ + { "HenonDust", 1.0 }, \ + { "CellularSynth", 1.0 }, \ + { "StochasticPulse", 1.0 }, \ + { "BitShift", 1.0 }, \ + { "FeedbackFM", 1.0 }, \ + { "RunawayFilter", 1.0 }, \ + { "GlitchLoop", 1.0 }, \ + { "SubHarmonic", 1.0 }, \ + { "DualAttractor", 1.0 } \ +} -// #define BANKS_DEF_6 -// #define BANKS_DEF_7 -// #define BANKS_DEF_8 -// #define BANKS_DEF_9 -// #define BANKS_DEF_10 +#define BANKS_DEF_6 { \ + { "PulseWander", 1.0 }, \ + { "TwinPulse", 1.0 }, \ + { "QuantPulse", 1.0 }, \ + { "ShapedPulse", 1.0 }, \ + { "ShiftPulse", 1.0 }, \ + { "MetallicNoise", 1.0 }, \ + { "NoiseSlew", 1.0 }, \ + { "NoiseBurst", 1.0 }, \ + { "LFSRNoise", 1.0 }, \ + { "DualPulse", 1.0 } \ +} From ba01a5347289391d95a9f3b643b6194be3b69540 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:29:36 +0200 Subject: [PATCH 03/11] Add 8 plugins using native Teensy audio objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These use standard AudioConnection routing (no custom DSP): - FormantNoise: noise → 3 SVF bandpass → mixer (vowel formants) - IceRain: S&H → freeverb → mixer (ambient droplets) - NoiseHarmonics: noise + DC → wavefolder → SVF lowpass - DroneBody: 2 cross-FM sines → mixer → wavefolder - FlangeNoise: pink noise → flanger - BitShift: 2 sawtooths → XOR digital combine - RunawayFilter: noise → SVF bandpass at extreme Q - MetallicNoise: 6 square oscs → 3 mixers → SVF highpass Co-Authored-By: Claude Opus 4.6 (1M context) --- P_BitShift.hpp | 54 +++++++++++++++++++++++ P_DroneBody.hpp | 73 ++++++++++++++++++++++++++++++ P_FlangeNoise.hpp | 57 ++++++++++++++++++++++++ P_FormantNoise.hpp | 103 +++++++++++++++++++++++++++++++++++++++++++ P_IceRain.hpp | 64 +++++++++++++++++++++++++++ P_MetallicNoise.hpp | 92 ++++++++++++++++++++++++++++++++++++++ P_NoiseHarmonics.hpp | 60 +++++++++++++++++++++++++ P_RunawayFilter.hpp | 53 ++++++++++++++++++++++ 8 files changed, 556 insertions(+) create mode 100644 P_BitShift.hpp create mode 100644 P_DroneBody.hpp create mode 100644 P_FlangeNoise.hpp create mode 100644 P_FormantNoise.hpp create mode 100644 P_IceRain.hpp create mode 100644 P_MetallicNoise.hpp create mode 100644 P_NoiseHarmonics.hpp create mode 100644 P_RunawayFilter.hpp diff --git a/P_BitShift.hpp b/P_BitShift.hpp new file mode 100644 index 0000000..53450f1 --- /dev/null +++ b/P_BitShift.hpp @@ -0,0 +1,54 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class BitShift : public Plugin { + +public: + + BitShift() + : patchCord1(waveform1, 0, combine1, 0) + , patchCord2(waveform2, 0, combine1, 1) + { } + + ~BitShift() override {} + + BitShift(const BitShift&) = delete; + BitShift& operator=(const BitShift&) = delete; + + void init() override { + // sawtooths produce diverse bit patterns -- XOR creates rich digital artifacts + // (square waves only have 2 values, XOR of +/-32767 produces near-silence) + waveform1.begin(1.0f, 100, WAVEFORM_SAWTOOTH); + waveform2.begin(1.0f, 200, WAVEFORM_SAWTOOTH); + combine1.setCombineMode(1); // XOR + } + + void process(float k1, float k2) override { + float freq1 = 20.0f + pow(k1, 2) * 5000.0f; + float freq2 = freq1 * (1.0f + k2 * 7.0f); + waveform1.frequency(freq1); + waveform2.frequency(freq2); + } + + AudioStream& getStream() override { + return combine1; + } + unsigned char getPort() override { + return 0; + } + +private: + AudioSynthWaveformModulated waveform1; + AudioSynthWaveformModulated waveform2; + AudioEffectDigitalCombine combine1; + AudioConnection patchCord1; + AudioConnection patchCord2; +}; + +REGISTER_PLUGIN(BitShift); diff --git a/P_DroneBody.hpp b/P_DroneBody.hpp new file mode 100644 index 0000000..fe93e65 --- /dev/null +++ b/P_DroneBody.hpp @@ -0,0 +1,73 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class DroneBody : public Plugin { + +public: + + DroneBody() + : patchCord1(sine_fm2, 0, sine_fm1, 0) + , patchCord2(sine_fm1, 0, sine_fm2, 0) + , patchCord3(sine_fm1, 0, mixer1, 0) + , patchCord4(sine_fm2, 0, mixer1, 1) + , patchCord5(mixer1, 0, wavefolder1, 0) + , patchCord6(dc1, 0, wavefolder1, 1) + { } + + ~DroneBody() override {} + + // delete copy constructors + DroneBody(const DroneBody&) = delete; + DroneBody& operator=(const DroneBody&) = delete; + + void init() override { + sine_fm1.frequency(100); + sine_fm1.amplitude(1.0); + + sine_fm2.frequency(101); + sine_fm2.amplitude(1.0); + + dc1.amplitude(0.3); + + mixer1.gain(0, 0.5); + mixer1.gain(1, 0.5); + } + + void process(float k1, float k2) override { + float freq = 20.0f + pow(k1, 2) * 500.0f; + float detune_ratio = 1.0f + k2 * 0.05f; + float dc_fold = 0.1f + k2 * 0.5f; + + sine_fm1.frequency(freq); + sine_fm2.frequency(freq * detune_ratio); + dc1.amplitude(dc_fold); + } + + AudioStream& getStream() override { + return wavefolder1; + } + unsigned char getPort() override { + return 0; + } + +private: + AudioSynthWaveformSineModulated sine_fm1; + AudioSynthWaveformSineModulated sine_fm2; + AudioSynthWaveformDc dc1; + AudioEffectWaveFolder wavefolder1; + AudioMixer4 mixer1; + AudioConnection patchCord1; + AudioConnection patchCord2; + AudioConnection patchCord3; + AudioConnection patchCord4; + AudioConnection patchCord5; + AudioConnection patchCord6; +}; + +REGISTER_PLUGIN(DroneBody); diff --git a/P_FlangeNoise.hpp b/P_FlangeNoise.hpp new file mode 100644 index 0000000..ce1f5fe --- /dev/null +++ b/P_FlangeNoise.hpp @@ -0,0 +1,57 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +#define FLANGE_NOISE_DELAY_LENGTH (8 * AUDIO_BLOCK_SAMPLES) + +class FlangeNoise : public Plugin { + +public: + + FlangeNoise() + : patchCord1(pink1, flange1) + { } + + ~FlangeNoise() override {} + + // delete copy constructors + FlangeNoise(const FlangeNoise&) = delete; + FlangeNoise& operator=(const FlangeNoise&) = delete; + + void init() override { + pink1.amplitude(1.0); + flange1.begin(flangeDelayLine, FLANGE_NOISE_DELAY_LENGTH, s_offset, s_depth, s_rate); + } + + void process(float k1, float k2) override { + float rate = 0.05f + pow(k1, 2) * 5.0f; + int depth = (int)(2 + k2 * 120); // wider sweep range for audible flanging + int offset = (int)(2 + k2 * 80); + + flange1.voices(offset, depth, rate); + } + + AudioStream& getStream() override { + return flange1; + } + unsigned char getPort() override { + return 0; + } + +private: + AudioSynthNoisePink pink1; + AudioEffectFlange flange1; + AudioConnection patchCord1; + + short flangeDelayLine[FLANGE_NOISE_DELAY_LENGTH]; + int s_offset = 2 * FLANGE_NOISE_DELAY_LENGTH / 4; + int s_depth = FLANGE_NOISE_DELAY_LENGTH / 4; + float s_rate = 0.5; +}; + +REGISTER_PLUGIN(FlangeNoise); diff --git a/P_FormantNoise.hpp b/P_FormantNoise.hpp new file mode 100644 index 0000000..ea6259e --- /dev/null +++ b/P_FormantNoise.hpp @@ -0,0 +1,103 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class FormantNoise : public Plugin { + +public: + + FormantNoise() + : patchCord1(noise1, 0, filter1, 0) + , patchCord2(noise1, 0, filter2, 0) + , patchCord3(noise1, 0, filter3, 0) + , patchCord4(filter1, 1, mixer1, 0) + , patchCord5(filter2, 1, mixer1, 1) + , patchCord6(filter3, 1, mixer1, 2) + { } + + ~FormantNoise() override {} + + // delete copy constructors + FormantNoise(const FormantNoise&) = delete; + FormantNoise& operator=(const FormantNoise&) = delete; + + void init() override { + noise1.amplitude(1); + + filter1.resonance(2.0f); + filter1.octaveControl(0); + filter2.resonance(2.0f); + filter2.octaveControl(0); + filter3.resonance(2.0f); + filter3.octaveControl(0); + + mixer1.gain(0, 0.5f); + mixer1.gain(1, 0.5f); + mixer1.gain(2, 0.5f); + mixer1.gain(3, 0); + } + + void process(float k1, float k2) override { + // Vowel formant table (Hz): + // A=[730,1090,2440], E=[660,1720,2410], I=[270,2290,3010], + // O=[570,840,2410], U=[300,870,2240] + static const float formants[5][3] = { + {730.0f, 1090.0f, 2440.0f}, // A + {660.0f, 1720.0f, 2410.0f}, // E + {270.0f, 2290.0f, 3010.0f}, // I + {570.0f, 840.0f, 2410.0f}, // O + {300.0f, 870.0f, 2240.0f} // U + }; + + // continuous vowel morph: 0=A, 0.25=E, 0.5=I, 0.75=O, 1.0=U + float pos = k1 * 4.0f; // 0..4 + int idx = (int)pos; + if (idx < 0) idx = 0; + if (idx > 3) idx = 3; + float frac = pos - (float)idx; + if (frac < 0.0f) frac = 0.0f; + if (frac > 1.0f) frac = 1.0f; + + // interpolate F1, F2, F3 between adjacent vowels + float f1 = formants[idx][0] + frac * (formants[idx + 1][0] - formants[idx][0]); + float f2 = formants[idx][1] + frac * (formants[idx + 1][1] - formants[idx][1]); + float f3 = formants[idx][2] + frac * (formants[idx + 1][2] - formants[idx][2]); + + filter1.frequency(f1); + filter2.frequency(f2); + filter3.frequency(f3); + + // resonance: q = 1.0 + k2 * 4.0 (range 1-5) + float q = 1.0f + k2 * 4.0f; + filter1.resonance(q); + filter2.resonance(q); + filter3.resonance(q); + } + + AudioStream& getStream() override { + return mixer1; + } + unsigned char getPort() override { + return 0; + } + +private: + AudioSynthNoiseWhite noise1; + AudioFilterStateVariable filter1; + AudioFilterStateVariable filter2; + AudioFilterStateVariable filter3; + AudioMixer4 mixer1; + AudioConnection patchCord1; + AudioConnection patchCord2; + AudioConnection patchCord3; + AudioConnection patchCord4; + AudioConnection patchCord5; + AudioConnection patchCord6; +}; + +REGISTER_PLUGIN(FormantNoise); diff --git a/P_IceRain.hpp b/P_IceRain.hpp new file mode 100644 index 0000000..4c2884f --- /dev/null +++ b/P_IceRain.hpp @@ -0,0 +1,64 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class IceRain : public Plugin { + +public: + + IceRain() + : patchCord1(waveformMod1, 0, mixer1, 0) + , patchCord2(waveformMod1, freeverb1) + , patchCord3(freeverb1, 0, mixer1, 1) + { } + + ~IceRain() override {} + + // delete copy constructors + IceRain(const IceRain&) = delete; + IceRain& operator=(const IceRain&) = delete; + + void init() override { + waveformMod1.begin(1.0, 5, WAVEFORM_SAMPLE_HOLD); + waveformMod1.frequencyModulation(10); + + freeverb1.roomsize(0.8); + freeverb1.damping(0.8); + + mixer1.gain(0, 0.3); + mixer1.gain(1, 1.0); + } + + void process(float k1, float k2) override { + float freq = 0.5f + pow(k1, 2) * 200.0f; + float roomsize = 0.3f + k2 * 0.7f; + + waveformMod1.frequency(freq); + freeverb1.roomsize(roomsize); + + mixer1.gain(0, 0.3f * (1.0f - k2)); + mixer1.gain(1, k2 * 2.0f); + } + + AudioStream& getStream() override { + return mixer1; + } + unsigned char getPort() override { + return 0; + } + +private: + AudioSynthWaveformModulated waveformMod1; + AudioEffectFreeverb freeverb1; + AudioMixer4 mixer1; + AudioConnection patchCord1; + AudioConnection patchCord2; + AudioConnection patchCord3; +}; + +REGISTER_PLUGIN(IceRain); diff --git a/P_MetallicNoise.hpp b/P_MetallicNoise.hpp new file mode 100644 index 0000000..d2bdaf7 --- /dev/null +++ b/P_MetallicNoise.hpp @@ -0,0 +1,92 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class MetallicNoise : public Plugin { + +public: + + MetallicNoise() + : patchCord1(osc[0], 0, mixer1, 0) + , patchCord2(osc[1], 0, mixer1, 1) + , patchCord3(osc[2], 0, mixer1, 2) + , patchCord4(osc[3], 0, mixer1, 3) + , patchCord5(osc[4], 0, mixer2, 0) + , patchCord6(osc[5], 0, mixer2, 1) + , patchCord7(mixer1, 0, mixer3, 0) + , patchCord8(mixer2, 0, mixer3, 1) + , patchCord9(mixer3, 0, filter1, 0) + { } + + ~MetallicNoise() override {} + + MetallicNoise(const MetallicNoise&) = delete; + MetallicNoise& operator=(const MetallicNoise&) = delete; + + void init() override { + for (int i = 0; i < 6; i++) { + osc[i].begin(0.15f, baseFreqs[i], WAVEFORM_SQUARE); // 6*0.15=0.9 max sum, no clipping + } + + mixer1.gain(0, 1.0f); + mixer1.gain(1, 1.0f); + mixer1.gain(2, 1.0f); + mixer1.gain(3, 1.0f); + + mixer2.gain(0, 1.0f); + mixer2.gain(1, 1.0f); + mixer2.gain(2, 0.0f); + mixer2.gain(3, 0.0f); + + mixer3.gain(0, 1.0f); + mixer3.gain(1, 1.0f); + mixer3.gain(2, 0.0f); + mixer3.gain(3, 0.0f); + + filter1.frequency(5000); + filter1.resonance(0.7f); + filter1.octaveControl(2.0f); + } + + void process(float k1, float k2) override { + float pitchMult = 0.5f + k1 * 2.0f; + for (int i = 0; i < 6; i++) { + osc[i].frequency(baseFreqs[i] * pitchMult); + } + + float filterFreq = 500.0f + pow(k2, 2) * 15000.0f; + filter1.frequency(filterFreq); + } + + AudioStream& getStream() override { + return filter1; + } + unsigned char getPort() override { + return 2; + } + +private: + static constexpr float baseFreqs[6] = {205.3f, 304.4f, 369.6f, 522.7f, 800.6f, 1053.4f}; + + AudioSynthWaveformModulated osc[6]; + AudioMixer4 mixer1; + AudioMixer4 mixer2; + AudioMixer4 mixer3; + AudioFilterStateVariable filter1; + AudioConnection patchCord1; + AudioConnection patchCord2; + AudioConnection patchCord3; + AudioConnection patchCord4; + AudioConnection patchCord5; + AudioConnection patchCord6; + AudioConnection patchCord7; + AudioConnection patchCord8; + AudioConnection patchCord9; +}; + +REGISTER_PLUGIN(MetallicNoise); diff --git a/P_NoiseHarmonics.hpp b/P_NoiseHarmonics.hpp new file mode 100644 index 0000000..3a053af --- /dev/null +++ b/P_NoiseHarmonics.hpp @@ -0,0 +1,60 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class NoiseHarmonics : public Plugin { + +public: + + NoiseHarmonics() + : patchCord1(noise1, 0, wavefolder1, 0) + , patchCord2(dc1, 0, wavefolder1, 1) + , patchCord3(wavefolder1, 0, filter1, 0) + { } + + ~NoiseHarmonics() override {} + + // delete copy constructors + NoiseHarmonics(const NoiseHarmonics&) = delete; + NoiseHarmonics& operator=(const NoiseHarmonics&) = delete; + + void init() override { + noise1.amplitude(1.0); + dc1.amplitude(0.5); + + filter1.frequency(1000); + filter1.resonance(1.5); // lower Q = wider, grittier character + filter1.octaveControl(2.0); + } + + void process(float k1, float k2) override { + float dcAmp = 0.1f + pow(k1, 2) * 1.5f; // wider range, more aggressive folding + float filterFreq = 60.0f + pow(k2, 2) * 8000.0f; + + dc1.amplitude(dcAmp); + filter1.frequency(filterFreq); + } + + AudioStream& getStream() override { + return filter1; + } + unsigned char getPort() override { + return 0; + } + +private: + AudioSynthNoiseWhite noise1; + AudioSynthWaveformDc dc1; + AudioEffectWaveFolder wavefolder1; + AudioFilterStateVariable filter1; + AudioConnection patchCord1; + AudioConnection patchCord2; + AudioConnection patchCord3; +}; + +REGISTER_PLUGIN(NoiseHarmonics); diff --git a/P_RunawayFilter.hpp b/P_RunawayFilter.hpp new file mode 100644 index 0000000..3c0924c --- /dev/null +++ b/P_RunawayFilter.hpp @@ -0,0 +1,53 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" + +class RunawayFilter : public Plugin { + +public: + + RunawayFilter() + : patchCord1(noise1, 0, filter1, 0) + { } + + ~RunawayFilter() override {} + + RunawayFilter(const RunawayFilter&) = delete; + RunawayFilter& operator=(const RunawayFilter&) = delete; + + void init() override { + noise1.amplitude(0.5f); + filter1.frequency(1000.0f); + filter1.resonance(4.8f); + filter1.octaveControl(3.0f); + } + + void process(float k1, float k2) override { + float freq = 30.0f + pow(k1, 2.0f) * 10000.0f; + float noiseAmp = 0.01f + k2 * 0.99f; + float resonance = 4.5f + (1.0f - k2) * 0.49f; + + noise1.amplitude(noiseAmp); + filter1.frequency(freq); + filter1.resonance(resonance); + } + + AudioStream& getStream() override { + return filter1; + } + unsigned char getPort() override { + return 1; + } + +private: + AudioSynthNoiseWhite noise1; + AudioFilterStateVariable filter1; + AudioConnection patchCord1; +}; + +REGISTER_PLUGIN(RunawayFilter); From 32c67fd36f7c54aa00889b7147edcc5964aab020 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:29:52 +0200 Subject: [PATCH 04/11] Add 22 plugins using AudioStreamCustom (per-sample DSP) Bank D custom DSP: - CombNoise, PluckCloud, TubeResonance (comb filter / delay-based) - BowedMetal, NoiseBells (high-Q 2-pole resonators) Bank E custom DSP: - LogisticNoise, HenonDust (mathematical chaos) - CellularSynth (CA + 8 sawtooth phase accumulators) - StochasticPulse (random impulses + inline SVF) - FeedbackFM (self-feedback sine) - GlitchLoop (buffer capture + bit degradation) - SubHarmonic (square + frequency dividers) - DualAttractor (coupled Lorenz + sine oscillators) Bank F custom DSP: - PulseWander, TwinPulse, QuantPulse, ShapedPulse, ShiftPulse - NoiseSlew, NoiseBurst (inline noise generation) - LFSRNoise (16-bit LFSR with selectable taps) - DualPulse (dual S&H) Co-Authored-By: Claude Opus 4.6 (1M context) --- P_BowedMetal.hpp | 129 ++++++++++++++++++++++++++++++++++++ P_CellularSynth.hpp | 127 ++++++++++++++++++++++++++++++++++++ P_CombNoise.hpp | 94 +++++++++++++++++++++++++++ P_DualAttractor.hpp | 147 ++++++++++++++++++++++++++++++++++++++++++ P_DualPulse.hpp | 84 ++++++++++++++++++++++++ P_FeedbackFM.hpp | 66 +++++++++++++++++++ P_GlitchLoop.hpp | 104 ++++++++++++++++++++++++++++++ P_HenonDust.hpp | 75 +++++++++++++++++++++ P_LFSRNoise.hpp | 85 ++++++++++++++++++++++++ P_LogisticNoise.hpp | 71 ++++++++++++++++++++ P_NoiseBells.hpp | 123 +++++++++++++++++++++++++++++++++++ P_NoiseBurst.hpp | 84 ++++++++++++++++++++++++ P_NoiseSlew.hpp | 67 +++++++++++++++++++ P_PluckCloud.hpp | 116 +++++++++++++++++++++++++++++++++ P_PulseWander.hpp | 82 +++++++++++++++++++++++ P_QuantPulse.hpp | 83 ++++++++++++++++++++++++ P_ShapedPulse.hpp | 98 ++++++++++++++++++++++++++++ P_ShiftPulse.hpp | 108 +++++++++++++++++++++++++++++++ P_StochasticPulse.hpp | 91 ++++++++++++++++++++++++++ P_SubHarmonic.hpp | 109 +++++++++++++++++++++++++++++++ P_TubeResonance.hpp | 100 ++++++++++++++++++++++++++++ P_TwinPulse.hpp | 102 +++++++++++++++++++++++++++++ 22 files changed, 2145 insertions(+) create mode 100644 P_BowedMetal.hpp create mode 100644 P_CellularSynth.hpp create mode 100644 P_CombNoise.hpp create mode 100644 P_DualAttractor.hpp create mode 100644 P_DualPulse.hpp create mode 100644 P_FeedbackFM.hpp create mode 100644 P_GlitchLoop.hpp create mode 100644 P_HenonDust.hpp create mode 100644 P_LFSRNoise.hpp create mode 100644 P_LogisticNoise.hpp create mode 100644 P_NoiseBells.hpp create mode 100644 P_NoiseBurst.hpp create mode 100644 P_NoiseSlew.hpp create mode 100644 P_PluckCloud.hpp create mode 100644 P_PulseWander.hpp create mode 100644 P_QuantPulse.hpp create mode 100644 P_ShapedPulse.hpp create mode 100644 P_ShiftPulse.hpp create mode 100644 P_StochasticPulse.hpp create mode 100644 P_SubHarmonic.hpp create mode 100644 P_TubeResonance.hpp create mode 100644 P_TwinPulse.hpp diff --git a/P_BowedMetal.hpp b/P_BowedMetal.hpp new file mode 100644 index 0000000..2cab079 --- /dev/null +++ b/P_BowedMetal.hpp @@ -0,0 +1,129 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class BowedMetal : public Plugin { + +public: + + BowedMetal() {} + + ~BowedMetal() override {} + + BowedMetal(const BowedMetal&) = delete; + BowedMetal& operator=(const BowedMetal&) = delete; + + void init() override { + customStream.noiseLevel = 0.5f; + + for (int i = 0; i < NUM_RES; i++) { + customStream.y1[i] = 0.0f; + customStream.y2[i] = 0.0f; + } + + updateResonators(200.0f, 0.5f, 0.5f); + } + + void process(float k1, float k2) override { + float freq = 30.0f + pow(k1, 2) * 2000.0f; + + // k2 low = struck (short ring, louder noise burst) + // k2 high = bowed (long ring, quieter continuous noise) + float ringTime = 0.9985f + k2 * 0.0013f; + float noiseLevel = 0.4f - k2 * 0.32f; + customStream.noiseLevel = noiseLevel; + + updateResonators(freq, k2, ringTime); + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + static const int NUM_RES = 8; + + // Metal plate / cymbal mode ratios + static constexpr float modeRatios[NUM_RES] = { + 1.000f, 1.183f, 1.506f, 1.741f, + 2.098f, 2.534f, 2.917f, 3.483f + }; + + void updateResonators(float fundamental, float k2, float ringTime) { + for (int i = 0; i < NUM_RES; i++) { + float f = fundamental * modeRatios[i]; + if (f > 19000.0f) f = 19000.0f; + if (f < 20.0f) f = 20.0f; + + // higher modes decay faster + float r = ringTime - i * 0.0002f; + if (r < 0.99f) r = 0.99f; + + float w = 2.0f * M_PI * f / 44100.0f; + + customStream.coeff_a[i] = 2.0f * r * cos(w); + customStream.coeff_b[i] = r * r; + customStream.coeff_g[i] = 1.0f - r; + } + + // dense amplitude distribution + customStream.gains[0] = 1.0f; + customStream.gains[1] = 0.9f; + customStream.gains[2] = 0.85f; + customStream.gains[3] = 0.75f; + customStream.gains[4] = 0.65f; + customStream.gains[5] = 0.55f; + customStream.gains[6] = 0.45f; + customStream.gains[7] = 0.35f; + } + + class Stream : public AudioStreamCustom { + public: + static const int NUM_RES = 8; + + float y1[NUM_RES] = {}; + float y2[NUM_RES] = {}; + float coeff_a[NUM_RES] = {}; + float coeff_b[NUM_RES] = {}; + float coeff_g[NUM_RES] = {}; + float gains[NUM_RES] = {}; + float noiseLevel = 0.5f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + float input = (random(-32768, 32768) / 32768.0f) * noiseLevel; + + float mixed = 0.0f; + + for (int r = 0; r < NUM_RES; r++) { + float out = coeff_a[r] * y1[r] - coeff_b[r] * y2[r] + coeff_g[r] * input; + + y2[r] = y1[r]; + y1[r] = out; + + if (isnan(out) || isinf(out)) { + y1[r] = 0.0f; + y2[r] = 0.0f; + out = 0.0f; + } + + mixed += out * gains[r]; + } + + mixed *= 0.1f; + + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(BowedMetal); diff --git a/P_CellularSynth.hpp b/P_CellularSynth.hpp new file mode 100644 index 0000000..3d6bd80 --- /dev/null +++ b/P_CellularSynth.hpp @@ -0,0 +1,127 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class CellularSynth : public Plugin { + +public: + + CellularSynth() {} + + ~CellularSynth() override {} + + CellularSynth(const CellularSynth&) = delete; + CellularSynth& operator=(const CellularSynth&) = delete; + + void init() override { + // Initialize CA with single cell in center + for (int i = 0; i < 32; i++) { + cells[i] = false; + } + cells[16] = true; + + clockCounter = 0; + clockSamples = 128; + rule = 110; + + float fundamental = 55.0f; + + for (int i = 0; i < 8; i++) { + customStream.oscPhase[i] = 0.0f; + customStream.oscFreq[i] = fundamental * (i + 1); + customStream.oscAmp[i] = 0.2f; + } + + // Set initial amplitudes from CA state + updateOscAmplitudes(); + } + + void process(float k1, float k2) override { + rule = (int)(k1 * 255.0f); + clockSamples = (int)(128 + (1.0f - k2) * 128.0f * 50.0f); + + clockCounter += AUDIO_BLOCK_SAMPLES; + if (clockCounter >= clockSamples) { + evolveCA(); + updateOscAmplitudes(); + clockCounter = 0; + } + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + void evolveCA() { + bool newCells[32]; + for (int i = 0; i < 32; i++) { + int left = (i - 1 + 32) % 32; + int right = (i + 1) % 32; + + int pattern = (cells[left] ? 4 : 0) + | (cells[i] ? 2 : 0) + | (cells[right] ? 1 : 0); + + newCells[i] = ((rule >> pattern) & 1) != 0; + } + for (int i = 0; i < 32; i++) { + cells[i] = newCells[i]; + } + } + + void updateOscAmplitudes() { + // 32 cells grouped into 8 groups of 4 + for (int g = 0; g < 8; g++) { + int count = 0; + for (int c = 0; c < 4; c++) { + if (cells[g * 4 + c]) count++; + } + float amp = count * 0.25f; // 0, 0.25, 0.5, 0.75, 1.0 + customStream.oscAmp[g] = amp * 0.2f; // master volume scaling + } + } + + bool cells[32] = {}; + int clockCounter = 0; + int clockSamples = 128; + int rule = 110; + + class Stream : public AudioStreamCustom { + public: + float oscPhase[8] = {}; + float oscFreq[8] = {}; + float oscAmp[8] = {}; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + float mixed = 0.0f; + + for (int o = 0; o < 8; o++) { + // Sawtooth oscillator: phase accumulator + oscPhase[o] += oscFreq[o] / 44100.0f; + if (oscPhase[o] >= 1.0f) oscPhase[o] -= 1.0f; + + // Sawtooth: -1 to +1 + float saw = oscPhase[o] * 2.0f - 1.0f; + mixed += saw * oscAmp[o]; + } + + // Master mix scaling (equivalent to mixer gains 0.7 * 0.5) + mixed *= 0.7f; + + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(CellularSynth); diff --git a/P_CombNoise.hpp b/P_CombNoise.hpp new file mode 100644 index 0000000..4f399a6 --- /dev/null +++ b/P_CombNoise.hpp @@ -0,0 +1,94 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class CombNoise : public Plugin { + +public: + + CombNoise() {} + + ~CombNoise() override {} + + CombNoise(const CombNoise&) = delete; + CombNoise& operator=(const CombNoise&) = delete; + + void init() override { + for (int c = 0; c < 4; c++) { + customStream.writePos[c] = 0; + for (int i = 0; i < 2048; i++) { + customStream.delayBuffer[c][i] = 0.0f; + } + } + } + + void process(float k1, float k2) override { + float delayInSamples = 44100.0f / (40.0f + pow(k1, 2) * 4960.0f); + customStream.feedback = k2 * 0.95f; + + // 4 combs at delay ratios 1.0, 0.7, 0.5, 0.35 + customStream.delaySamples[0] = delayInSamples * 1.0f; + customStream.delaySamples[1] = delayInSamples * 0.7f; + customStream.delaySamples[2] = delayInSamples * 0.5f; + customStream.delaySamples[3] = delayInSamples * 0.35f; + + // clamp delay lengths to valid buffer range + for (int c = 0; c < 4; c++) { + if (customStream.delaySamples[c] < 1.0f) customStream.delaySamples[c] = 1.0f; + if (customStream.delaySamples[c] > 2047.0f) customStream.delaySamples[c] = 2047.0f; + } + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float delayBuffer[4][2048] = {}; + int writePos[4] = {}; + float delaySamples[4] = {100.0f, 70.0f, 50.0f, 35.0f}; + float feedback = 0.0f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Generate white noise input + float input = random(-32768, 32768) / 32768.0f; + float mixed = 0.0f; + + for (int c = 0; c < 4; c++) { + int delay = (int)delaySamples[c]; + if (delay < 1) delay = 1; + if (delay > 2047) delay = 2047; + + int readPos = writePos[c] - delay; + if (readPos < 0) readPos += 2048; + + float out = input + feedback * delayBuffer[c][readPos]; + delayBuffer[c][writePos[c]] = out; + + writePos[c] = (writePos[c] + 1) & 2047; // mod 2048 + + mixed += out; + } + + // mix 4 combs equally (scale by 0.25) + mixed *= 0.25f; + + // clamp and convert to int16 + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(CombNoise); diff --git a/P_DualAttractor.hpp b/P_DualAttractor.hpp new file mode 100644 index 0000000..94c378e --- /dev/null +++ b/P_DualAttractor.hpp @@ -0,0 +1,147 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class DualAttractor : public Plugin { + +public: + + DualAttractor() {} + + ~DualAttractor() override {} + + DualAttractor(const DualAttractor&) = delete; + DualAttractor& operator=(const DualAttractor&) = delete; + + void init() override { + // Lorenz system 1 + x1 = 1.0f; + y1 = 1.0f; + z1 = 1.0f; + + // Lorenz system 2 + x2 = -1.0f; + y2 = 0.0f; + z2 = 2.0f; + + coupling = 0.0f; + baseFreq = 100.0f; + + customStream.sine1Freq = 100.0f; + customStream.sine2Freq = 150.0f; + customStream.sine1Phase = 0.0f; + customStream.sine2Phase = 0.0f; + } + + void process(float k1, float k2) override { + coupling = k1 * 5.0f; + baseFreq = 30.0f + pow(k2, 2.0f) * 2000.0f; + + // Lorenz parameters + const float sigma = 10.0f; + const float rho = 28.0f; + const float beta = 8.0f / 3.0f; + const float dt = 0.001f; + + // Run 10 integration steps for stability + for (int step = 0; step < 10; step++) { + float dx1 = sigma * (y1 - x1) + coupling * (x2 - x1); + float dy1 = x1 * (rho - z1) - y1; + float dz1 = x1 * y1 - beta * z1; + + float dx2 = sigma * (y2 - x2) + coupling * (x1 - x2); + float dy2 = x2 * (rho - z2) - y2; + float dz2 = x2 * y2 - beta * z2; + + x1 += dx1 * dt; + y1 += dy1 * dt; + z1 += dz1 * dt; + + x2 += dx2 * dt; + y2 += dy2 * dt; + z2 += dz2 * dt; + } + + // Check for NaN/infinity and reset if needed + if (isnan(x1) || isinf(x1) || isnan(y1) || isinf(y1) || isnan(z1) || isinf(z1)) { + x1 = 1.0f; + y1 = 1.0f; + z1 = 1.0f; + } + if (isnan(x2) || isinf(x2) || isnan(y2) || isinf(y2) || isnan(z2) || isinf(z2)) { + x2 = -1.0f; + y2 = 0.0f; + z2 = 2.0f; + } + + // Map Lorenz x outputs to frequency offsets + float freq1 = baseFreq + x1 * baseFreq * 0.4f; + float freq2 = baseFreq * 1.5f + x2 * baseFreq * 0.4f; + + // Clamp frequencies to reasonable range + if (freq1 < 20.0f) freq1 = 20.0f; + if (freq1 > 20000.0f) freq1 = 20000.0f; + if (freq2 < 20.0f) freq2 = 20.0f; + if (freq2 > 20000.0f) freq2 = 20000.0f; + + customStream.sine1Freq = freq1; + customStream.sine2Freq = freq2; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + // Lorenz system 1 + float x1 = 1.0f; + float y1 = 1.0f; + float z1 = 1.0f; + + // Lorenz system 2 + float x2 = -1.0f; + float y2 = 0.0f; + float z2 = 2.0f; + + float coupling = 0.0f; + float baseFreq = 100.0f; + + class Stream : public AudioStreamCustom { + public: + float sine1Freq = 100.0f; + float sine2Freq = 150.0f; + float sine1Phase = 0.0f; + float sine2Phase = 0.0f; + + void fillBlock(audio_block_t *block) override { + const float twoPi = 2.0f * 3.14159265358979323846f; + + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Sine oscillator 1 + sine1Phase += twoPi * sine1Freq / 44100.0f; + if (sine1Phase >= twoPi) sine1Phase -= twoPi; + float s1 = sin(sine1Phase) * 0.7f; + + // Sine oscillator 2 + sine2Phase += twoPi * sine2Freq / 44100.0f; + if (sine2Phase >= twoPi) sine2Phase -= twoPi; + float s2 = sin(sine2Phase) * 0.7f; + + // Mix both sines + float mixed = s1 + s2; + + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(DualAttractor); diff --git a/P_DualPulse.hpp b/P_DualPulse.hpp new file mode 100644 index 0000000..c5c8a7a --- /dev/null +++ b/P_DualPulse.hpp @@ -0,0 +1,84 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class DualPulse : public Plugin { + +public: + + DualPulse() {} + + ~DualPulse() override {} + + DualPulse(const DualPulse&) = delete; + DualPulse& operator=(const DualPulse&) = delete; + + void init() override { + customStream.value1 = 0.0f; + customStream.value2 = 0.0f; + customStream.counter1 = 0; + customStream.counter2 = 0; + customStream.samplesPerClock1 = 441; + customStream.samplesPerClock2 = 441; + } + + void process(float k1, float k2) override { + float rate1 = 5.0f + pow(k1, 2) * 495.0f; + float rate2 = 5.0f + pow(k2, 2) * 495.0f; + int spc1 = (int)(44100.0f / rate1); + if (spc1 < 1) spc1 = 1; + int spc2 = (int)(44100.0f / rate2); + if (spc2 < 1) spc2 = 1; + customStream.samplesPerClock1 = spc1; + customStream.samplesPerClock2 = spc2; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float value1 = 0.0f; + float value2 = 0.0f; + int counter1 = 0; + int counter2 = 0; + int samplesPerClock1 = 441; + int samplesPerClock2 = 441; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + counter1++; + if (counter1 >= samplesPerClock1) { + value1 = random(-32768, 32768) / 32768.0f; + counter1 = 0; + } + + counter2++; + if (counter2 >= samplesPerClock2) { + value2 = random(-32768, 32768) / 32768.0f; + counter2 = 0; + } + + float out = 0.5f * value1 + 0.5f * value2; + + // Add dither + out += (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + block->data[i] = (int16_t)(out * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(DualPulse); diff --git a/P_FeedbackFM.hpp b/P_FeedbackFM.hpp new file mode 100644 index 0000000..0b46ddf --- /dev/null +++ b/P_FeedbackFM.hpp @@ -0,0 +1,66 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class FeedbackFM : public Plugin { + +public: + + FeedbackFM() {} + + ~FeedbackFM() override {} + + FeedbackFM(const FeedbackFM&) = delete; + FeedbackFM& operator=(const FeedbackFM&) = delete; + + void init() override { + customStream.phase = 0.0f; + customStream.prevOut = 0.0f; + customStream.currentFreq = 440.0f; + customStream.currentFeedback = 0.0f; + } + + void process(float k1, float k2) override { + customStream.currentFreq = 20.0f + pow(k1, 2.0f) * 5000.0f; + customStream.currentFeedback = k2 * 2.5f; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float phase = 0.0f; + float prevOut = 0.0f; + float currentFreq = 440.0f; + float currentFeedback = 0.0f; + + void fillBlock(audio_block_t *block) override { + const float twoPi = 2.0f * 3.14159265358979323846f; + const float phaseInc = twoPi * currentFreq / 44100.0f; + + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + phase += phaseInc; + if (phase >= twoPi) phase -= twoPi; + + float out = sin(phase + currentFeedback * prevOut); + prevOut = out; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(FeedbackFM); diff --git a/P_GlitchLoop.hpp b/P_GlitchLoop.hpp new file mode 100644 index 0000000..8266c3d --- /dev/null +++ b/P_GlitchLoop.hpp @@ -0,0 +1,104 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +#define GLITCHLOOP_MAX_SAMPLES 8820 + +class GlitchLoop : public Plugin { + +public: + + GlitchLoop() {} + + ~GlitchLoop() override {} + + GlitchLoop(const GlitchLoop&) = delete; + GlitchLoop& operator=(const GlitchLoop&) = delete; + + void init() override { + customStream.currentLoopLen = GLITCHLOOP_MAX_SAMPLES; + customStream.loopPos = 0; + customStream.bitDepthAccum = 16.0f; + customStream.bitsToLose = 0.0f; + + // Fill buffer with white noise + for (int i = 0; i < GLITCHLOOP_MAX_SAMPLES; i++) { + customStream.loopBuffer[i] = random(-32768, 32768) / 32768.0f; + } + } + + void process(float k1, float k2) override { + customStream.currentLoopLen = (int)(441.0f + pow(k1, 2.0f) * 8379.0f); + if (customStream.currentLoopLen > GLITCHLOOP_MAX_SAMPLES) { + customStream.currentLoopLen = GLITCHLOOP_MAX_SAMPLES; + } + if (customStream.currentLoopLen < 1) { + customStream.currentLoopLen = 1; + } + + customStream.bitsToLose = k2 * 0.05f; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float loopBuffer[GLITCHLOOP_MAX_SAMPLES]; + int loopPos = 0; + int currentLoopLen = GLITCHLOOP_MAX_SAMPLES; + float bitDepthAccum = 16.0f; + float bitsToLose = 0.0f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + float sample = loopBuffer[loopPos]; + + // Apply bit depth reduction + int effectiveBits = (int)bitDepthAccum; + if (effectiveBits < 1) effectiveBits = 1; + if (effectiveBits > 16) effectiveBits = 16; + + // Bit crush: quantize to fewer bits + int shift = 16 - effectiveBits; + if (shift > 0) { + int32_t intSample = (int32_t)(sample * 32767.0f); + intSample = (intSample >> shift) << shift; + sample = (float)intSample / 32767.0f; + } + + // Write crushed sample back into buffer for progressive degradation + loopBuffer[loopPos] = sample; + + int32_t out = (int32_t)(sample * 32767.0f); + if (out > 32767) out = 32767; + if (out < -32767) out = -32767; + block->data[i] = (int16_t)out; + + loopPos++; + if (loopPos >= currentLoopLen) { + loopPos = 0; + bitDepthAccum -= bitsToLose; + + // When bit depth is exhausted, refill with fresh noise + if (bitDepthAccum < 1.0f) { + bitDepthAccum = 16.0f; + for (int j = 0; j < GLITCHLOOP_MAX_SAMPLES; j++) { + loopBuffer[j] = random(-32768, 32768) / 32768.0f; + } + } + } + } + } + } customStream; +}; + +REGISTER_PLUGIN(GlitchLoop); diff --git a/P_HenonDust.hpp b/P_HenonDust.hpp new file mode 100644 index 0000000..4cc56df --- /dev/null +++ b/P_HenonDust.hpp @@ -0,0 +1,75 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class HenonDust : public Plugin { + +public: + + HenonDust() {} + + ~HenonDust() override {} + + HenonDust(const HenonDust&) = delete; + HenonDust& operator=(const HenonDust&) = delete; + + void init() override { + customStream.hx = 0.1f; + customStream.hy = 0.1f; + customStream.a = 1.4f; + customStream.b = 0.3f; + } + + void process(float k1, float k2) override { + // focus k1 on the interesting chaos transition zone (1.15-1.4) + customStream.a = 1.15f + k1 * 0.25f; + customStream.b = 0.15f + k2 * 0.2f; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float hx = 0.1f; + float hy = 0.1f; + float a = 1.4f; + float b = 0.3f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + float new_x = 1.0f - a * hx * hx + hy; + float new_y = b * hx; + hx = new_x; + hy = new_y; + + // Reset on divergence or NaN + if (isnan(hx) || isinf(hx) || isnan(hy) || isinf(hy)) { + hx = random(-32768, 32768) / 32768.0f * 0.5f; + hy = random(-32768, 32768) / 32768.0f * 0.5f; + } + + // Normalize hx from [-1.5, 1.5] to [-1, 1] + float out = hx; + if (out > 1.5f) out = 1.5f; + if (out < -1.5f) out = -1.5f; + out /= 1.5f; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(HenonDust); diff --git a/P_LFSRNoise.hpp b/P_LFSRNoise.hpp new file mode 100644 index 0000000..c393c0d --- /dev/null +++ b/P_LFSRNoise.hpp @@ -0,0 +1,85 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class LFSRNoise : public Plugin { + +public: + + LFSRNoise() {} + + ~LFSRNoise() override {} + + LFSRNoise(const LFSRNoise&) = delete; + LFSRNoise& operator=(const LFSRNoise&) = delete; + + void init() override { + customStream.lfsr = 0xACE1; + customStream.clockCounter = 0; + customStream.samplesPerClock = 441; + customStream.currentTaps = taps[0]; + } + + void process(float k1, float k2) override { + float rate = 5.0f + pow(k1, 2) * 495.0f; + int spc = (int)(44100.0f / rate); + if (spc < 1) spc = 1; + customStream.samplesPerClock = spc; + + int tapIndex = (int)(k2 * 7.99f); + if (tapIndex < 0) tapIndex = 0; + if (tapIndex > 7) tapIndex = 7; + customStream.currentTaps = taps[tapIndex]; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + static constexpr uint16_t taps[8] = { + 0x002D, 0x0039, 0xD008, 0xB400, + 0x6000, 0xD295, 0xB002, 0xE100 + }; + + class Stream : public AudioStreamCustom { + public: + uint16_t lfsr = 0xACE1; + int clockCounter = 0; + int samplesPerClock = 441; + uint16_t currentTaps = 0x002D; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + clockCounter++; + if (clockCounter >= samplesPerClock) { + uint16_t masked = lfsr & currentTaps; + uint16_t feedback = __builtin_popcount(masked) & 1; + lfsr = (lfsr >> 1) | (feedback << 15); + if (lfsr == 0) lfsr = 0xACE1; // prevent permanent lockup + clockCounter = 0; + } + + // Output top 4 bits: 16 discrete levels + int16_t quantized = (int16_t)(lfsr >> 12); + float out = quantized / 7.5f - 1.0f; + + // Add dither + out += (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + block->data[i] = (int16_t)(out * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(LFSRNoise); diff --git a/P_LogisticNoise.hpp b/P_LogisticNoise.hpp new file mode 100644 index 0000000..c8e762c --- /dev/null +++ b/P_LogisticNoise.hpp @@ -0,0 +1,71 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class LogisticNoise : public Plugin { + +public: + + LogisticNoise() {} + + ~LogisticNoise() override {} + + LogisticNoise(const LogisticNoise&) = delete; + LogisticNoise& operator=(const LogisticNoise&) = delete; + + void init() override { + customStream.x = 0.5f; + customStream.holdCounter = 0; + customStream.samplesPerIteration = 1; + customStream.r = 3.8f; + } + + void process(float k1, float k2) override { + customStream.r = 3.5f + k1 * 0.5f; + int spi = 1 + (int)((1.0f - k2) * 30); + if (spi < 1) spi = 1; + customStream.samplesPerIteration = spi; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float x = 0.5f; + float r = 3.8f; + int holdCounter = 0; + int samplesPerIteration = 1; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + holdCounter++; + if (holdCounter >= samplesPerIteration) { + x = r * x * (1.0f - x); + holdCounter = 0; + + // Reset on numerical instability + if (x < 0.0f || x > 1.0f || isnan(x) || isinf(x)) { + x = 0.5f + (random(0, 32768) / 32768.0f - 0.5f) * 0.1f; + } + } + + float out = x * 2.0f - 1.0f; + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(LogisticNoise); diff --git a/P_NoiseBells.hpp b/P_NoiseBells.hpp new file mode 100644 index 0000000..fd8e63a --- /dev/null +++ b/P_NoiseBells.hpp @@ -0,0 +1,123 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class NoiseBells : public Plugin { + +public: + + NoiseBells() {} + + ~NoiseBells() override {} + + NoiseBells(const NoiseBells&) = delete; + NoiseBells& operator=(const NoiseBells&) = delete; + + void init() override { + for (int i = 0; i < NUM_RESONATORS; i++) { + customStream.y1[i] = 0.0f; + customStream.y2[i] = 0.0f; + customStream.coeff_a[i] = 0.0f; + customStream.coeff_b[i] = 0.0f; + customStream.coeff_g[i] = 0.0f; + } + + // initial resonator setup + updateResonators(500.0f, 0.5f); + } + + void process(float k1, float k2) override { + float freq = 80.0f + pow(k1, 2) * 4000.0f; + updateResonators(freq, k2); + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + static const int NUM_RESONATORS = 4; + + void updateResonators(float fundamental, float inharmonicity) { + // bell-like inharmonic ratios + float stretch = 1.0f + inharmonicity * 2.0f; + float freqs[NUM_RESONATORS]; + freqs[0] = fundamental; + freqs[1] = fundamental * (1.0f + 0.59f * stretch); + freqs[2] = fundamental * (1.0f + 1.33f * stretch); + freqs[3] = fundamental * (1.0f + 2.14f * stretch); + + float baseR = 0.9992f; + + for (int i = 0; i < NUM_RESONATORS; i++) { + float f = freqs[i]; + if (f > 20000.0f) f = 20000.0f; + if (f < 20.0f) f = 20.0f; + + float r = baseR - i * 0.0003f; + float w = 2.0f * M_PI * f / 44100.0f; + + customStream.coeff_a[i] = 2.0f * r * cos(w); + customStream.coeff_b[i] = r * r; + customStream.coeff_g[i] = 1.0f - r; + } + + // amplitude rolloff for higher partials + customStream.gains[0] = 1.0f; + customStream.gains[1] = 0.7f; + customStream.gains[2] = 0.4f; + customStream.gains[3] = 0.25f; + } + + class Stream : public AudioStreamCustom { + public: + static const int NUM_RESONATORS = 4; + + float y1[NUM_RESONATORS] = {}; + float y2[NUM_RESONATORS] = {}; + float coeff_a[NUM_RESONATORS] = {}; + float coeff_b[NUM_RESONATORS] = {}; + float coeff_g[NUM_RESONATORS] = {}; + float gains[NUM_RESONATORS] = {}; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + float input = random(-32768, 32768) / 32768.0f; + + float mixed = 0.0f; + + for (int r = 0; r < NUM_RESONATORS; r++) { + // 2-pole resonator + float out = coeff_a[r] * y1[r] - coeff_b[r] * y2[r] + coeff_g[r] * input; + + y2[r] = y1[r]; + y1[r] = out; + + // safety: reset on NaN/inf + if (isnan(out) || isinf(out)) { + y1[r] = 0.0f; + y2[r] = 0.0f; + out = 0.0f; + } + + mixed += out * gains[r]; + } + + // scale down (resonators can be loud) + mixed *= 0.15f; + + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(NoiseBells); diff --git a/P_NoiseBurst.hpp b/P_NoiseBurst.hpp new file mode 100644 index 0000000..725eb6a --- /dev/null +++ b/P_NoiseBurst.hpp @@ -0,0 +1,84 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class NoiseBurst : public Plugin { + +public: + + NoiseBurst() {} + + ~NoiseBurst() override {} + + NoiseBurst(const NoiseBurst&) = delete; + NoiseBurst& operator=(const NoiseBurst&) = delete; + + void init() override { + customStream.burstCounter = 0; + customStream.currentBurstLen = 200; + customStream.burstProb = 0.001f; + } + + void process(float k1, float k2) override { + customStream.burstProb = 0.00002f + pow(k1, 2) * 0.003f; + customStream.currentBurstLen = (int)(44 + k2 * 4400); + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + int burstCounter = 0; + int currentBurstLen = 200; + float burstProb = 0.001f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Generate white noise sample + float noiseSample = random(-32768, 32768) / 32768.0f; + + if (burstCounter > 0) { + // Envelope fade-out on last 20 samples + float gain = 1.0f; + if (burstCounter < 20) { + gain = burstCounter / 20.0f; + } + int32_t out = (int32_t)(noiseSample * gain * 32767.0f); + if (out > 32767) out = 32767; + if (out < -32767) out = -32767; + block->data[i] = (int16_t)out; + burstCounter--; + } + else { + if (random(0, 32768) / 32768.0f < burstProb) { + burstCounter = currentBurstLen; + // Output this sample as part of the burst + float gain = 1.0f; + if (burstCounter < 20) { + gain = burstCounter / 20.0f; + } + int32_t out = (int32_t)(noiseSample * gain * 32767.0f); + if (out > 32767) out = 32767; + if (out < -32767) out = -32767; + block->data[i] = (int16_t)out; + burstCounter--; + } + else { + block->data[i] = 0; + } + } + } + } + } customStream; +}; + +REGISTER_PLUGIN(NoiseBurst); diff --git a/P_NoiseSlew.hpp b/P_NoiseSlew.hpp new file mode 100644 index 0000000..b5f2cd4 --- /dev/null +++ b/P_NoiseSlew.hpp @@ -0,0 +1,67 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class NoiseSlew : public Plugin { + +public: + + NoiseSlew() {} + + ~NoiseSlew() override {} + + NoiseSlew(const NoiseSlew&) = delete; + NoiseSlew& operator=(const NoiseSlew&) = delete; + + void init() override { + customStream.slewState = 0.0f; + customStream.alpha = 0.01f; + customStream.mix = 0.0f; + } + + void process(float k1, float k2) override { + customStream.alpha = 0.0003f + pow(k1, 3) * 0.15f; + customStream.mix = k2; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float slewState = 0.0f; + float alpha = 0.01f; + float mix = 0.0f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Generate white noise sample + float noiseSample = random(-32768, 32768) / 32768.0f; + + slewState = slewState * (1.0f - alpha) + noiseSample * alpha; + + // NaN guard + if (isnan(slewState) || isinf(slewState)) { + slewState = 0.0f; + } + + float out = slewState * (1.0f - mix * 0.7f) + noiseSample * mix * 0.3f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + block->data[i] = (int16_t)(out * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(NoiseSlew); diff --git a/P_PluckCloud.hpp b/P_PluckCloud.hpp new file mode 100644 index 0000000..b4d85e1 --- /dev/null +++ b/P_PluckCloud.hpp @@ -0,0 +1,116 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class PluckCloud : public Plugin { + +public: + + PluckCloud() {} + + ~PluckCloud() override {} + + PluckCloud(const PluckCloud&) = delete; + PluckCloud& operator=(const PluckCloud&) = delete; + + void init() override { + for (int v = 0; v < 6; v++) { + customStream.writePos[v] = 0; + customStream.filterState[v] = 0.0f; + customStream.triggered[v] = false; + for (int i = 0; i < 1024; i++) { + customStream.buf[v][i] = 0.0f; + } + } + customStream.damping = 0.5f; + customStream.baseDelay = 100.0f; + } + + void process(float k1, float k2) override { + float freq = 60.0f + pow(k1, 2) * 2940.0f; + customStream.baseDelay = 44100.0f / freq; + customStream.damping = 0.98f - k2 * 0.58f; + + // random retrigger: each voice has ~2% chance per process() call + for (int v = 0; v < 6; v++) { + if (random(0, 32768) / 32768.0f < 0.02f) { + triggerVoice(v); + } + } + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + void triggerVoice(int v) { + float voiceDelay = customStream.baseDelay; + int delay = (int)voiceDelay; + if (delay < 1) delay = 1; + if (delay > 1023) delay = 1023; + + for (int i = 0; i < delay; i++) { + int pos = (customStream.writePos[v] - delay + i + 1024) & 1023; + customStream.buf[v][pos] = random(-32768, 32768) / 32768.0f; + } + customStream.filterState[v] = 0.0f; + customStream.triggered[v] = true; + } + + class Stream : public AudioStreamCustom { + public: + float buf[6][1024] = {}; + int writePos[6] = {}; + float filterState[6] = {}; + bool triggered[6] = {}; + float damping = 0.5f; + float baseDelay = 100.0f; + + void fillBlock(audio_block_t *block) override { + // voice detune ratios + static const float detuneRatio[6] = {1.0f, 1.005f, 0.995f, 1.01f, 0.99f, 1.015f}; + + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + float mixed = 0.0f; + + for (int v = 0; v < 6; v++) { + float voiceDelay = baseDelay * detuneRatio[v]; + int delay = (int)voiceDelay; + if (delay < 1) delay = 1; + if (delay > 1023) delay = 1023; + + int readPos = writePos[v] - delay; + if (readPos < 0) readPos += 1024; + + // read from delay and apply one-pole LP filter + float delayOut = buf[v][readPos]; + float filtered = damping * filterState[v] + (1.0f - damping) * delayOut; + filterState[v] = filtered; + + // write filtered output back into delay line + buf[v][writePos[v]] = filtered; + writePos[v] = (writePos[v] + 1) & 1023; // mod 1024 + + mixed += filtered; + } + + // scale by number of voices + mixed *= (1.0f / 6.0f); + + // clamp and convert to int16 + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(PluckCloud); diff --git a/P_PulseWander.hpp b/P_PulseWander.hpp new file mode 100644 index 0000000..d7569cc --- /dev/null +++ b/P_PulseWander.hpp @@ -0,0 +1,82 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class PulseWander : public Plugin { + +public: + + PulseWander() {} + + ~PulseWander() override {} + + PulseWander(const PulseWander&) = delete; + PulseWander& operator=(const PulseWander&) = delete; + + void init() override { + customStream.currentValue = random(-32768, 32768) / 32768.0f; + customStream.targetValue = random(-32768, 32768) / 32768.0f; + customStream.smoothAlpha = 0.1f; + customStream.sampleCounter = 0; + customStream.samplesPerTarget = 441; + } + + void process(float k1, float k2) override { + float rate = 5.0f + pow(k1, 2) * 395.0f; + int spt = (int)(44100.0f / rate); + if (spt < 1) spt = 1; + customStream.samplesPerTarget = spt; + + float minAlpha = 3.0f / (float)customStream.samplesPerTarget; + customStream.smoothAlpha = minAlpha + (1.0f - k2) * (1.0f - minAlpha); + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float currentValue = 0.0f; + float targetValue = 0.0f; + float smoothAlpha = 0.1f; + int sampleCounter = 0; + int samplesPerTarget = 441; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + sampleCounter++; + if (sampleCounter >= samplesPerTarget) { + targetValue = random(-32768, 32768) / 32768.0f; + sampleCounter = 0; + } + + currentValue = currentValue * (1.0f - smoothAlpha) + targetValue * smoothAlpha; + + // NaN/Inf guard + if (isnan(currentValue) || isinf(currentValue)) { + currentValue = 0.0f; + } + + float out = currentValue + (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(PulseWander); diff --git a/P_QuantPulse.hpp b/P_QuantPulse.hpp new file mode 100644 index 0000000..ff9c07f --- /dev/null +++ b/P_QuantPulse.hpp @@ -0,0 +1,83 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class QuantPulse : public Plugin { + +public: + + QuantPulse() {} + + ~QuantPulse() override {} + + QuantPulse(const QuantPulse&) = delete; + QuantPulse& operator=(const QuantPulse&) = delete; + + void init() override { + customStream.currentValue = 0.0f; + customStream.sampleCounter = 0; + customStream.samplesPerClock = 441; + customStream.bits = 3.0f; + } + + void process(float k1, float k2) override { + float rate = 5.0f + pow(k1, 2) * 495.0f; + int spc = (int)(44100.0f / rate); + if (spc < 1) spc = 1; + customStream.samplesPerClock = spc; + + customStream.bits = 1.0f + k2 * 5.0f; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float currentValue = 0.0f; + int sampleCounter = 0; + int samplesPerClock = 441; + float bits = 3.0f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + sampleCounter++; + if (sampleCounter >= samplesPerClock) { + float raw = random(-32768, 32768) / 32768.0f; + float levels = pow(2.0f, bits); + currentValue = round(raw * levels) / levels; + + if (currentValue > 1.0f) currentValue = 1.0f; + if (currentValue < -1.0f) currentValue = -1.0f; + + sampleCounter = 0; + } + + // NaN/Inf guard + if (isnan(currentValue) || isinf(currentValue)) { + currentValue = 0.0f; + } + + float out = currentValue + (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(QuantPulse); diff --git a/P_ShapedPulse.hpp b/P_ShapedPulse.hpp new file mode 100644 index 0000000..df3d4b8 --- /dev/null +++ b/P_ShapedPulse.hpp @@ -0,0 +1,98 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class ShapedPulse : public Plugin { + +public: + + ShapedPulse() {} + + ~ShapedPulse() override {} + + ShapedPulse(const ShapedPulse&) = delete; + ShapedPulse& operator=(const ShapedPulse&) = delete; + + void init() override { + customStream.currentValue = 0.0f; + customStream.sampleCounter = 0; + customStream.samplesPerClock = 441; + customStream.shape = 0.0f; + } + + void process(float k1, float k2) override { + float rate = 5.0f + pow(k1, 2) * 495.0f; + int spc = (int)(44100.0f / rate); + if (spc < 1) spc = 1; + customStream.samplesPerClock = spc; + + customStream.shape = k2; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float currentValue = 0.0f; + int sampleCounter = 0; + int samplesPerClock = 441; + float shape = 0.0f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + sampleCounter++; + if (sampleCounter >= samplesPerClock) { + float val; + + if (shape < 0.5f) { + // Blend uniform and triangular + float blend = shape * 2.0f; + float uniform = random(-32768, 32768) / 32768.0f; + float tri = (random(0, 32768) / 32768.0f + random(0, 32768) / 32768.0f) - 1.0f; + val = uniform * (1.0f - blend) + tri * blend; + } + else { + // Blend triangular and peaked (pseudo-gaussian) + float blend = (shape - 0.5f) * 2.0f; + float tri = (random(0, 32768) / 32768.0f + random(0, 32768) / 32768.0f) - 1.0f; + float peaked = (random(0, 32768) / 32768.0f + random(0, 32768) / 32768.0f + + random(0, 32768) / 32768.0f + random(0, 32768) / 32768.0f) * 0.5f - 1.0f; + val = tri * (1.0f - blend) + peaked * blend; + } + + if (val > 1.0f) val = 1.0f; + if (val < -1.0f) val = -1.0f; + + currentValue = val; + sampleCounter = 0; + } + + // NaN/Inf guard + if (isnan(currentValue) || isinf(currentValue)) { + currentValue = 0.0f; + } + + float out = currentValue + (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(ShapedPulse); diff --git a/P_ShiftPulse.hpp b/P_ShiftPulse.hpp new file mode 100644 index 0000000..c7eb7b4 --- /dev/null +++ b/P_ShiftPulse.hpp @@ -0,0 +1,108 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class ShiftPulse : public Plugin { + +public: + + ShiftPulse() {} + + ~ShiftPulse() override {} + + ShiftPulse(const ShiftPulse&) = delete; + ShiftPulse& operator=(const ShiftPulse&) = delete; + + void init() override { + for (int i = 0; i < 4; i++) { + customStream.stages[i] = 0.0f; + customStream.stageWeights[i] = 0.0f; + } + customStream.stageWeights[0] = 1.0f; + customStream.sampleCounter = 0; + customStream.samplesPerClock = 441; + } + + void process(float k1, float k2) override { + float rate = 5.0f + pow(k1, 2) * 295.0f; + int spc = (int)(44100.0f / rate); + if (spc < 1) spc = 1; + customStream.samplesPerClock = spc; + + // Crossfade stage weights based on k2 + float w0 = 1.0f; + float w1 = k2 * 3.0f; + if (w1 > 1.0f) w1 = 1.0f; + float w2 = k2 * 3.0f - 1.0f; + if (w2 < 0.0f) w2 = 0.0f; + if (w2 > 1.0f) w2 = 1.0f; + float w3 = k2 * 3.0f - 2.0f; + if (w3 < 0.0f) w3 = 0.0f; + if (w3 > 1.0f) w3 = 1.0f; + + // Normalize weights + float sum = w0 + w1 + w2 + w3; + if (sum > 0.0f) { + customStream.stageWeights[0] = w0 / sum; + customStream.stageWeights[1] = w1 / sum; + customStream.stageWeights[2] = w2 / sum; + customStream.stageWeights[3] = w3 / sum; + } + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float stages[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float stageWeights[4] = {1.0f, 0.0f, 0.0f, 0.0f}; + int sampleCounter = 0; + int samplesPerClock = 441; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + sampleCounter++; + if (sampleCounter >= samplesPerClock) { + // Shift register: shift down + stages[3] = stages[2]; + stages[2] = stages[1]; + stages[1] = stages[0]; + stages[0] = random(-32768, 32768) / 32768.0f; + + sampleCounter = 0; + } + + float out = 0.0f; + for (int s = 0; s < 4; s++) { + out += stageWeights[s] * stages[s]; + } + + // NaN/Inf guard + if (isnan(out) || isinf(out)) { + out = 0.0f; + } + + out += (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(ShiftPulse); diff --git a/P_StochasticPulse.hpp b/P_StochasticPulse.hpp new file mode 100644 index 0000000..d7846dc --- /dev/null +++ b/P_StochasticPulse.hpp @@ -0,0 +1,91 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class StochasticPulse : public Plugin { + +public: + + StochasticPulse() {} + + ~StochasticPulse() override {} + + StochasticPulse(const StochasticPulse&) = delete; + StochasticPulse& operator=(const StochasticPulse&) = delete; + + void init() override { + customStream.density = 50.0f; + customStream.lastPulseSign = false; + customStream.dryMix = 0.3f; + customStream.filterFreq = 1000.0f; + customStream.filterQ = 3.5f; + + // Init SVF state + customStream.svfLow = 0.0f; + customStream.svfBand = 0.0f; + } + + void process(float k1, float k2) override { + customStream.density = 5.0f + pow(k1, 2) * 500.0f; + customStream.filterFreq = 80.0f + pow(k2, 2) * 8000.0f; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float density = 50.0f; + float dryMix = 0.3f; + bool lastPulseSign = false; + float filterFreq = 1000.0f; + float filterQ = 3.5f; + + // State variable filter state + float svfLow = 0.0f; + float svfBand = 0.0f; + + void fillBlock(audio_block_t *block) override { + float probability = density / 44100.0f; + + // SVF coefficients + float f = 2.0f * sin(M_PI * filterFreq / 44100.0f); + float q = 1.0f / filterQ; + + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Generate stochastic impulse + float impulse = 0.0f; + if (random(0, 32768) / 32768.0f < probability) { + lastPulseSign = !lastPulseSign; + impulse = lastPulseSign ? 1.0f : -1.0f; + } + + // State variable filter (bandpass) + svfLow += f * svfBand; + float svfHigh = impulse - svfLow - q * svfBand; + svfBand += f * svfHigh; + + // Clamp SVF state to prevent blowup + if (isnan(svfBand) || isinf(svfBand)) svfBand = 0.0f; + if (isnan(svfLow) || isinf(svfLow)) svfLow = 0.0f; + + // Mix: dry click + resonant ring (bandpass output) + float mixed = impulse * dryMix + svfBand; + + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(StochasticPulse); diff --git a/P_SubHarmonic.hpp b/P_SubHarmonic.hpp new file mode 100644 index 0000000..c26f1f4 --- /dev/null +++ b/P_SubHarmonic.hpp @@ -0,0 +1,109 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class SubHarmonic : public Plugin { + +public: + + SubHarmonic() {} + + ~SubHarmonic() override {} + + SubHarmonic(const SubHarmonic&) = delete; + SubHarmonic& operator=(const SubHarmonic&) = delete; + + void init() override { + customStream.srcFreq = 440.0f; + customStream.srcPhase = 0.0f; + + for (int i = 0; i < 4; i++) { + customStream.divCounters[i] = 0; + customStream.divStates[i] = false; + customStream.divWeights[i] = 0.0f; + } + customStream.divWeights[0] = 1.0f; + customStream.prevSamplePositive = true; + } + + void process(float k1, float k2) override { + float freq = 100.0f + pow(k1, 2.0f) * 4000.0f; + customStream.srcFreq = freq; + + // Crossfade subharmonic levels based on k2 + customStream.divWeights[0] = 1.0f; // div2 always active + float w1 = k2 * 3.0f; + customStream.divWeights[1] = w1 < 0.0f ? 0.0f : (w1 > 1.0f ? 1.0f : w1); + float w2 = k2 * 3.0f - 1.0f; + customStream.divWeights[2] = w2 < 0.0f ? 0.0f : (w2 > 1.0f ? 1.0f : w2); + float w3 = k2 * 3.0f - 2.0f; + customStream.divWeights[3] = w3 < 0.0f ? 0.0f : (w3 > 1.0f ? 1.0f : w3); + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float srcFreq = 440.0f; + float srcPhase = 0.0f; + + int divRatios[4] = {2, 3, 5, 7}; + int divCounters[4] = {0, 0, 0, 0}; + bool divStates[4] = {false, false, false, false}; + float divWeights[4] = {1.0f, 0.0f, 0.0f, 0.0f}; + + bool prevSamplePositive = true; + + void fillBlock(audio_block_t *block) override { + float phaseInc = srcFreq / 44100.0f; + + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Square wave oscillator via phase accumulator + srcPhase += phaseInc; + if (srcPhase >= 1.0f) srcPhase -= 1.0f; + + float srcFloat = (srcPhase < 0.5f) ? 1.0f : -1.0f; + bool currentPositive = (srcFloat >= 0.0f); + + // Detect zero crossing (sign change) + if (currentPositive != prevSamplePositive) { + for (int d = 0; d < 4; d++) { + divCounters[d]++; + if (divCounters[d] >= divRatios[d]) { + divStates[d] = !divStates[d]; + divCounters[d] = 0; + } + } + } + prevSamplePositive = currentPositive; + + // Mix: source + subharmonics, normalized to prevent clipping + float totalWeight = 0.15f; + for (int d = 0; d < 4; d++) totalWeight += divWeights[d] * 0.35f; + float norm = 0.9f / totalWeight; + + float mix = 0.15f * norm * srcFloat; + for (int d = 0; d < 4; d++) { + float divValue = divStates[d] ? 0.35f : -0.35f; + mix += divWeights[d] * norm * divValue; + } + + int32_t out = (int32_t)(mix * 32767.0f); + if (out > 32767) out = 32767; + if (out < -32767) out = -32767; + block->data[i] = (int16_t)out; + } + } + } customStream; +}; + +REGISTER_PLUGIN(SubHarmonic); diff --git a/P_TubeResonance.hpp b/P_TubeResonance.hpp new file mode 100644 index 0000000..4faa016 --- /dev/null +++ b/P_TubeResonance.hpp @@ -0,0 +1,100 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class TubeResonance : public Plugin { + +public: + + TubeResonance() {} + + ~TubeResonance() override {} + + TubeResonance(const TubeResonance&) = delete; + TubeResonance& operator=(const TubeResonance&) = delete; + + void init() override { + for (int c = 0; c < 4; c++) { + customStream.writePos[c] = 0; + for (int i = 0; i < 2048; i++) { + customStream.buf[c][i] = 0.0f; + } + } + customStream.fundamentalFreq = 200.0f; + customStream.feedback = 0.85f; + customStream.excitation = 0.5f; + } + + void process(float k1, float k2) override { + customStream.fundamentalFreq = 40.0f + pow(k1, 2) * 960.0f; + customStream.excitation = k2; + customStream.feedback = 0.7f + (1.0f - k2) * 0.25f; + + // compute delay lengths for 4 harmonics + for (int c = 0; c < 4; c++) { + float harmonic = (float)(c + 1); + float freq = customStream.fundamentalFreq * harmonic; + float delaySamples = 44100.0f / freq; + customStream.delayLen[c] = (int)delaySamples; + if (customStream.delayLen[c] < 1) customStream.delayLen[c] = 1; + if (customStream.delayLen[c] > 2047) customStream.delayLen[c] = 2047; + + // feedback per harmonic scaled by 1/harmonic_number + customStream.harmonicFeedback[c] = customStream.feedback / harmonic; + } + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float buf[4][2048] = {}; + int writePos[4] = {}; + int delayLen[4] = {100, 50, 33, 25}; + float harmonicFeedback[4] = {}; + float fundamentalFreq = 200.0f; + float feedback = 0.85f; + float excitation = 0.5f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + // Generate white noise input scaled by excitation + float input = (random(-32768, 32768) / 32768.0f) * excitation; + float mixed = 0.0f; + + // process 4 comb filters in parallel + for (int c = 0; c < 4; c++) { + int readPos = writePos[c] - delayLen[c]; + if (readPos < 0) readPos += 2048; + + float delayOut = buf[c][readPos]; + float out = input + harmonicFeedback[c] * delayOut; + buf[c][writePos[c]] = out; + + writePos[c] = (writePos[c] + 1) & 2047; // mod 2048 + + mixed += out; + } + + // mix 4 combs equally + mixed *= 0.25f; + + // clamp and convert to int16 + if (mixed > 1.0f) mixed = 1.0f; + if (mixed < -1.0f) mixed = -1.0f; + block->data[i] = (int16_t)(mixed * 32767.0f); + } + } + } customStream; +}; + +REGISTER_PLUGIN(TubeResonance); diff --git a/P_TwinPulse.hpp b/P_TwinPulse.hpp new file mode 100644 index 0000000..33ec20a --- /dev/null +++ b/P_TwinPulse.hpp @@ -0,0 +1,102 @@ +// Noise Plethora Plugins +// Copyright (c) 2021 Befaco / Jeremy Bernstein +// Open-source software +// Licensed under GPL-3.0-or-later + +#pragma once + +#include "Plugin.hpp" +#include "AudioStreamCustom.hpp" + +class TwinPulse : public Plugin { + +public: + + TwinPulse() {} + + ~TwinPulse() override {} + + TwinPulse(const TwinPulse&) = delete; + TwinPulse& operator=(const TwinPulse&) = delete; + + void init() override { + customStream.currentA = random(-32768, 32768) / 32768.0f; + customStream.targetA = random(-32768, 32768) / 32768.0f; + customStream.currentB = random(-32768, 32768) / 32768.0f; + customStream.targetB = random(-32768, 32768) / 32768.0f; + customStream.counterA = 0; + customStream.counterB = 0; + customStream.alpha = 0.1f; + customStream.correlation = 0.0f; + customStream.samplesPerTarget = 441; + } + + void process(float k1, float k2) override { + float rate = 5.0f + pow(k1, 2) * 395.0f; + int spt = (int)(44100.0f / rate); + if (spt < 1) spt = 1; + customStream.samplesPerTarget = spt; + + float a = 4.0f / (float)customStream.samplesPerTarget; + if (a > 0.5f) a = 0.5f; + customStream.alpha = a; + + customStream.correlation = k2; + } + + AudioStream& getStream() override { return customStream; } + unsigned char getPort() override { return 0; } + +private: + + class Stream : public AudioStreamCustom { + public: + float currentA = 0.0f; + float targetA = 0.0f; + float currentB = 0.0f; + float targetB = 0.0f; + int counterA = 0; + int counterB = 0; + int samplesPerTarget = 441; + float alpha = 0.1f; + float correlation = 0.0f; + + void fillBlock(audio_block_t *block) override { + for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { + counterA++; + if (counterA >= samplesPerTarget) { + targetA = random(-32768, 32768) / 32768.0f; + counterA = 0; + } + + counterB++; + if (counterB >= samplesPerTarget) { + targetB = random(-32768, 32768) / 32768.0f; + counterB = 0; + } + + currentA = currentA * (1.0f - alpha) + targetA * alpha; + currentB = currentB * (1.0f - alpha) + targetB * alpha; + + // NaN/Inf guard + if (isnan(currentA) || isinf(currentA)) currentA = 0.0f; + if (isnan(currentB) || isinf(currentB)) currentB = 0.0f; + + float blendedB = correlation * currentA + (1.0f - correlation) * currentB; + float out = 0.5f * currentA + 0.5f * blendedB; + + out += (random(0, 32768) / 32768.0f - 0.5f) * 0.01f; + + if (out > 1.0f) out = 1.0f; + if (out < -1.0f) out = -1.0f; + + int32_t sample = (int32_t)(out * 32767.0f); + if (sample > 32767) sample = 32767; + if (sample < -32767) sample = -32767; + block->data[i] = (int16_t)sample; + } + } + } customStream; +}; + +REGISTER_PLUGIN(TwinPulse); From c486fe46e458602a6c3561647997c2fc8596d767 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:40:39 +0200 Subject: [PATCH 05/11] Add Makefile for firmware build and flash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make — compile for Teensy 4.0 make upload — compile + flash (auto-detects port) make clean — remove build artifacts make port — list available serial ports Co-Authored-By: Claude Opus 4.6 (1M context) --- Makefile | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e7ba776 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# Noise Plethora Firmware — Build & Flash +# Requires: arduino-cli, teensy:avr core, patched AudioStream files +# +# Usage: +# make — compile firmware +# make upload — compile + flash to connected Teensy +# make clean — remove build artifacts +# make port — show available serial ports + +FQBN := teensy:avr:teensy40 +SKETCH_DIR := /tmp/Noise_Plethora +SRC_DIR := $(shell pwd) +PORT ?= $(shell arduino-cli board list 2>/dev/null | grep -i "teensy\|usb" | awk '{print $$1}' | head -1) + +.PHONY: all compile upload clean port + +all: compile + +compile: + @echo "Copying source to $(SKETCH_DIR) (Arduino requires dir name = sketch name)..." + @rm -rf $(SKETCH_DIR) + @cp -r $(SRC_DIR) $(SKETCH_DIR) + @echo "Compiling for Teensy 4.0..." + arduino-cli compile --fqbn $(FQBN) $(SKETCH_DIR) + +upload: compile + @if [ -z "$(PORT)" ]; then \ + echo "ERROR: No Teensy found. Connect via USB and try again."; \ + echo "Available ports:"; \ + arduino-cli board list; \ + exit 1; \ + fi + @echo "Flashing to $(PORT)..." + arduino-cli upload --fqbn $(FQBN) --port $(PORT) $(SKETCH_DIR) + +clean: + rm -rf $(SKETCH_DIR) + +port: + arduino-cli board list From 8d4abba943fa19d5c9229ef4ab071c5cf6c0345d Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:46:07 +0200 Subject: [PATCH 06/11] Add stereo mode to firmware Double-click the encoder to toggle stereo mode: - B mirrors A's algorithm automatically - XA/YA control both generators - XB = stereo width (timbral offset between L/R) - YB = stereo movement (LFO speed for offset) - Both display dots light up to indicate stereo - Offset applied to k2 (timbre) not k1 (pitch) - B syncs when A changes program Single click delayed 300ms to distinguish from double-click. Co-Authored-By: Claude Opus 4.6 (1M context) --- Noise_Plethora.ino | 88 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/Noise_Plethora.ino b/Noise_Plethora.ino index be88d52..cf685a4 100644 --- a/Noise_Plethora.ino +++ b/Noise_Plethora.ino @@ -58,6 +58,13 @@ bool wantsLoadTestMode = false; bool wantsUnloadTestMode = false; bool inTestMode = false; +// Stereo mode: B mirrors A, XB=stereo width, YB=stereo movement +bool stereoMode = false; +float stereoLFOPhase = 0.0; +float stereoOffset = 0.0; +long lastClickTime = 0; +bool waitingForDoubleClick = false; + AudioProcessor processor; ProgramSelector program; @@ -496,8 +503,19 @@ void loop() { #endif auto pinA = PluginFactory::Instance()->Create(nameA); processor.setA(pinA, gainA); + + // Stereo: when A changes, B follows + if (stereoMode) { + state_progB_Bank = state_progA_Bank; + state_progB_Program = state_progA_Program; + program.getB().setBank(state_progA_Bank); + program.getB().setProgram(state_progA_Program); + auto pinB = PluginFactory::Instance()->Create(nameA); + processor.setB(pinB, gainA); + switchProgramB = false; // already handled + } } - if (switchProgramB) { + if (switchProgramB && !stereoMode) { auto nameB = program.getB().getCurrentProgramName(); auto gainB = program.getB().getCurrentProgramGain(); #ifdef DEBUGGING @@ -550,12 +568,24 @@ void loop() { sevseg.setChars(charsToDisplay); } else { - float numberToDisplay = (valToDisplayA * 10) + valToDisplayB; - int decimalPoint = 0; - if (modeA) { - decimalPoint = 1; + float numberToDisplay; + int decimalPoint; + if (stereoMode) { + // Stereo: both digits show A's program, both dots on + // SevSeg: both dots on = both digits with '.' suffix + char stereoFull[] = "0.0."; + stereoFull[0] = '0' + valToDisplayA; + stereoFull[2] = '0' + valToDisplayA; + sevseg.setChars(stereoFull); + } + else { + numberToDisplay = (valToDisplayA * 10) + valToDisplayB; + decimalPoint = 0; + if (modeA) { + decimalPoint = 1; + } + sevseg.setNumber(numberToDisplay, decimalPoint); } - sevseg.setNumber(numberToDisplay, decimalPoint); } } // display refreshed in interrupt (displayTimer) @@ -566,18 +596,60 @@ void loop() { } else { if (actionOnButtonUp) { - program.setMode(!program.getMode()); + // Double-click detection for stereo mode + if (waitingForDoubleClick && (ms - lastClickTime < 300)) { + // Double-click detected — toggle stereo mode + stereoMode = !stereoMode; + waitingForDoubleClick = false; + if (stereoMode) { + // Sync B to A + state_progB_Bank = state_progA_Bank; + state_progB_Program = state_progA_Program; + program.getB().setBank(state_progA_Bank); + program.getB().setProgram(state_progA_Program); + auto nameB = program.getB().getCurrentProgramName(); + auto gainB = program.getB().getCurrentProgramGain(); + auto pinB = PluginFactory::Instance()->Create(nameB); + processor.setB(pinB, gainB); + stateTimer = ms; + } + } + else { + waitingForDoubleClick = true; + lastClickTime = ms; + } } } actionOnButtonUp = true; buttonStartTime = testModeButtonStartTime = 0; } + // Single click timeout — if no second click within 300ms, do normal A/B toggle + if (waitingForDoubleClick && (ms - lastClickTime >= 300)) { + waitingForDoubleClick = false; + if (!stereoMode) { + program.setMode(!program.getMode()); + } + } + AudioNoInterrupts(); g_process_mode = PROCESS_MODE_A; processor.processA(knob_1, knob_2); g_process_mode = PROCESS_MODE_B; - processor.processB(knob_3, knob_4); + if (stereoMode) { + // Stereo: B uses A's params. XB=width, YB=movement speed. + // Offset applied to k2 (timbre) not k1 (pitch). + float width = knob_3; // XB repurposed as stereo width + float speed = knob_4; // YB repurposed as stereo movement + stereoLFOPhase += speed * 0.3 * 0.003; // ~3ms per loop iteration + if (stereoLFOPhase > 1.0) stereoLFOPhase -= 1.0; + stereoOffset = width * 0.4 * sin(2.0 * PI * stereoLFOPhase); + float stereo_k2 = constrain(knob_2 + stereoOffset, 0.0, 1.0); + processor.processB(knob_1, stereo_k2); + } + else { + processor.processB(knob_3, knob_4); + } g_process_mode = PROCESS_MODE_NONE; AudioInterrupts(); } From ce7bd3df81e543fa38182b09d7c3e3e772d4740f Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 00:54:12 +0200 Subject: [PATCH 07/11] Add README and documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewritten README with build instructions, bank overview, stereo mode summary, memory usage, and links to detailed guides. Documentation copied from VCV repo: - NOISE_PLETHORA_PLUGIN_GUIDE.md — all 60 algorithms with parameters - NOISE_PLETHORA_CATEGORIES.md — programs by use case - NOISE_PLETHORA_STEREO_GUIDE.md — stereo mode and patch ideas Co-Authored-By: Claude Opus 4.6 (1M context) --- Makefile | 38 +- NOISE_PLETHORA_CATEGORIES.md | 140 +++++ NOISE_PLETHORA_PLUGIN_GUIDE.md | 1034 ++++++++++++++++++++++++++++++++ NOISE_PLETHORA_STEREO_GUIDE.md | 153 +++++ README.md | 91 ++- 5 files changed, 1443 insertions(+), 13 deletions(-) create mode 100644 NOISE_PLETHORA_CATEGORIES.md create mode 100644 NOISE_PLETHORA_PLUGIN_GUIDE.md create mode 100644 NOISE_PLETHORA_STEREO_GUIDE.md diff --git a/Makefile b/Makefile index e7ba776..cf758c7 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,47 @@ FQBN := teensy:avr:teensy40 SKETCH_DIR := /tmp/Noise_Plethora SRC_DIR := $(shell pwd) PORT ?= $(shell arduino-cli board list 2>/dev/null | grep -i "teensy\|usb" | awk '{print $$1}' | head -1) +TEENSY_CORE := $(shell find $(HOME)/Library/Arduino15/packages/teensy/hardware/avr -maxdepth 1 -type d 2>/dev/null | tail -1) -.PHONY: all compile upload clean port +.PHONY: all compile upload clean port patch setup all: compile +setup: patch + @echo "Setup complete. Run 'make' to compile." + +patch: + @if [ -z "$(TEENSY_CORE)" ]; then \ + echo "ERROR: Teensy core not found. Run first:"; \ + echo " arduino-cli config add board_manager.additional_urls https://www.pjrc.com/teensy/package_teensy_index.json"; \ + echo " arduino-cli core update-index && arduino-cli core install teensy:avr"; \ + exit 1; \ + fi + @echo "Patching Teensy AudioStream at $(TEENSY_CORE)..." + @cp "$(TEENSY_CORE)/cores/teensy4/AudioStream.h" "$(TEENSY_CORE)/cores/teensy4/AudioStream.h.bak" 2>/dev/null || true + @cp "$(TEENSY_CORE)/cores/teensy4/AudioStream.cpp" "$(TEENSY_CORE)/cores/teensy4/AudioStream.cpp.bak" 2>/dev/null || true + @cp teensy/avr/cores/teensy4/AudioStream.h "$(TEENSY_CORE)/cores/teensy4/" + @cp teensy/avr/cores/teensy4/AudioStream.cpp "$(TEENSY_CORE)/cores/teensy4/" + @DELAY=$$(find "$(TEENSY_CORE)" -name "effect_delay.h" -path "*/Audio/*" | head -1); \ + if [ -n "$$DELAY" ]; then \ + cp "$$DELAY" "$${DELAY}.bak" 2>/dev/null || true; \ + cp teensy/avr/libraries/Audio/effect_delay.h "$$DELAY"; \ + echo "Patched: AudioStream.h, AudioStream.cpp, effect_delay.h"; \ + else \ + echo "Patched: AudioStream.h, AudioStream.cpp (effect_delay.h not found)"; \ + fi + @echo "Originals backed up with .bak extension." + +unpatch: + @if [ -z "$(TEENSY_CORE)" ]; then echo "Teensy core not found."; exit 1; fi + @echo "Restoring original Teensy AudioStream..." + @cd "$(TEENSY_CORE)/cores/teensy4" && \ + [ -f AudioStream.h.bak ] && mv AudioStream.h.bak AudioStream.h && \ + [ -f AudioStream.cpp.bak ] && mv AudioStream.cpp.bak AudioStream.cpp || true + @DELAY=$$(find "$(TEENSY_CORE)" -name "effect_delay.h.bak" -path "*/Audio/*" | head -1); \ + if [ -n "$$DELAY" ]; then mv "$$DELAY" "$${DELAY%.bak}"; fi + @echo "Originals restored." + compile: @echo "Copying source to $(SKETCH_DIR) (Arduino requires dir name = sketch name)..." @rm -rf $(SKETCH_DIR) diff --git a/NOISE_PLETHORA_CATEGORIES.md b/NOISE_PLETHORA_CATEGORIES.md new file mode 100644 index 0000000..c3eab3c --- /dev/null +++ b/NOISE_PLETHORA_CATEGORIES.md @@ -0,0 +1,140 @@ +# Noise Plethora — Sound Categories + +*Befaco Noise Plethora v1.5 + Banks D, E & F Extension* + +## Table of Contents + +- [Drone](#-drone--sustained-meditative-evolving) +- [Harsh / Noise](#-harsh--noise--aggressive-industrial-digital-destruction) +- [Percussive / Rhythmic](#-percussive--rhythmic--pulses-clicks-patterns) +- [Textural / Ambient](#-textural--ambient--atmospheric-spatial-enveloping) +- [Chaotic / Experimental](#-chaotic--experimental--unpredictable-mathematical-alien) +- [Versatile Programs](#versatile-programs) + +--- + +Every program in the Noise Plethora can be broadly classified by its sonic character. Some programs are versatile and appear in multiple categories depending on knob positions — see [Versatile Programs](#versatile-programs) for details. + +--- + +### 🔊 DRONE — Sustained, meditative, evolving + +Long, sustained tones that evolve slowly. Ideal for ambient backgrounds, meditation, soundscapes, and bass layers. Best experienced with the module's analog filters in lowpass mode with moderate resonance. + +| Program | Name | Why it works for drone | +|---------|------|----------------------| +| D-4 | BowedMetal | 8 metallic resonators with infinite sustain at high Y. Singing bowl character. | +| D-6 | NoiseBells | 4 ultra-sharp resonant peaks ring continuously. Crystal bell drone. | +| D-9 | DroneBody | Two beating sines + wavefolder. Designed specifically for drones. | +| D-2 | TubeResonance | Self-resonating tube drone at Y=0. Hollow, harmonic series. | +| D-0 | CombNoise | Sustained metallic resonance at high feedback. Pipe organ character. | +| E-5 | FeedbackFM | Pure sine at Y=0. The most "clean" drone source. | +| E-6 | RunawayFilter | Self-oscillating filter whistle. Clean pitched tone. | +| E-9 | DualAttractor | Two sines drifting chaotically. Never repeats, always moving. | +| B-4 | TriFMcluster | 6 triangles with ultra-slow phasing. Lush ensemble drift. | +| B-0 | ClusterSaw | 16 sawtooths in tight cluster. Massive shimmering wall. | +| B-8 | PartialCluster | Harmonic series from unison to bells. Most tuneable cluster. | +| B-9 | PhasingCluster | 16 squares with 16 independent LFOs. Self-evolving forever. | +| B-5 | PrimeCluster | Inharmonic prime number chord. Unique metallic shimmer. | + +**Tip:** Use stereo mode with BowedMetal or NoiseBells for an immersive, wide drone. Set XB (width) to ~0.3 for subtle stereo. + +--- + +### ⚡ HARSH / NOISE — Aggressive, industrial, digital destruction + +Raw, aggressive textures. Bitcrushing, distortion, chaotic modulation. Good for industrial music, harsh noise, sound design, and adding grit to other sources via mixing. + +| Program | Name | Character | +|---------|------|-----------| +| E-4 | BitShift | XOR of two sawtooths. 8-bit digital screech. | +| E-7 | GlitchLoop | Captured noise loop that degrades to 1-bit over time. | +| C-8 | Rwalk_BitCrushPW | 9 random-walk oscillators crushed to 1 bit. Extreme lo-fi. | +| C-7 | SatanWorkout | Pink noise FM + self-exciting reverb. Relentless. | +| C-0 | BasuraTotal | LFSR-triggered chaotic bursts with reverb. "Total Garbage." | +| A-6 | GrainGlitch | Granular + XOR. Broken CD character. | +| D-7 | NoiseHarmonics | Wavefolded noise through filter. Angry bee swarm. | +| A-0 | RadioOhNo | 4-way cross-modulation. Unstable shortwave radio. | +| C-6 | WhoKnows | Audio-rate filter sweeps. Wild and unpredictable. | +| A-4 | CrossModRing | Triple cascaded ring mod. Maximum sideband chaos. | + +**Tip:** Harsh programs respond dramatically to the analog resonance control. Crank the module's RES knob with these for screaming filter peaks. + +--- + +### 🥁 PERCUSSIVE / RHYTHMIC — Pulses, clicks, patterns + +Programs that produce discrete events, rhythmic patterns, or percussive textures. From sparse droplets to dense crackle. Useful as rhythmic noise sources, triggers for other modules, or standalone percussion. + +| Program | Name | Character | +|---------|------|-----------| +| D-1 | PluckCloud | Random plucked strings. Kalimba in the rain. | +| D-8 | IceRain | Sparse droplets + reverb tails. Crystalline ambient percussion. | +| E-3 | StochasticPulse | Random metallic pings with resonant filter. Geiger counter. | +| E-8 | SubHarmonic | Square wave + sub-octave dividers. Tectonic bass pulse. | +| A-9 | Basurilla | Noise gated by 3 pulse LFOs. Complex rhythmic chopping. | +| F-0 | PulseWander | Organic random walk pulses. Breathing rhythm. | +| F-1 | TwinPulse | Two correlated pulse streams. Polyrhythmic interference. | +| F-7 | NoiseBurst | Sporadic noise bursts with envelope. Rain on window. | +| F-4 | ShiftPulse | 4-stage shift register. Cascading pulse echoes. | +| E-2 | CellularSynth | Cellular automaton patterns. Self-rewriting music box. | + +**Tip:** PluckCloud (D-1) and IceRain (D-8) are the most "musical" percussive sources. Use the analog lowpass filter to tame the high end for a warmer, more natural percussion texture. + +--- + +### 🌊 TEXTURAL / AMBIENT — Atmospheric, spatial, enveloping + +Smooth, evolving, atmospheric textures. Filtered noise, spatial effects, organic movement. Ideal for ambient soundscapes, film sound design, and background layers. + +| Program | Name | Character | +|---------|------|-----------| +| D-3 | FormantNoise | Ghost choir morphing through vowels. Robotic whispers. | +| D-5 | FlangeNoise | Swept comb filter on noise. Jet engine, ocean waves. | +| C-9 | Rwalk_LFree | PWM random walk + clean reverb. Dreamy, spacious. | +| C-3 | S_H | Sample-hold + reverb. Ambient stochastic clicks. | +| C-5 | ExistenceIsPain | 4 slow LFO-swept filters. Deep, breathing, organic. | +| A-1 | Rwalk_SineFMFlange | FM + random walk + flanger. Complex atmospheric. | +| F-6 | NoiseSlew | Slew-limited noise. From smooth waves to hiss. | +| B-7 | FibonacciCluster | Golden ratio spacing. Naturally balanced shimmer. | +| C-2 | WalkingFilomena | 16 random-walk oscillators. Living, breathing mass. | +| B-3 | SineFMcluster | Subharmonic FM. Warm analog ensemble. | + +**Tip:** These textures shine in stereo mode. FormantNoise (D-3) in stereo with XB at ~0.4 creates a wide vocal-like wash where each channel whispers slightly different vowels. + +--- + +### 🔮 CHAOTIC / EXPERIMENTAL — Unpredictable, mathematical, alien + +Programs based on mathematical chaos, digital logic, and stochastic processes. Sounds that don't exist in the acoustic world. For experimental music, generative systems, and sound design. + +| Program | Name | Character | +|---------|------|-----------| +| E-0 | LogisticNoise | Logistic map. Order dissolving into chaos. Period-3 windows. | +| E-1 | HenonDust | Henon attractor. Alien vinyl crackle. | +| E-2 | CellularSynth | Cellular automaton. Self-rewriting alien music box. | +| F-2 | QuantPulse | Quantized random (1-6 bits). Digital stepped textures. | +| F-3 | ShapedPulse | Shaped probability distribution. Uniform to gaussian. | +| F-5 | MetallicNoise | 6 inharmonic squares (cymbal technique). Pure metallic. | +| F-8 | LFSRNoise | Shift register sequences. 4-bit retro digital patterns. | +| F-9 | DualPulse | Two S&H at independent rates. Polyrhythmic steps. | +| C-4 | ArrayOnTheRocks | Corrupted wavetable + FM. Broken digital beauty. | +| A-2 | xModRingSqr | Cross-FM ring mod squares. Metallic clang. | + +**Tip:** LogisticNoise (E-0) is the most unique program in the module. Slowly sweep X from left to right to hear the transition from periodic order through bifurcation into full chaos. The period-3 window at X≈0.66 is a brief island of order in the noise — genuinely beautiful mathematics made audible. + +--- + +## Versatile Programs + +These programs change category dramatically depending on knob positions: + +| Program | Y low | Y high | Range | +|---------|-------|--------|-------| +| **E-5 FeedbackFM** | Pure sine (DRONE) | White noise (HARSH) | The entire timbral spectrum in one knob | +| **E-6 RunawayFilter** | Clean whistle (DRONE) | Resonant noise (TEXTURAL) | Self-oscillation to noise wash | +| **D-2 TubeResonance** | Hollow drone (DRONE) | Breathy pipe (TEXTURAL) | Self-resonance to wind | +| **E-2 CellularSynth** | Steady drone (DRONE, simple rules) | Rapid mutation (CHAOTIC, Rule 30) | Order to chaos via rule selection | +| **D-4 BowedMetal** | Struck cymbal (PERCUSSIVE, Y low) | Infinite shimmer (DRONE, Y high) | Hit to sustain | +| **B-8 PartialCluster** | Dense unison (DRONE, Y≈1) | Metallic bells (CHAOTIC, Y>2) | Harmonic to inharmonic | +| **C-7 SatanWorkout** | Raw noise texture (HARSH) | Self-exciting reverb feedback (EXPERIMENTAL) | Noise to feedback | diff --git a/NOISE_PLETHORA_PLUGIN_GUIDE.md b/NOISE_PLETHORA_PLUGIN_GUIDE.md new file mode 100644 index 0000000..d1d11ec --- /dev/null +++ b/NOISE_PLETHORA_PLUGIN_GUIDE.md @@ -0,0 +1,1034 @@ +# Noise Plethora — Complete Plugin Guide + +*Befaco Noise Plethora v1.5 + Banks D, E & F Extension* + +## About This Guide + +The Noise Plethora is a Eurorack noise workstation with 3 digital sound generators (A, B, and C), each followed by an analog multimode filter. Generators A and B run interchangeable algorithms organized in banks of 10. Each algorithm has two parameters controlled by the **X** and **Y** knobs (and their corresponding CV inputs, 0–10Vpp). + +This guide documents all 60 algorithms across 6 banks: + +| Bank | Name | Programs | Theme | +|------|------|----------|-------| +| **A** | Textures | 0–9 | Cross-modulation, ring mod, granular, noise AM | +| **B** | HH Clusters | 0–9 | Additive/cluster synthesis with 6–16 oscillators | +| **C** | Harsh & Wild | 0–9 | Bitcrushing, random walks, chaotic oscillators | +| **D** | Resonant Bodies | 0–9 | Delay-based resonance, physical modeling, spectral shaping | +| **E** | Chaos Machines | 0–9 | Deterministic chaos, algorithmic processes, digital manipulation | +| **F** | Stochastic | 0–9 | Random pulses, metallic noise, LFSR sequences, noise textures | + +Banks A–C are the original Befaco firmware. Banks D–F are community extensions. + +--- + +## Table of Contents + +- [Bank A: Textures](#bank-a-textures) + - [A-0: RadioOhNo](#a-0-radioohno) — Cross-modulated square waves + - [A-1: Rwalk_SineFMFlange](#a-1-rwalk_sinefmflange) — Random walk FM + flanger + - [A-2: xModRingSqr](#a-2-xmodringsqr) — Cross FM square ring mod + - [A-3: XModRingSine](#a-3-xmodringsine) — Cross FM sine ring mod + - [A-4: CrossModRing](#a-4-crossmodring) — 4-osc cascaded ring mod + - [A-5: Resonoise](#a-5-resonoise) — Wavefolder-controlled resonant filter + - [A-6: GrainGlitch](#a-6-grainglitch) — Granular + XOR + - [A-7: GrainGlitchII](#a-7-grainglitchii) — Granular amplified + - [A-8: GrainGlitchIII](#a-8-grainglitchiii) — Granular sawtooth + - [A-9: Basurilla](#a-9-basurilla) — Noise gated by 3 pulse LFOs +- [Bank B: HH Clusters](#bank-b-hh-clusters) + - [B-0: ClusterSaw](#b-0-clustersaw) — 16 sawtooths, geometric spread + - [B-1: PwCluster](#b-1-pwcluster) — 6 pulse waves, adjustable PW + - [B-2: CrCluster2](#b-2-crcluster2) — 6 sines, single FM modulator + - [B-3: SineFMcluster](#b-3-sinefmcluster) — 6 triangles, independent sub-FM + - [B-4: TriFMcluster](#b-4-trifmcluster) — 6 triangles, ultra-slow FM + - [B-5: PrimeCluster](#b-5-primecluster) — 16 oscillators at prime frequencies + - [B-6: PrimeCnoise](#b-6-primecnoise) — Primes, wider range variant + - [B-7: FibonacciCluster](#b-7-fibonaccicluster) — 16 sawtooths, Fibonacci spacing + - [B-8: PartialCluster](#b-8-partialcluster) — 16 sawtooths, multiplicative spread + - [B-9: PhasingCluster](#b-9-phasingcluster) — 16 squares with 16 independent LFOs +- [Bank C: Harsh & Wild](#bank-c-harsh--wild) + - [C-0: BasuraTotal](#c-0-basuratotal) — LFSR-triggered oscillator + reverb + - [C-1: Atari](#c-1-atari) — 8-bit cross-modulation + - [C-2: WalkingFilomena](#c-2-walkingfilomena) — 16 random-walk oscillators + - [C-3: S_H](#c-3-s_h-sample--hold) — Sample & hold + reverb + - [C-4: ArrayOnTheRocks](#c-4-arrayontherocks) — Randomized wavetable FM + - [C-5: ExistenceIsPain](#c-5-existenceispain) — 4 LFO-swept bandpass filters + - [C-6: WhoKnows](#c-6-whoknows) — Fast LFO bandpass filters + - [C-7: SatanWorkout](#c-7-satanworkout) — Pink noise FM + reverb + - [C-8: Rwalk_BitCrushPW](#c-8-rwalk_bitcrushpw) — 9 random walks, 1-bit crush + - [C-9: Rwalk_LFree](#c-9-rwalk_lfree) — 4 PWM random walks + reverb +- [Bank D: Resonant Bodies](#bank-d-resonant-bodies) + - [D-0: CombNoise](#d-0-combnoise) — 4 parallel comb filters + - [D-1: PluckCloud](#d-1-pluckcloud) — 6 Karplus-Strong voices + - [D-2: TubeResonance](#d-2-tuberesonance) — Harmonic comb filter tube + - [D-3: FormantNoise](#d-3-formantnoise) — Vowel formant filters + - [D-4: BowedMetal](#d-4-bowedmetal) — 8 metallic resonators + - [D-5: FlangeNoise](#d-5-flangenoise) — Pink noise flanger + - [D-6: NoiseBells](#d-6-noisebells) — 4 sharp bell resonators + - [D-7: NoiseHarmonics](#d-7-noiseharmonics) — Wavefolder + bandpass + - [D-8: IceRain](#d-8-icerain) — S&H droplets + reverb + - [D-9: DroneBody](#d-9-dronebody) — Cross-FM drone + wavefolder +- [Bank E: Chaos Machines](#bank-e-chaos-machines) + - [E-0: LogisticNoise](#e-0-logisticnoise) — Logistic map chaos + - [E-1: HenonDust](#e-1-henondust) — Henon attractor crackle + - [E-2: CellularSynth](#e-2-cellularsynth) — Cellular automaton + 8 oscillators + - [E-3: StochasticPulse](#e-3-stochasticpulse) — Random impulses + resonant filter + - [E-4: BitShift](#e-4-bitshift) — Two sawtooths XOR'd + - [E-5: FeedbackFM](#e-5-feedbackfm) — Self-feedback FM sine + - [E-6: RunawayFilter](#e-6-runawayfilter) — Self-oscillating SVF + - [E-7: GlitchLoop](#e-7-glitchloop) — Loop with bit degradation + - [E-8: SubHarmonic](#e-8-subharmonic) — Frequency dividers + - [E-9: DualAttractor](#e-9-dualattractor) — Coupled Lorenz oscillators +- [Bank F: Stochastic](#bank-f-stochastic) + - [F-0: PulseWander](#f-0-pulsewander) — Smooth random walk pulses + - [F-1: TwinPulse](#f-1-twinpulse) — Two correlated random walks + - [F-2: QuantPulse](#f-2-quantpulse) — Quantized random (1-6 bits) + - [F-3: ShapedPulse](#f-3-shapedpulse) — Shaped probability distribution + - [F-4: ShiftPulse](#f-4-shiftpulse) — 4-stage shift register sequences + - [F-5: MetallicNoise](#f-5-metallicnoise) — 6 inharmonic square oscillators + - [F-6: NoiseSlew](#f-6-noiseslew) — Noise with adjustable LP slew + - [F-7: NoiseBurst](#f-7-noiseburst) — Sporadic noise bursts + - [F-8: LFSRNoise](#f-8-lfsrnoise) — LFSR pseudo-random sequences + - [F-9: DualPulse](#f-9-dualpulse) — Two S&H at independent rates + +--- + +## Bank A: Textures + +Cross-modulation, ring modulation, granular processing, and noise AM. Focused on complex modulation techniques that create rich, evolving textures. + +--- + +### A-0: RadioOhNo + +**Four square wave oscillators cross-modulated in couples, summed together.** + +Four pulse-width-modulated oscillators interconnected in feedback pairs: oscillator 1 modulates 2 and vice versa, oscillator 3 modulates 4 and vice versa. A DC source modulates all oscillators' frequency. The cross-modulation creates complex sidebands and chaotic harmonic interactions. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Oscillator 1 frequency | 20 – 2520 Hz | Base frequency for all 4 oscillators (each at a different ratio). Quadratic mapping for fine control at low frequencies. | +| **Y (k2)** | PWM / DC modulation | 0 – 1 | Controls DC amplitude which modulates all oscillators' frequency. Higher values create wider FM deviation and more chaotic sidebands. | + +**Sound character:** Aggressive, unstable radio-frequency-like textures. Like tuning between shortwave stations. Rich in aliasing and intermodulation products. + +--- + +### A-1: Rwalk_SineFMFlange + +**Four random walkers controlling pulse oscillators, FM'd by sines, through a flanger.** + +Four 2D random walkers move in a bounded box with random velocities. Their positions map to the frequencies of 4 pulse wave oscillators, which feed into 4 sine FM oscillators. The mixed output passes through a flanger effect. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | FM sine frequency | 10 – 510 Hz | Base frequency of the 4 sine FM oscillators (each offset by 55–75 Hz). | +| **Y (k2)** | Flanger modulation rate | 0 – 3 Hz | LFO speed of the flanger. Adds swept comb filtering on top of the FM textures. | + +**Sound character:** Morphing, evolving FM textures with organic movement. The flanger adds spatial depth and sweeping spectral animation. No two moments are alike. Atmospheric and complex. + +--- + +### A-2: xModRingSqr + +**Cross FM between two square wave oscillators, ring modulated.** + +Two square wave oscillators in a feedback configuration: each modulates the other's frequency. Their outputs are ring modulated (multiplied) together. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Oscillator 1 frequency | 100 – 5100 Hz | Pitch of the first square wave. Quadratic mapping. | +| **Y (k2)** | Oscillator 2 frequency | 20 – 1020 Hz | Pitch of the second square wave. Quadratic mapping. | + +**Sound character:** Metallic, clanging ring modulation. At simple ratios (2:1, 3:2) = tonal metallic tones. At complex ratios = inharmonic metallic clatter. + +--- + +### A-3: XModRingSine + +**Cross FM between two sine oscillators, ring modulated.** + +Same topology as xModRingSqr but using sine FM oscillators. Sines produce cleaner, more bell-like results than squares. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Oscillator 1 frequency | 100 – 8100 Hz | Pitch of the first sine. Quadratic mapping. Wide range. | +| **Y (k2)** | Oscillator 2 frequency | 60 – 3060 Hz | Pitch of the second sine. Quadratic mapping. | + +**Sound character:** Bell-like and metallic tones. All spectral content comes from the FM and ring modulation. Good for tuned metallic percussion sounds. + +--- + +### A-4: CrossModRing + +**Four oscillators in a cross-modulation matrix with cascaded ring modulators.** + +Four oscillators (2 square, 1 sawtooth with offset, 1 square) modulate each other's frequencies. Three ring modulators in a cascaded configuration: multiply1 and multiply2 feed into multiply3. The most complex modulation topology in the module. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Frequency (all oscillators) | 1 – 827 Hz | Scales all 4 oscillator frequencies with individual ratios. | +| **Y (k2)** | FM depth | 2 – 10 octaves | Master frequency modulation depth. Low = subtle. High = total harmonic chaos. | + +**Sound character:** Incredibly dense sideband spectrum. From metallic drones at low FM depth to total chaos at high depth. Like a 4-operator FM synth pushed to extremes. + +--- + +### A-5: Resonoise + +**Square wave FM-modulating a sine, through a wavefolder, controlling a resonant filter on white noise.** + +A modulated square wave drives a sine FM oscillator. The sine passes through a wavefolder, then feeds into a state-variable filter as the frequency control signal. White noise is the audio input to the filter (resonance=3). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Both oscillator frequencies | 20 – 10020 Hz | Controls the modulation rate of the filter cutoff. | +| **Y (k2)** | Wavefold amount | 0.03 – 0.23 | DC bias into the wavefolder. More folding = more complex filter modulation pattern. | + +**Sound character:** Resonant noise with animated filter modulation. Like white noise through an auto-wah driven by a complex LFO. Organic and vocal. + +--- + +### A-6: GrainGlitch + +**Square wave through a granular cell, output XOR'd with the input.** + +A square wave feeds into a granular pitch-shifting processor with feedback. The original square wave and the granular output are combined using XOR digital logic. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Square wave frequency | 500 – 5500 Hz | Base frequency of the source oscillator. | +| **Y (k2)** | Grain size + speed | 25–100 ms / 0.125–8x | Grain size (pitch shift window) and playback speed ratio. | + +**Sound character:** Glitchy digital artifacts from XOR combination. Harsh, broken, digital — like a CD skipping. + +--- + +### A-7: GrainGlitchII + +**Square wave through a granular cell, amplified.** + +Similar to GrainGlitch but without XOR. Granular output amplified 32000x to bring up the quiet signal. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Square wave frequency | 500 – 5500 Hz | Base frequency of the source oscillator. | +| **Y (k2)** | Grain size + speed | 25–100 ms / 0.125–8x | Same mapping as GrainGlitch. | + +**Sound character:** Cleaner granular texture than GrainGlitch. More transparent and "musical" than the XOR version. + +--- + +### A-8: GrainGlitchIII + +**Sawtooth wave through a granular cell.** + +Similar to GrainGlitchII but using a sawtooth waveform. Richer harmonic spectrum provides more material for the granular processor. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Sawtooth frequency | 400 – 5500 Hz | Base frequency. | +| **Y (k2)** | Grain size + speed | 25–80 ms / 0.125–8x | Slightly narrower grain range than I/II. | + +**Sound character:** The richest granular variant. Textures from buzzy granular drones to scattered metallic fragments. + +--- + +### A-9: Basurilla + +**White noise amplitude-modulated by 3 independent pulse wave LFOs.** + +Three parallel channels: white noise is ring-modulated by 3 pulse wave oscillators at different rates and pulse widths. The pulse waves gate the noise in complex rhythmic patterns. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | LFO frequencies | 0.1 – 110 Hz | Controls all 3 pulse LFOs. Low = slow gates. High = audio-rate AM. | +| **Y (k2)** | Pulse widths / noise level | PW + inverse amplitude | Pulse width of the 3 LFOs and inversely controls noise amplitude. | + +**Sound character:** Chaotic, granular-like texture from noise gated by 3 independent pulse trains. "Basurilla" = "little garbage" in Spanish — beautifully organized trash. + +--- + +## Bank B: HH Clusters + +Cluster/additive synthesis using 6–16 oscillators at mathematically related frequency ratios. Dense, shimmering tonal textures. + +--- + +### B-0: ClusterSaw + +**16 sawtooth oscillators with adjustable geometric frequency spread.** + +Sixteen sawtooths tuned in geometric progression. Each frequency is the previous one multiplied by a constant factor. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 20 – 1020 Hz | Fundamental frequency. | +| **Y (k2)** | Spread factor | 1.01 – 1.91 | Ratio between adjacent oscillators. Low = beating unison. High = wide harmonic spread. | + +**Sound character:** Shimmering, buzzing mass of sawtooths. The quintessential "cluster" sound. 16 voices create an extremely rich spectral texture. + +--- + +### B-1: PwCluster + +**6 detuned pulse waveforms with adjustable pulse width.** + +Six pulse waves at fixed detuned ratios (1.227, 1.24, 1.17, 1.2, 1.3). DC-controlled pulse width. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 40 – 8040 Hz | Widest range of any cluster plugin. | +| **Y (k2)** | Pulse width | Wide – Narrow (inverted) | Low Y = full, warm. High Y = thin, nasal. | + +**Sound character:** Chorused pulse waves. At wide PW: organ-like. At narrow PW: reedy, nasal. Constant beating from non-harmonic detuning. + +--- + +### B-2: CrCluster2 + +**6 detuned sine waves with low-frequency FM from a single modulator.** + +Six sines, all FM'd by one sine at 2.7x the base frequency. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 40 – 8040 Hz | Cluster fundamental. | +| **Y (k2)** | FM index | 0 – 1.0 | Low = pure sine cluster. High = bright FM harmonics. | + +**Sound character:** Clean sine cluster that becomes progressively FM-modulated. Warm to bright. Spectrally coherent. + +--- + +### B-3: SineFMcluster + +**6 triangle waves, each independently FM'd by its own sine modulator (subharmonic ratio 0.333).** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 300 – 8300 Hz | Higher starting frequency. | +| **Y (k2)** | FM index | 0.1 – 1.0 | Subharmonic FM adds content below carriers. | + +**Sound character:** Warm, soft FM cluster. Triangle carriers + subharmonic FM = "analog" feeling warmth and depth. + +--- + +### B-4: TriFMcluster + +**6 triangle waves, each independently FM'd at very slow ratio (0.07).** + +Same architecture as SineFMcluster but with 1/14th FM ratio. Creates phasing/chorus rather than FM timbral change. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 300 – 8300 Hz | Same range as SineFMcluster. | +| **Y (k2)** | FM index | 0.1 – 1.0 | Creates slow drifting detuning, not bright harmonics. | + +**Sound character:** Lush, animated ensemble effect. Like 6 slightly out-of-tune instruments. Very different from SineFMcluster despite identical architecture. + +--- + +### B-5: PrimeCluster + +**16 oscillators tuned to prime number frequencies, noise-modulated.** + +Sixteen variable-triangle oscillators at primes (53, 127, 199... 1523 Hz), all scaled by a common factor. White noise FM adds animation. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Pitch multiplier | 0.5 – 10.5 | Scales all 16 primes. Linear mapping. | +| **Y (k2)** | Noise modulation | 0 – 0.2 | Noise FM amount. Low = clean. High = tremolo-like. | + +**Gain:** 0.8 (reduced to prevent clipping). + +**Sound character:** Inharmonic, bell-like chord. No common harmonics — a unique shimmering metallic texture. + +--- + +### B-6: PrimeCnoise + +**16 triangle oscillators at prime frequencies (wider range variant).** + +Nearly identical to PrimeCluster but with quadratic pitch mapping (k1^2 x 12) for wider range. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Pitch multiplier | 0.5 – 12.5 | Quadratic mapping — more extreme range. | +| **Y (k2)** | Noise modulation | 0 – 0.2 | Same as PrimeCluster. | + +**Gain:** 0.8 + +**Sound character:** Same prime character, different response to knob gestures. More exponential/extreme than PrimeCluster. + +--- + +### B-7: FibonacciCluster + +**16 sawtooth oscillators at Fibonacci-series frequency spacing.** + +Frequencies follow `f(n) = f(n-1) + f(n-2) x spread`. Noise FM adds animation. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 50 – 1050 Hz | Fundamental pitch. | +| **Y (k2)** | Spread factor | 0.1 – 0.6 | How fast frequencies diverge. Near 0.618 = golden ratio intervals. | + +**Sound character:** Nature's favorite ratio. At spread near golden ratio, intervals feel "naturally balanced." Unique organic quality. + +--- + +### B-8: PartialCluster + +**16 sawtooth oscillators with multiplicative harmonic spacing.** + +Each frequency is the previous multiplied by a spread factor. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Base frequency | 50 – 1050 Hz | Fundamental. | +| **Y (k2)** | Harmonic spread | 1.01 – 2.11 | At 2.0 = octave spacing. Below = dense. Above = stretched/metallic. | + +**Sound character:** The most "tuneable" cluster. Morph from dense unison to harmonic series to metallic bells by sweeping Y. + +--- + +### B-9: PhasingCluster + +**16 square waves with individual LFO phase modulation.** + +Each oscillator has its own triangle LFO at a unique rate: 10, 11, 15, 1, 1, 3, 17, 14, 0.11, 5, 2, 7, 1, 0.1, 0.7, 0.5 Hz. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Carrier frequency | 30 – 5030 Hz | Base frequency for all 16 squares. | +| **Y (k2)** | Spread | 1.0 – 1.5 | Spreads carrier frequencies slightly. | + +**Sound character:** Massive phasing. 16 oscillators drifting at their own rates — constantly evolving, never-repeating interference patterns. Glacial slow LFOs (0.1 Hz) + shimmer (17 Hz). + +--- + +## Bank C: Harsh & Wild + +Extreme processing: bitcrushing, random walks, chaotic oscillators, reverb abuse. The most experimental and aggressive bank. + +--- + +### C-0: BasuraTotal + +**"Bent" LFSR driving an oscillator with reverb.** + +A Galois LFSR generates pseudo-random timing events that trigger a waveform oscillator. Output through Freeverb. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Oscillator frequency | 200 – 5200 Hz | Pitch of triggered waveform. | +| **Y (k2)** | Event rate | Slow – Fast | Timing between LFSR-triggered events. | + +**Sound character:** Sporadic bursts with reverb tails. "BasuraTotal" = "Total Garbage" — chaotic, messy, and beautiful. + +--- + +### C-1: Atari + +**Two square waves with PWM/FM cross-modulation.** + +First oscillator does PWM on the second, second does FM on the first. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Wave 1 frequency | 10 – 60 Hz | Sub-bass territory. | +| **Y (k2)** | Wave 2 freq + FM depth | 10–210 Hz / 3–11 oct | Higher pitch AND more extreme modulation. | + +**Sound character:** Classic 8-bit Atari/chiptune. Buzzy, aggressive, nostalgic. + +--- + +### C-2: WalkingFilomena + +**16 random walkers controlling pulse oscillator frequencies.** + +Sixteen pulse oscillators with independent 2D random walkers in a bounded box. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Random walk box size | 200 – 1800 Hz | Frequency range of all 16 oscillators. | +| **Y (k2)** | Pulse width | 0.1 – 0.9 | Thin = bright/nasal. Wide = warm/full. | + +**Sound character:** Shimmering, constantly evolving polyphonic texture. 16 oscillators wandering in pitch space. Organic and alive. + +--- + +### C-3: S_H (Sample & Hold) + +**Sample-and-hold noise with dirty reverb.** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | S&H rate | 15 – 5015 Hz | Slow = stepped tones. Fast = granular noise. | +| **Y (k2)** | Reverb mix | Dry – Wet | Dry = stochastic clicks. Wet = ambient wash. | + +**Sound character:** Classic S&H randomness with spatial depth from reverb. + +--- + +### C-4: ArrayOnTheRocks + +**FM synthesis with a randomized 256-point wavetable.** + +A 500 Hz sine modulates an arbitrary waveform with randomly corrupted table values. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Wavetable oscillator freq | 10 – 10010 Hz | Playback speed. | +| **Y (k2)** | FM modulator amplitude | 0 – 1.0 | Low = clean wavetable. High = rich FM sidebands. | + +**Sound character:** Unpredictable timbral textures from the corrupted wavetable. Different from standard FM because the carrier waveform itself is partially random. + +--- + +### C-5: ExistenceIsPain + +**Sample-and-hold noise through 4 bandpass filters modulated by 4 LFOs.** + +S&H source into 4 parallel SVF bandpass filters with independent triangle LFOs at 11, 70, 23, 0.01 Hz. Resonance=5. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | S&H noise frequency | 50 – 5050 Hz | Base texture density. | +| **Y (k2)** | Filter sweep range | 0.3 – 3.3 octaves | Narrow = focused. Wide = dramatic animation. | + +**Sound character:** Slowly breathing, organic multiband filtering. The 0.01 Hz LFO creates glacial changes, 70 Hz adds shimmer. Deep, meditative, slightly ominous. + +--- + +### C-6: WhoKnows + +**Pulse wave through 4 bandpass filters with fast LFO modulation.** + +Similar to ExistenceIsPain but with 5 Hz pulse source and much faster LFOs (21, 70, 90, 77 Hz). Resonance=7. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Pulse frequency | 15 – 515 Hz | Source frequency. | +| **Y (k2)** | Filter sweep range | 0.3 – 6.3 octaves | At high settings, filters sweep wildly. | + +**Sound character:** Dramatic, rapidly animated filter textures. Audio-rate LFOs create sideband-like effects. Aggressive and chaotic. + +--- + +### C-7: SatanWorkout + +**Pink noise FM-ing a PWM oscillator, through reverb.** + +Pink noise modulates a PWM oscillator. Freeverb with negative damping (-5). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | PWM frequency | 8 – 6008 Hz | "Pitch" of noise modulation. | +| **Y (k2)** | Reverb room size | 0.001 – 4.0 | Beyond 1.0 = self-exciting reverb feedback. | + +**Sound character:** Raw organic noise with reverb. At high Y the reverb self-excites. Relentless intensity. + +--- + +### C-8: Rwalk_BitCrushPW + +**9 random-walk pulse oscillators, bitcrushed to 1 bit, with reverb.** + +Nine pulse oscillators with 2D random walkers. Mixed, 1-bit crushed, parallel reverb path. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Pulse width | 0.2 – 0.75 | Timbre control alongside random walk frequencies. | +| **Y (k2)** | Reverb room size | 0 – 4.0 | High = self-exciting reverb. | + +**Sound character:** Extreme digital destruction. Dense, distorted digital texture with organic pitch movement from random walks. Lo-fi at its most extreme. + +--- + +### C-9: Rwalk_LFree + +**4 PWM oscillators with random-walk frequencies, through reverb.** + +Four PWM oscillators with independent random walkers. Clean Freeverb (no bitcrushing). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Random walk boundary | 50 – 550 Hz | Frequency space size. | +| **Y (k2)** | Reverb room size | 0 – 1.0 | Conservative range, clean reverb. | + +**Sound character:** Spacious, clean random pulse textures. Warm timbre, gentle pitch drift, dreamy ambient reverb. The gentlest of the random walk family. + +--- + +## Bank D: Resonant Bodies + +Delay-based resonance, physical modeling, and spectral shaping. What happens when noise passes through resonant structures: comb filters, waveguides, feedback networks, and high-Q resonators. + +--- + +### D-0: CombNoise + +**White noise through 4 parallel comb filters.** + +A comb filter is a short delay line with feedback — it reinforces frequencies at the delay time and its harmonics. Four combs at ratios 1.0 : 0.7 : 0.5 : 0.35 create a rich, multi-pitched metallic tone. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Resonant pitch | 40 – 5000 Hz | Fundamental frequency of all 4 comb filters. | +| **Y (k2)** | Feedback | 0 – 95% | Resonance sustain. Low = colored noise. High = clearly pitched ringing. | + +**Sound character:** Like blowing across a metal pipe. The 4 overlapping combs create a rich harmonic spectrum. + +--- + +### D-1: PluckCloud + +**6 Karplus-Strong plucked string voices with random retriggering.** + +Noise burst into delay line with LP filter in feedback loop. Voices retrigger randomly (~2% per cycle). Detuned at ratios 1.0, 1.005, 0.995, 1.01, 0.99, 1.015. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Pitch center | 60 – 3000 Hz | Base pitch for all 6 voices. | +| **Y (k2)** | Brightness / decay | Dull – Bright | Low = muted string, fast decay. High = bright ring, long sustain. | + +**Sound character:** Rain on a kalimba, or a prepared piano played by dice. Sparse, organic, constantly evolving. + +--- + +### D-2: TubeResonance + +**Noise excitation through 4 harmonic comb filters simulating a resonant tube.** + +Four combs at 1x, 2x, 3x, 4x fundamental. Higher harmonics decay faster (feedback scaled by 1/harmonic). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Tube length | 40 – 1000 Hz | Fundamental pitch. Low = long tube. High = short pipe. | +| **Y (k2)** | Excitation | Self-resonance – Breath | Low = singing bowl drone. High = breathy wind-through-pipe. | + +**Sound character:** Hollow drone to breathy pipe texture. Harmonic series is clearly audible — sounds like a real resonant object. + +--- + +### D-3: FormantNoise + +**White noise through 3 bandpass filters at vowel formant frequencies.** + +Interpolates between 5 vowel shapes (A, E, I, O, U) using 3 SVF bandpass filters. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Vowel morph | A → E → I → O → U | Smooth interpolation between vowel formants. | +| **Y (k2)** | Resonance | Q 1.0 – 5.0 | Wide/breathy to sharp/nasal vowels. | + +**Formant table (Hz):** + +| Vowel | F1 | F2 | F3 | +|-------|-----|------|------| +| A | 730 | 1090 | 2440 | +| E | 660 | 1720 | 2410 | +| I | 270 | 2290 | 3010 | +| O | 570 | 840 | 2410 | +| U | 300 | 870 | 2240 | + +**Sound character:** Robotic choir whispering vowels. Sweep X for ghostly "aaah → eeeh → iiih → oooh → uuuh." + +--- + +### D-4: BowedMetal + +**Noise exciting 8 high-Q resonators at dense metallic frequency ratios.** + +Eight 2-pole resonators (Q ~300–500) at plate vibration mode ratios. Denser than NoiseBells = more reverberant. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Fundamental pitch | 30 – 2030 Hz | All 8 resonators scale proportionally. | +| **Y (k2)** | Struck → Bowed | Short ring – Infinite sustain | Low = cymbal hit. High = bowed singing bowl. | + +**Resonator ratios:** 1.000, 1.183, 1.506, 1.741, 2.098, 2.534, 2.917, 3.483 + +**Sound character:** Dense metallic reverberance. Bowed cymbal or gong. More diffuse than NoiseBells. + +--- + +### D-5: FlangeNoise + +**Pink noise through a flanger effect.** + +Swept comb filter on noise. Classic "jet engine whoosh." + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Flange rate | 0.05 – 5 Hz | Slow sweep to fast warble. | +| **Y (k2)** | Flange depth | Subtle – Deep | Phase-like to dramatic comb sweep. | + +**Sound character:** Jet plane flyover, ocean through metal tube, wind in rotating vent. + +--- + +### D-6: NoiseBells + +**White noise exciting 4 sharp 2-pole resonators at bell-like inharmonic ratios.** + +Extremely high Q (~625) creates laser-sharp peaks. Clear ringing tones from noise. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Bell pitch | 80 – 4080 Hz | Fundamental. All 4 resonators scale. | +| **Y (k2)** | Inharmonicity | Chime → Gong | Near-harmonic (wind chime) to stretched (gamelan). | + +**Resonator ratios (vary with Y):** + +| Resonator | Y=0 (chime) | Y=1 (gong) | +|-----------|-------------|------------| +| 1 | 1.00x | 1.00x | +| 2 | 1.59x | 2.48x | +| 3 | 2.33x | 4.33x | +| 4 | 3.14x | 6.35x | + +**Sound character:** Clear bell tones from noise. Visible on spectrum analyzer as 4 needle-sharp peaks. + +--- + +### D-7: NoiseHarmonics + +**White noise through a wavefolder, then resonant bandpass filter.** + +Wavefolder creates harmonics from noise (DC bias controlled). SVF filter (Q=3) selects a frequency band. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Wavefold intensity | Gentle – Heavy | More folding = denser harmonics. | +| **Y (k2)** | Filter frequency | 100 – 8100 Hz | Selects which band is heard. | + +**Sound character:** Aggressive buzzing. Like an angry bee swarm at a specific pitch. Grittier than plain filtered noise. + +--- + +### D-8: IceRain + +**Sparse sample-and-hold events with long reverb tails.** + +Random stepped values at controllable rate into Freeverb (high damping). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Droplet density | 0.5 – 200 Hz | Sparse plinks to dense rain. | +| **Y (k2)** | Reverb size | Small – Vast | Dry/close to cathedral-like. | + +**Sound character:** Crystalline drops in a cave. Sparse plinks with shimmering tails. Very spatial and ambient. + +--- + +### D-9: DroneBody + +**Two cross-modulated FM sines through a wavefolder.** + +Slightly detuned sines with block-delayed cross-feedback FM. Wavefolder adds harmonics. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Drone pitch | 20 – 520 Hz | Fundamental of both sines. | +| **Y (k2)** | Detune + Fold | Clean – Rich | Y=0: clean hum. Y=1: beating + growling overtones. | + +**Sound character:** Deep meditative drone. Tibetan singing bowl meets power transformer. Good for sustained bass. + +--- + +## Bank E: Chaos Machines + +Deterministic chaos, algorithmic processes, and digital manipulation. Mathematical systems that hover between order and noise. + +--- + +### E-0: LogisticNoise + +**Logistic map chaotic oscillator: `x(n+1) = r * x(n) * (1 - x(n))`** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Chaos parameter r | 3.5 – 4.0 | 3.5=periodic. 3.57=chaos onset. 3.83=period-3 window. 4.0=full chaos. | +| **Y (k2)** | Output rate | Slow – Fast | Held samples (buzzy tone) to per-sample (noise-like at r=4). | + +**Sound character:** Mathematical structure dissolving into chaos. Sweep r to hear windows of order breaking into noise. The period-3 window at r~3.83 is particularly striking. + +--- + +### E-1: HenonDust + +**Henon attractor: `x' = 1 - a*x^2 + y, y' = b*x`** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Parameter a | 1.15 – 1.40 | Low=periodic. ~1.2=period-doubling. 1.4=full chaos. | +| **Y (k2)** | Parameter b | 0.15 – 0.35 | Attractor shape and crackle density. | + +**Sound character:** Alien vinyl surface noise. Almost-repeating patterns that never quite repeat. Living, deterministic crackle. + +--- + +### E-2: CellularSynth + +**1D cellular automaton (32 cells) controlling 8 sawtooth oscillators at harmonics of 55 Hz.** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | CA rule | 0 – 255 | Rule 30=chaotic. Rule 90=fractal. Rule 110=complex. | +| **Y (k2)** | Evolution speed | Glacial – Rapid | How fast the CA evolves. | + +**Sound character:** Alien music box that rewrites its own score. Some rules = steady drone, others = constant mutation. + +--- + +### E-3: StochasticPulse + +**Poisson-distributed impulses through a resonant bandpass filter (Q=4.5).** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Impulse density | 5 – 505 /sec | Sparse pings to dense crackling. | +| **Y (k2)** | Filter frequency | 80 – 8080 Hz | Pitch of the resonant ring. | + +**Sound character:** Geiger counter meets resonant drum. Random metallic pings with clear musical pitch. + +--- + +### E-4: BitShift + +**Two sawtooth oscillators combined via XOR digital logic.** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Oscillator 1 freq | 20 – 5020 Hz | First sawtooth. | +| **Y (k2)** | Oscillator 2 ratio | 1:1 – 1:8 | Simple ratios = structured. Irrational = maximum complexity. | + +**Sound character:** Broken NES / ZX Spectrum. Hard digital edges, rapidly shifting sum/difference tones. Aggressive 8-bit. + +--- + +### E-5: FeedbackFM + +**Single sine with sample-delayed self-feedback into its own phase.** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Carrier frequency | 20 – 5020 Hz | Pitch. | +| **Y (k2)** | Feedback | 0 – 2.5 rad | 0=sine. ~0.8=bright. ~1.5=metallic. ~2.0=gritty. 2.5=noise. | + +**Sound character:** The full timbral spectrum in one knob. Classic DX7 operator technique. Sweet spot at Y~1.0 = vocal/nasal quality. + +--- + +### E-6: RunawayFilter + +**White noise through a self-oscillating SVF (Q ~4.5–4.99).** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Filter frequency | 30 – 10030 Hz | Pitched self-oscillation tone. | +| **Y (k2)** | Noise injection | Whistle – Noise | Y=0: clean sine whistle. Y=1: resonant noise. Transition zone = sweet spot. | + +**Sound character:** Pitched whistle fighting noise. Like tuning a shortwave radio. Fragile tone that breaks and reforms. + +--- + +### E-7: GlitchLoop + +**Noise captured into a loop with progressive bit-depth degradation.** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Loop length | 10 – 200 ms | Short = buzz. Long = stutter. | +| **Y (k2)** | Degradation rate | Stable – Rapid | How fast the loop crumbles. Refills when bit depth hits 1. | + +**Sound character:** Digital decay. Loop captures, degrades, collapses to 1-bit, refreshes. Rhythmic destruction cycle. + +--- + +### E-8: SubHarmonic + +**Square wave with frequency dividers at /2, /3, /5, /7.** + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Fundamental frequency | 100 – 4100 Hz | Source square wave pitch. | +| **Y (k2)** | Subharmonic mix | /2 only → All four | Progressive: Y=0: octave below. Y=0.33: +fifth below. Y=0.66: +major 3rd down. Y=1: +minor 7th down. | + +**Subharmonic intervals at 440 Hz:** /2=220 Hz, /3=~147 Hz, /5=88 Hz, /7=~63 Hz + +**Sound character:** Deep rumbling sub-bass. Earth-shaking low end at full Y with low fundamental. Tectonic. + +--- + +### E-9: DualAttractor + +**Two coupled Lorenz attractors driving two sine oscillators.** + +Lorenz system: `dx/dt = sigma(y-x)`, `dy/dt = x(rho-z)-y`, `dz/dt = xy-beta*z`. sigma=10, rho=28, beta=8/3. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Coupling strength | 0 – 5 | 0=independent chaos. 5=tones lock/unlock in bursts. | +| **Y (k2)** | Base frequency | 30 – 2030 Hz | Center pitch the attractors orbit. | + +**Sound character:** Two drunk musicians trying to play in unison. Moments of harmony interrupted by chaos. Organic, alive, never repeating. + +--- + +## Bank F: Stochastic + +Random processes generating pulses, transients, and noise textures. The pulse-based algorithms (F-0 through F-4) exploit the Noise Plethora's internal DC blocker to transform random stepped values into unique transient/pulse shapes — a character exclusive to this module's architecture. + +--- + +### F-0: PulseWander + +**Smooth random walk generating organic pulse patterns.** + +A random target value is chosen periodically, and a one-pole LP filter smoothly tracks it. The module's DC blocker transforms held values into distinctive pulse shapes with natural attack/decay envelopes. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Rate | 5 – 400 Hz | How often a new random target is chosen. Low = sparse, isolated pulses. High = dense, overlapping transients. | +| **Y (k2)** | Smoothness | Instant – Smooth | k2=0: instant jumps (sharp S&H pulses). k2=1: smooth glide between targets (softer, rounder transients). The LP filter alpha auto-scales with rate. | + +**Sound character:** Organic, irregular pulse train. Each pulse has a unique amplitude from the random walk. The smoothness control morphs from sharp clicks to rounded bumps. Unlike a regular clock, the randomness creates a "breathing" quality. + +--- + +### F-1: TwinPulse + +**Two random walks with controllable correlation creating crossed pulse patterns.** + +Two independent random walk generators running at the same rate. The correlation control blends the second walk toward the first, creating patterns that range from fully independent (complex interference) to identical (simple doubled pulse). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Rate | 5 – 400 Hz | Speed of both random walks. | +| **Y (k2)** | Correlation | Independent – Identical | 0 = two completely unrelated pulse streams (complex polyrhythmic interference). 1 = both outputs identical (single pulse stream, louder). Sweet spot around 0.3–0.7 where patterns partially align. | + +**Sound character:** Two overlapping pulse patterns. At low correlation, a complex, polyrhythmic texture where pulses sometimes align and sometimes don't. At high correlation, they merge into a single stream. The interplay creates a rhythmic quality that pure randomness lacks. + +--- + +### F-2: QuantPulse + +**Random values quantized to N bits, creating stepped pulse patterns.** + +Generates random values quantized to a variable number of discrete levels (2^bits). At 1 bit: binary (2 levels). At 6 bits: 64 levels. The DC blocker creates pulses whose amplitude is quantized to these levels. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Rate | 5 – 500 Hz | Clock speed. How often a new quantized random value is generated. | +| **Y (k2)** | Bit depth | 1 – 6 bits (continuous) | Quantization resolution. 1 bit = binary pulse (on/off). 2 bits = 4 levels. 6 bits = 64 levels (nearly continuous). Continuous morphing between bit depths. | + +**Sound character:** Quantized random pulses. At low bits: harsh, digital, with clearly discrete amplitude steps. At high bits: smoother, approaching the character of PulseWander. The bit quantization adds a lo-fi, digital texture to the random process. + +--- + +### F-3: ShapedPulse + +**Random with variable probability distribution shape.** + +The random values follow different probability distributions depending on Y. Uniform (flat — all values equally likely), triangular (values near center more likely), or peaked/gaussian (values strongly concentrated near center, rare extremes). + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Rate | 5 – 500 Hz | Clock speed. | +| **Y (k2)** | Distribution | Uniform → Triangular → Gaussian | 0 = uniform (all amplitudes equally likely — evenly distributed pulses). 0.5 = triangular (sum of 2 uniforms — moderate peak clustering). 1.0 = peaked (sum of 4 uniforms, central limit theorem — most pulses near zero, rare large ones). | + +**Sound character:** The distribution shape is audible. Uniform: even, balanced pulse amplitudes. Peaked: mostly quiet with occasional loud spikes — creates a "crackling" or "dripping" quality where large events are rare surprises. + +--- + +### F-4: ShiftPulse + +**4-stage shift register creating evolving pulse sequences.** + +A shift register where random values enter stage 1 and shift down to stages 2, 3, 4 on each clock tick. All 4 stages are mixed in the output with adjustable weighting, creating patterns that evolve as values propagate through the chain. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Clock rate | 5 – 300 Hz | Speed of the shift register clock. | +| **Y (k2)** | Stage mix | Stage 1 only → All 4 | Progressive activation: Y=0: only stage 1 (fastest changing). Y=0.33: adds stage 2 (delayed echo). Y=0.66: adds stage 3. Y=1.0: all 4 stages equally mixed. The delayed repetitions create rhythmic pattern echoes. | + +**Sound character:** A pulse pattern that develops delayed echoes of itself. At low Y: simple random pulses. As Y increases, you hear the same values repeated 1, 2, 3 steps later, creating a cascading effect — like an echo chamber for random events. + +--- + +### F-5: MetallicNoise + +**6 square wave oscillators at inharmonic frequencies mixed and highpass filtered.** + +The classic technique used in the TR-808 cymbal and hi-hat circuits: six pulse oscillators at non-harmonic frequency ratios, summed together. The resulting waveform has a dense, metallic spectral character that no single oscillator can produce. A highpass filter controls brightness. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Pitch multiplier | 0.5x – 2.5x | Scales all 6 base frequencies together. At 1.0x: original frequencies. Below 1.0: lower, darker. Above: higher, brighter. | +| **Y (k2)** | Brightness | 500 – 15500 Hz HPF | Highpass filter cutoff. Low = full-bodied metallic noise. High = thin, sizzling hi-hat territory. | + +**Base frequencies (Hz):** 205.3, 304.4, 369.6, 522.7, 800.6, 1053.4 + +**Sound character:** Unmistakably metallic. At low HPF: thick, crash cymbal-like. At high HPF: thin, closed hi-hat sizzle. The 6 inharmonic frequencies create dense beating patterns that give the "shimmer" characteristic of real cymbals. + +--- + +### F-6: NoiseSlew + +**White noise with adjustable one-pole lowpass slew.** + +A simple but effective texture generator: white noise passed through a one-pole LP filter with adjustable cutoff. At high cutoff: full noise. At low cutoff: smooth, slowly undulating random wave. A mix control adds raw noise texture on top of the slewed signal. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Smoothness | Smooth – Noisy | LP filter coefficient. Low = very smooth, slowly varying random wave. High = barely filtered, noise-like. | +| **Y (k2)** | Texture mix | Clean – Textured | Blends raw noise on top of the slewed signal. 0 = only slewed output. 1 = slewed + 30% raw noise for gritty texture. | + +**Sound character:** The full spectrum from smooth random undulation to white noise in one algorithm. At X=0: a gentle, wandering random wave. At X=1: nearly unfiltered noise. The texture mix adds a "grain" to the smooth version without overpowering it. + +--- + +### F-7: NoiseBurst + +**Sporadic bursts of white noise with silence between them.** + +Generates discrete events: random-length bursts of noise separated by silence. Each burst has a natural fade-out envelope (20-sample ramp) to prevent clicks. The density controls how often bursts occur. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Burst density | ~1/sec – ~130/sec | Average burst rate. Low = sparse, isolated events. High = dense but never continuous — always gaps between bursts. | +| **Y (k2)** | Burst length | 1 ms – 100 ms | Duration of each noise burst. Short = percussive clicks. Long = noise "grains." | + +**Sound character:** Sporadic noise events in silence. Like a Geiger counter, or rain hitting a window irregularly. At high density with short bursts: crackling granular texture. At low density with long bursts: isolated noise "drops" with natural decay. + +--- + +### F-8: LFSRNoise + +**16-bit Linear Feedback Shift Register with selectable tap configurations.** + +A classic LFSR pseudo-random sequence generator with 8 different feedback tap polynomials. Different taps produce different sequence lengths and spectral characteristics. Output quantized to 16 discrete levels (top 4 bits) for a recognizable stepped character. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | Clock rate | 5 – 500 Hz | How fast the register shifts. Low = slow stepped pattern. High = fast digital noise with pitch. | +| **Y (k2)** | Tap selection | 8 configurations | Selects from 8 feedback polynomials. Each produces a different sequence pattern — some nearly random, some with audible repetition. Sweep to find sweet spots. | + +**Sound character:** Digital pseudo-random sequences with a lo-fi, quantized character. Different taps create different "flavors" of digital noise — some more tonal, some more chaotic. The 16-level quantization gives it a distinctly stepped, 4-bit retro quality. + +--- + +### F-9: DualPulse + +**Two sample-and-hold generators running at independent rates, mixed together.** + +Two independent S&H circuits, each generating random values at their own clock rate. The outputs are mixed 50/50, creating complex polyrhythmic stepped patterns from the interaction of two independent clocks. + +| Parameter | Control | Range | Effect | +|-----------|---------|-------|--------| +| **X (k1)** | S&H 1 rate | 5 – 500 Hz | Clock speed of the first generator. | +| **Y (k2)** | S&H 2 rate | 5 – 500 Hz | Clock speed of the second generator. Independent of X. | + +**Sound character:** Two overlapping random step patterns. When the two rates are similar: slow beating between the patterns. When rates are very different: a fast pattern modulated by a slow one. At simple ratios (2:1, 3:2): hints of regularity emerge. At complex ratios: maximally unpredictable interaction. diff --git a/NOISE_PLETHORA_STEREO_GUIDE.md b/NOISE_PLETHORA_STEREO_GUIDE.md new file mode 100644 index 0000000..d33e018 --- /dev/null +++ b/NOISE_PLETHORA_STEREO_GUIDE.md @@ -0,0 +1,153 @@ +# Noise Plethora — Stereo Mode Guide + +*Befaco Noise Plethora v1.5 + Banks D, E & F Extension* + +## Table of Contents + +- [What It Does](#what-it-does) +- [How to Activate (VCV Rack)](#how-to-activate-vcv-rack) +- [How to Activate (Hardware)](#how-to-activate-hardware) +- [Controls in Stereo Mode](#controls-in-stereo-mode) +- [How It Works Internally](#how-it-works-internally) +- [Stereo Patch Ideas](#stereo-patch-ideas) + +--- + +## What It Does + +Stereo mode transforms the Noise Plethora from two independent mono generators into a single **stereo sound source**. Generator B automatically mirrors Generator A's algorithm, and the B knobs (XB/YB) are repurposed as stereo controls. + +The result: a wide stereo field from a single algorithm, where the left and right channels share the same pitch but differ in timbral character. The difference varies slowly over time, creating organic stereo movement. + +--- + +## How to Activate (VCV Rack) + +1. **Right-click** on the Noise Plethora module +2. Under "Stereo," check **"Stereo Mode"** +3. Both display dots light up to confirm stereo is active +4. To deactivate: right-click and uncheck + +--- + +## How to Activate (Hardware) + +**Double-click** the Program/Bank encoder (two quick clicks within 300ms): + +| Gesture | Action | +|---------|--------| +| Single click | Toggle between Gen A and Gen B (normal) | +| **Double-click** | **Toggle stereo mode** | +| Hold 400ms | Enter/exit bank mode | + +When stereo mode is active: +- Both dots on the 7-segment display light up simultaneously +- The display briefly shows "St" on activation and "no" on deactivation +- Both digits show the same program number + +--- + +## Controls in Stereo Mode + +| Control | Normal Mode | Stereo Mode | +|---------|------------|-------------| +| **XA knob** | k1 for Gen A | k1 for **both** (pitch/primary param) | +| **YA knob** | k2 for Gen A | k2 for **both** (timbre/secondary param) | +| **XB knob** | k1 for Gen B | **Stereo Width** (0=mono, 1=wide) | +| **YB knob** | k2 for Gen B | **Stereo Movement** (LFO speed for width modulation) | +| **CV XA** | CV for k1 A | CV for k1 **both** | +| **CV YA** | CV for k2 A | CV for k2 **both** | +| **CV XB** | CV for k1 B | Not used (manual only) | +| **CV YB** | CV for k2 B | Not used (manual only) | +| **Encoder** | Select program for A or B | Select program for **both** | +| **Filter A** | Filter for Gen A | Filter for **left channel** | +| **Filter B** | Filter for Gen B | Filter for **right channel** | + +**Note:** The analog filters remain fully independent in stereo mode. This is a feature — setting the two filters differently (e.g., LP on left, HP on right) adds an additional layer of stereo character on top of the digital decorrelation. + +--- + +## How It Works Internally + +``` + XA (k1) ─────────────────────→ Gen A ──→ Filter A ──→ LEFT + YA (k2) ─────────────────────→ │ + │ + │ (same algorithm) + │ + XA (k1) ─────────────────────→ Gen B ──→ Filter B ──→ RIGHT + YA (k2) + stereo offset ─────→ │ + │ + XB ──→ offset amount │ + YB ──→ offset LFO speed ──────────┘ +``` + +**The stereo offset is applied to k2 (timbre), not k1 (pitch).** This is critical — it means both channels always play at the same frequency, but with slightly different timbral character. This creates a natural, phase-free stereo width without the pitch-detuning artifacts of traditional chorus effects. + +The offset follows a slow sine LFO: + +``` +offset = XB × 0.4 × sin(2π × YB × 0.3 × time) +``` + +- **XB = 0**: offset is zero → mono (both channels identical) +- **XB = 0.5**: offset swings ±0.2 → moderate stereo width +- **XB = 1.0**: offset swings ±0.4 → maximum stereo width +- **YB = 0**: offset is static → fixed stereo spread +- **YB = 0.5**: offset moves slowly → gentle stereo movement +- **YB = 1.0**: offset moves faster → animated stereo field + +--- + +## Stereo Patch Ideas + +### 1. Immersive Bell Drone +- Program: **D-6 NoiseBells** +- Stereo Mode: ON +- XA: ~0.3 (medium bell pitch) +- YA: ~0.2 (nearly harmonic, chime-like) +- XB: 0.3 (moderate width) +- YB: 0.15 (very slow movement) +- Filter A: LP, cutoff medium, res low +- Filter B: LP, cutoff slightly higher than A +- *Result: wide, shimmering bell drone where each ear hears slightly different harmonic content* + +### 2. Vocal Wash +- Program: **D-3 FormantNoise** +- Stereo Mode: ON +- XA: modulated by slow LFO (vowel morphing) +- YA: ~0.6 (moderate resonance) +- XB: 0.5 (wide) +- YB: 0.2 (slow drift) +- Filter A: BP, cutoff ~1kHz +- Filter B: BP, cutoff ~1.5kHz +- *Result: ghostly stereo choir, each ear whispers a slightly different vowel* + +### 3. Metallic Stereo Texture +- Program: **D-4 BowedMetal** +- Stereo Mode: ON +- XA: ~0.4 +- YA: ~0.8 (long sustain, bowed character) +- XB: 0.6 (wide separation) +- YB: 0.3 (moderate movement) +- Filter A: HP, cutoff low (full spectrum) +- Filter B: LP, cutoff medium (darker) +- *Result: left ear = bright metallic shimmer, right ear = dark resonant glow. Complementary stereo.* + +### 4. Chaos Stereo Field +- Program: **E-9 DualAttractor** +- Stereo Mode: ON +- XA: ~0.5 (moderate coupling) +- YA: ~0.4 +- XB: 0.4 +- YB: 0.4 +- *Result: two Lorenz attractors already create wandering tones. In stereo mode, the additional timbral offset makes each channel's attractor feel independent while remaining harmonically related.* + +### 5. Wide Cluster +- Program: **B-0 ClusterSaw** +- Stereo Mode: ON +- XA: ~0.3 (low fundamental) +- YA: ~0.1 (tight cluster = thick unison) +- XB: 0.2 (subtle width) +- YB: 0.1 (very slow) +- *Result: massive wide chorused sawtooth wall. The subtle timbral difference between channels creates a huge sound from a simple cluster.* diff --git a/README.md b/README.md index 6f83b4c..42ad9b5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,85 @@ -# Noise Plethora Firmware v1.5 +# Noise Plethora Firmware -Tennsyduino >= V1.55 is neceesary +Extended firmware for the [Befaco Noise Plethora](https://www.befaco.org/noise-plethora/) Eurorack module. Based on the original v1.5 firmware by Jeremy Bernstein / Befaco, with 30 new algorithms and a stereo mode. -To update Firmware we recommend to use Hex file provided in Releases page. For the brave that will compile the code, keep reading. +## What's New -Dependencies: -- ADC (https://github.com/pedvide/ADC/releases) -- QuadEncoder (https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library) -- SevSeg (https://github.com/DeanIsMe/SevSeg) +- **30 new algorithms** across 3 additional banks (D, E, F) — total: 60 programs in 6 banks +- **Stereo mode** — both generators run the same algorithm with timbral decorrelation +- **Makefile** for easy build and flash via `arduino-cli` -To make things work You'll need to replace some files in the Teensy distribution. The files and a quick guide can be found in the 'teensy' folder in this repository. +See [NOISE_PLETHORA_PLUGIN_GUIDE.md](NOISE_PLETHORA_PLUGIN_GUIDE.md) for detailed documentation of all 60 algorithms. +See [NOISE_PLETHORA_CATEGORIES.md](NOISE_PLETHORA_CATEGORIES.md) for programs grouped by use case (Drone, Harsh, Percussive, Textural, Chaotic). +See [NOISE_PLETHORA_STEREO_GUIDE.md](NOISE_PLETHORA_STEREO_GUIDE.md) for stereo mode documentation and patch ideas. -Copyright (c) 2021 Befaco / Jeremy Bernstein -Open-source software -Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.See LICENSE.txt for the complete license text +## Banks -Plugins are licensed under CC-BY-SA v4. Check individual files +| Bank | Name | Theme | +|------|------|-------| +| A | Textures | Cross-modulation, ring mod, granular *(original)* | +| B | HH Clusters | Additive/cluster synthesis, 6–16 oscillators *(original)* | +| C | Harsh & Wild | Bitcrushing, random walks, reverb abuse *(original)* | +| **D** | **Resonant Bodies** | Comb filters, physical modeling, resonators | +| **E** | **Chaos Machines** | Logistic maps, attractors, cellular automata | +| **F** | **Stochastic** | Random pulses, metallic noise, LFSR sequences | + +## Stereo Mode + +Double-click the encoder to toggle. Both generators run the same algorithm, with XB controlling stereo width and YB controlling stereo movement. Both display dots light up when active. Double-click again to exit. + +## Build & Flash + +Requires [arduino-cli](https://arduino-cli.github.io/) with the Teensy board package installed. + +> **Tested on:** macOS Sequoia 15.4 (Darwin 25.4.0, Apple Silicon). Linux and Windows have not been tested — the Makefile and patch paths may need adjustment for those platforms. + +### First-time setup + +```bash +# Install arduino-cli +brew install arduino-cli + +# Add Teensy board support +arduino-cli config add board_manager.additional_urls https://www.pjrc.com/teensy/package_teensy_index.json +arduino-cli core update-index +arduino-cli core install teensy:avr + +# Install dependencies +arduino-cli lib install "SevSeg" +# ADC and QuadEncoder are included with the Teensy core +``` + +### Patch Teensy Audio Library and compile + +```bash +make setup # patch AudioStream files (required once, originals backed up) +make # compile firmware +make upload # compile + flash (connect Teensy via USB first) +make port # show available serial ports +make unpatch # restore original Teensy AudioStream files +make clean # remove build artifacts +``` + +To specify a port manually: `make upload PORT=/dev/cu.usbmodem12345` + +## Hardware + +- **MCU**: Teensy 4.0 (ARM Cortex-M7, 600 MHz) +- **Audio**: SGTL5000 codec, I2S stereo output, 44.1 kHz / 16-bit +- **Controls**: Quadrature encoder, 4 potentiometers, 2 CV inputs (10Vpp), button +- **Display**: 2x 7-segment LED +- **Size**: 14HP Eurorack + +## Memory Usage (60 programs) + +| Resource | Used | Free | +|----------|------|------| +| FLASH | 329 KB | 1703 KB (83% free) | +| RAM1 | 331 KB | 193 KB | +| RAM2 | 65 KB | 459 KB | + +## License + +- Original firmware: [CC-BY-NC-SA 3.0](LICENSE.txt) — Befaco / Jeremy Bernstein, 2021 +- New plugins (Banks D, E, F): CC-BY-SA v4 +- Stereo mode and infrastructure: CC-BY-SA v4 From 84480e670354425a0113a2c142a32704e1fd2fdf Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 11:26:45 +0200 Subject: [PATCH 08/11] Improve stereo mode: real L/R panning, state restore, display fixes Replace timbral offset with real amplitude panning (gainL/gainR modulation via sine LFO). Add time-based LFO with quadratic speed response (max 1.5 Hz, independent of loop rate). Save and restore B's bank, program, and A/B mode on stereo toggle. Auto-exit bank mode on stereo entry. Show both decimal dots in bank mode + stereo. Add setGains() to AudioProcessor. Update README and stereo guide with complete control reference and panning details. Co-Authored-By: Claude Opus 4.6 (1M context) --- AudioProcessor.hpp | 5 ++ NOISE_PLETHORA_STEREO_GUIDE.md | 131 ++++++++++++++++++++++----------- Noise_Plethora.ino | 69 +++++++++++++---- README.md | 37 +++++++++- 4 files changed, 184 insertions(+), 58 deletions(-) diff --git a/AudioProcessor.hpp b/AudioProcessor.hpp index 0877037..d399dee 100644 --- a/AudioProcessor.hpp +++ b/AudioProcessor.hpp @@ -73,6 +73,11 @@ class AudioProcessor { } } + void setGains(float gainL, float gainR) { + ampA.gain(gainL); + ampB.gain(gainR); + } + void setA(std::unique_ptr& pin, float gain = 1.0) { if (!pin) return; AudioNoInterrupts(); diff --git a/NOISE_PLETHORA_STEREO_GUIDE.md b/NOISE_PLETHORA_STEREO_GUIDE.md index d33e018..eba2e3d 100644 --- a/NOISE_PLETHORA_STEREO_GUIDE.md +++ b/NOISE_PLETHORA_STEREO_GUIDE.md @@ -9,15 +9,16 @@ - [How to Activate (Hardware)](#how-to-activate-hardware) - [Controls in Stereo Mode](#controls-in-stereo-mode) - [How It Works Internally](#how-it-works-internally) +- [State Behavior](#state-behavior) - [Stereo Patch Ideas](#stereo-patch-ideas) --- ## What It Does -Stereo mode transforms the Noise Plethora from two independent mono generators into a single **stereo sound source**. Generator B automatically mirrors Generator A's algorithm, and the B knobs (XB/YB) are repurposed as stereo controls. +Stereo mode transforms the Noise Plethora from two independent mono generators into a single **stereo sound source**. Generator B automatically mirrors Generator A's algorithm, and the B knobs (XB/YB) are repurposed as stereo panning controls. -The result: a wide stereo field from a single algorithm, where the left and right channels share the same pitch but differ in timbral character. The difference varies slowly over time, creating organic stereo movement. +The result: a wide stereo field from a single algorithm, where a sine LFO pans the sound between the left and right outputs by modulating channel amplitudes. --- @@ -32,18 +33,19 @@ The result: a wide stereo field from a single algorithm, where the left and righ ## How to Activate (Hardware) -**Double-click** the Program/Bank encoder (two quick clicks within 300ms): +**Double-click** the Program/Bank encoder button (two clicks within 300 ms): | Gesture | Action | |---------|--------| -| Single click | Toggle between Gen A and Gen B (normal) | +| Single click | Toggle between Gen A and Gen B (normal mode) | | **Double-click** | **Toggle stereo mode** | -| Hold 400ms | Enter/exit bank mode | +| Hold >400 ms | Enter/exit bank mode | When stereo mode is active: -- Both dots on the 7-segment display light up simultaneously -- The display briefly shows "St" on activation and "no" on deactivation -- Both digits show the same program number +- Both decimal dots on the 7-segment display light up simultaneously +- Both digits show the same value (A's program number or bank letter) +- Single-click (A/B toggle) is disabled +- The module auto-exits bank mode upon entering stereo --- @@ -53,49 +55,99 @@ When stereo mode is active: |---------|------------|-------------| | **XA knob** | k1 for Gen A | k1 for **both** (pitch/primary param) | | **YA knob** | k2 for Gen A | k2 for **both** (timbre/secondary param) | -| **XB knob** | k1 for Gen B | **Stereo Width** (0=mono, 1=wide) | -| **YB knob** | k2 for Gen B | **Stereo Movement** (LFO speed for width modulation) | +| **XB knob** | k1 for Gen B | **Pan width** (0 = mono center, 1 = full L/R swing) | +| **YB knob** | k2 for Gen B | **Pan LFO speed** (0 = static, max = 1.5 Hz) | | **CV XA** | CV for k1 A | CV for k1 **both** | | **CV YA** | CV for k2 A | CV for k2 **both** | | **CV XB** | CV for k1 B | Not used (manual only) | | **CV YB** | CV for k2 B | Not used (manual only) | -| **Encoder** | Select program for A or B | Select program for **both** | +| **Encoder turn** | Select program for A or B | Select program for **both** (A changes, B follows) | +| **Single click** | Toggle A/B | **Disabled** (locked to A) | +| **Hold >400 ms** | Bank mode | Bank mode (works normally) | +| **Double-click** | Enter stereo | **Exit stereo** (restores B) | | **Filter A** | Filter for Gen A | Filter for **left channel** | | **Filter B** | Filter for Gen B | Filter for **right channel** | -**Note:** The analog filters remain fully independent in stereo mode. This is a feature — setting the two filters differently (e.g., LP on left, HP on right) adds an additional layer of stereo character on top of the digital decorrelation. +**Note:** The analog filters remain fully independent in stereo mode. Setting the two filters differently (e.g., LP on left, HP on right) adds an additional layer of stereo character on top of the digital panning. --- ## How It Works Internally ``` - XA (k1) ─────────────────────→ Gen A ──→ Filter A ──→ LEFT - YA (k2) ─────────────────────→ │ - │ - │ (same algorithm) - │ - XA (k1) ─────────────────────→ Gen B ──→ Filter B ──→ RIGHT - YA (k2) + stereo offset ─────→ │ - │ - XB ──→ offset amount │ - YB ──→ offset LFO speed ──────────┘ + XA (k1) ──→ Gen A ──→ ampA (gainL) ──→ Filter A ──→ LEFT + YA (k2) ──→ │ + │ (same algorithm, same params) + XA (k1) ──→ Gen B ──→ ampB (gainR) ──→ Filter B ──→ RIGHT + YA (k2) ──→ │ + │ + XB ──→ pan width (amplitude) + YB ──→ pan LFO speed ───────────────────┘ ``` -**The stereo offset is applied to k2 (timbre), not k1 (pitch).** This is critical — it means both channels always play at the same frequency, but with slightly different timbral character. This creates a natural, phase-free stereo width without the pitch-detuning artifacts of traditional chorus effects. - -The offset follows a slow sine LFO: +**The stereo effect uses real amplitude panning** — a sine LFO modulates the gain of each channel inversely: ``` -offset = XB × 0.4 × sin(2π × YB × 0.3 × time) +pan = width * sin(2pi * phase) +gainL = (1 - pan) / 2 +gainR = (1 + pan) / 2 +phase += speed^2 * 1.5 * dt (time-based, independent of loop rate) ``` -- **XB = 0**: offset is zero → mono (both channels identical) -- **XB = 0.5**: offset swings ±0.2 → moderate stereo width -- **XB = 1.0**: offset swings ±0.4 → maximum stereo width -- **YB = 0**: offset is static → fixed stereo spread -- **YB = 0.5**: offset moves slowly → gentle stereo movement -- **YB = 1.0**: offset moves faster → animated stereo field +### Pan Width (XB) + +| XB position | Pan range | Perceived effect | +|-------------|-----------|------------------| +| 0.0 | No panning | Mono (both channels equal, 0.5 / 0.5) | +| 0.5 | Half swing | Moderate stereo (0.25 – 0.75) | +| 1.0 | Full swing | Full pan (0.0 – 1.0, hard left to hard right) | + +### Pan LFO Speed (YB) + +The speed control has a **quadratic response** (`speed^2 * 1.5 Hz`) for fine control at low settings: + +| YB position | LFO rate | Cycle time | +|-------------|----------|------------| +| 0.0 | 0 Hz | Static (no movement) | +| 0.1 | 0.015 Hz | ~67 seconds | +| 0.2 | 0.06 Hz | ~17 seconds | +| 0.3 | 0.135 Hz | ~7.4 seconds | +| 0.5 | 0.375 Hz | ~2.7 seconds | +| 0.7 | 0.735 Hz | ~1.4 seconds | +| 1.0 | 1.5 Hz | ~0.67 seconds | + +### Display in Stereo Mode + +| View | Display | Example | +|------|---------|---------| +| Program mode | Both dots on, same program number | `3.3.` | +| Bank mode | Both dots on, same bank letter | `A.A.` | + +--- + +## State Behavior + +### Entering Stereo (double-click) + +1. Saves B's current bank, program, and A/B selection +2. Syncs B to A's algorithm +3. Forces encoder to control A (B follows automatically) +4. Auto-exits bank mode (so the stereo display indicator is visible) +5. Both decimal dots turn on + +### During Stereo + +- Encoder changes A's program/bank; B mirrors the change instantly +- Single click (A/B toggle) is disabled +- Hold >400 ms enters bank mode normally (both dots stay on) +- XB and YB control panning instead of Gen B parameters + +### Exiting Stereo (double-click) + +1. Restores channel gains to unity (1.0, 1.0) +2. Restores B's saved bank and program +3. Restores previous A/B selection +4. Both decimal dots return to normal behavior --- @@ -103,51 +155,46 @@ offset = XB × 0.4 × sin(2π × YB × 0.3 × time) ### 1. Immersive Bell Drone - Program: **D-6 NoiseBells** -- Stereo Mode: ON - XA: ~0.3 (medium bell pitch) - YA: ~0.2 (nearly harmonic, chime-like) - XB: 0.3 (moderate width) - YB: 0.15 (very slow movement) - Filter A: LP, cutoff medium, res low - Filter B: LP, cutoff slightly higher than A -- *Result: wide, shimmering bell drone where each ear hears slightly different harmonic content* +- *Result: wide, shimmering bell drone that slowly sweeps between speakers* ### 2. Vocal Wash - Program: **D-3 FormantNoise** -- Stereo Mode: ON - XA: modulated by slow LFO (vowel morphing) - YA: ~0.6 (moderate resonance) - XB: 0.5 (wide) - YB: 0.2 (slow drift) - Filter A: BP, cutoff ~1kHz - Filter B: BP, cutoff ~1.5kHz -- *Result: ghostly stereo choir, each ear whispers a slightly different vowel* +- *Result: ghostly stereo choir panning gently across the field* ### 3. Metallic Stereo Texture - Program: **D-4 BowedMetal** -- Stereo Mode: ON - XA: ~0.4 - YA: ~0.8 (long sustain, bowed character) - XB: 0.6 (wide separation) - YB: 0.3 (moderate movement) - Filter A: HP, cutoff low (full spectrum) - Filter B: LP, cutoff medium (darker) -- *Result: left ear = bright metallic shimmer, right ear = dark resonant glow. Complementary stereo.* +- *Result: metallic shimmer sweeping left-right. Filter differences add tonal depth to the pan.* ### 4. Chaos Stereo Field - Program: **E-9 DualAttractor** -- Stereo Mode: ON - XA: ~0.5 (moderate coupling) - YA: ~0.4 - XB: 0.4 - YB: 0.4 -- *Result: two Lorenz attractors already create wandering tones. In stereo mode, the additional timbral offset makes each channel's attractor feel independent while remaining harmonically related.* +- *Result: two Lorenz attractors panning slowly. The chaotic timbral evolution combined with spatial movement creates an immersive, unpredictable field.* ### 5. Wide Cluster - Program: **B-0 ClusterSaw** -- Stereo Mode: ON - XA: ~0.3 (low fundamental) - YA: ~0.1 (tight cluster = thick unison) - XB: 0.2 (subtle width) - YB: 0.1 (very slow) -- *Result: massive wide chorused sawtooth wall. The subtle timbral difference between channels creates a huge sound from a simple cluster.* +- *Result: massive chorused sawtooth wall that breathes between speakers. Subtle width keeps it cohesive.* diff --git a/Noise_Plethora.ino b/Noise_Plethora.ino index cf685a4..be8c94f 100644 --- a/Noise_Plethora.ino +++ b/Noise_Plethora.ino @@ -62,6 +62,10 @@ bool inTestMode = false; bool stereoMode = false; float stereoLFOPhase = 0.0; float stereoOffset = 0.0; +// Saved B state for stereo restore +byte savedB_Bank = 0; +byte savedB_Program = 0; +unsigned int savedMode = PROG_A; long lastClickTime = 0; bool waitingForDoubleClick = false; @@ -561,11 +565,20 @@ void loop() { } if (inBankMode) { static char charset[] = "AbCdEFHIJLnoPrSUYZ"; - char charsToDisplay[] = "A.A"; - charsToDisplay[0] = charset[valToDisplayA]; - charsToDisplay[1] = (modeA) ? '.' : charset[valToDisplayB]; - charsToDisplay[2] = (modeA) ? charset[valToDisplayB] : '.'; - sevseg.setChars(charsToDisplay); + if (stereoMode) { + // Both dots on to indicate stereo + char charsToDisplay[] = "A.A."; + charsToDisplay[0] = charset[valToDisplayA]; + charsToDisplay[2] = charset[valToDisplayA]; + sevseg.setChars(charsToDisplay); + } + else { + char charsToDisplay[] = "A.A"; + charsToDisplay[0] = charset[valToDisplayA]; + charsToDisplay[1] = (modeA) ? '.' : charset[valToDisplayB]; + charsToDisplay[2] = (modeA) ? charset[valToDisplayB] : '.'; + sevseg.setChars(charsToDisplay); + } } else { float numberToDisplay; @@ -602,7 +615,15 @@ void loop() { stereoMode = !stereoMode; waitingForDoubleClick = false; if (stereoMode) { - // Sync B to A + // Exit bank mode so stereo indicator is visible + inBankMode = false; + digitalWrite(ledPinBANK, LOW); + // Force A mode so encoder always controls A (B follows) + savedMode = program.getMode(); + program.setMode(PROG_A); + // Save B state, then sync B to A + savedB_Bank = state_progB_Bank; + savedB_Program = state_progB_Program; state_progB_Bank = state_progA_Bank; state_progB_Program = state_progA_Program; program.getB().setBank(state_progA_Bank); @@ -613,6 +634,20 @@ void loop() { processor.setB(pinB, gainB); stateTimer = ms; } + else { + // Restore gains, A/B mode, and B to its previous program + processor.setGains(1.0, 1.0); + program.setMode(savedMode); + state_progB_Bank = savedB_Bank; + state_progB_Program = savedB_Program; + program.getB().setBank(savedB_Bank); + program.getB().setProgram(savedB_Program); + auto nameB = program.getB().getCurrentProgramName(); + auto gainB = program.getB().getCurrentProgramGain(); + auto pinB = PluginFactory::Instance()->Create(nameB); + processor.setB(pinB, gainB); + stateTimer = ms; + } } else { waitingForDoubleClick = true; @@ -637,15 +672,21 @@ void loop() { processor.processA(knob_1, knob_2); g_process_mode = PROCESS_MODE_B; if (stereoMode) { - // Stereo: B uses A's params. XB=width, YB=movement speed. - // Offset applied to k2 (timbre) not k1 (pitch). - float width = knob_3; // XB repurposed as stereo width - float speed = knob_4; // YB repurposed as stereo movement - stereoLFOPhase += speed * 0.3 * 0.003; // ~3ms per loop iteration + // Stereo: B mirrors A's params. XB=pan width, YB=pan speed. + // Real L/R panning via amplitude modulation. + float width = knob_3; // XB repurposed as stereo pan width + float speed = knob_4; // YB repurposed as stereo pan speed + static unsigned long lastStereoMs = 0; + float dt = (ms - lastStereoMs) * 0.001f; // seconds + lastStereoMs = ms; + if (dt > 0.1f) dt = 0.1f; // cap for first iteration + stereoLFOPhase += speed * speed * 1.5f * dt; // quadratic: max 1.5 Hz if (stereoLFOPhase > 1.0) stereoLFOPhase -= 1.0; - stereoOffset = width * 0.4 * sin(2.0 * PI * stereoLFOPhase); - float stereo_k2 = constrain(knob_2 + stereoOffset, 0.0, 1.0); - processor.processB(knob_1, stereo_k2); + float pan = width * sin(2.0 * PI * stereoLFOPhase); // -1..+1 + float gainL = constrain(1.0 - pan, 0.0, 2.0) * 0.5; + float gainR = constrain(1.0 + pan, 0.0, 2.0) * 0.5; + processor.setGains(gainL, gainR); + processor.processB(knob_1, knob_2); } else { processor.processB(knob_3, knob_4); diff --git a/README.md b/README.md index 42ad9b5..e2756db 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Extended firmware for the [Befaco Noise Plethora](https://www.befaco.org/noise-p ## What's New - **30 new algorithms** across 3 additional banks (D, E, F) — total: 60 programs in 6 banks -- **Stereo mode** — both generators run the same algorithm with timbral decorrelation +- **Stereo mode** — both generators run the same algorithm with real L/R amplitude panning - **Makefile** for easy build and flash via `arduino-cli` See [NOISE_PLETHORA_PLUGIN_GUIDE.md](NOISE_PLETHORA_PLUGIN_GUIDE.md) for detailed documentation of all 60 algorithms. @@ -25,7 +25,40 @@ See [NOISE_PLETHORA_STEREO_GUIDE.md](NOISE_PLETHORA_STEREO_GUIDE.md) for stereo ## Stereo Mode -Double-click the encoder to toggle. Both generators run the same algorithm, with XB controlling stereo width and YB controlling stereo movement. Both display dots light up when active. Double-click again to exit. +**Toggle:** Double-click the encoder button (two clicks within 300 ms). + +When active, both generators (A and B) run the same algorithm. The sound is panned between left and right outputs using an LFO that modulates the amplitude of each channel. + +### Controls in Stereo Mode + +| Control | Function | Range | +|---------|----------|-------| +| **XA** (knob 1) | Pitch / frequency | 0.0 – 1.0 (same as normal mode) | +| **YA** (knob 2) | Timbre / parameter | 0.0 – 1.0 (same as normal mode) | +| **XB** (knob 3) | **Pan width** | 0.0 (mono, centered) – 1.0 (full L/R swing) | +| **YB** (knob 4) | **Pan LFO speed** | 0.0 (static) – 1.5 Hz max (quadratic response) | +| **Encoder** | Change program/bank | Changes A, B follows automatically | +| **Single click** | Disabled | A/B toggle is locked in stereo | +| **Hold >400 ms** | Enter/exit bank mode | Works normally | +| **Double-click** | Exit stereo mode | Restores B to its previous program | + +### Panning Details + +- **Method:** Amplitude modulation — `gainL = (1 - pan) / 2`, `gainR = (1 + pan) / 2` +- **LFO shape:** Sine wave +- **LFO rate:** Quadratic response (`speed² × 1.5`), time-based (independent of loop speed) +- **Pan range:** At width = 1.0 the signal sweeps from full-left to full-right +- **At width = 0:** Both channels equal volume (mono-compatible) + +### Display + +- **Program mode + stereo:** Both decimal dots on, both digits show A's program number (e.g., `3.3.`) +- **Bank mode + stereo:** Both decimal dots on, both digits show A's bank letter (e.g., `A.A.`) + +### State Behavior + +- **Entering stereo:** Saves B's current bank, program, and A/B selection. Syncs B to A. Forces encoder to control A. Auto-exits bank mode. +- **Exiting stereo:** Restores B's saved bank and program. Restores previous A/B selection. Resets gains to unity (1.0, 1.0). ## Build & Flash From 3e9f592167f64b008a000543e1442c35012fd162 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 11:33:42 +0200 Subject: [PATCH 09/11] Persist stereo mode in EEPROM (byte 2) Stereo mode now survives power cycles. On boot, if stereo was active, B is synced to A and encoder is forced to A mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- Noise_Plethora.ino | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Noise_Plethora.ino b/Noise_Plethora.ino index be8c94f..be435bc 100644 --- a/Noise_Plethora.ino +++ b/Noise_Plethora.ino @@ -193,7 +193,7 @@ void setup() { else { Serial.println("restoring EEPROM values"); state_progCVProgramEverywhere = EEPROM.read(1); - // leave some space, maybe we need some flags for something + stereoMode = EEPROM.read(2); state_progA_Bank = EEPROM.read(4); state_progA_Program = EEPROM.read(5); program.getA().setBank(state_progA_Bank); @@ -205,6 +205,15 @@ void setup() { program.getB().setProgram(state_progB_Program); } + if (stereoMode) { + // Restore stereo state: sync B to A, force A mode + program.setMode(PROG_A); + state_progB_Bank = state_progA_Bank; + state_progB_Program = state_progA_Program; + program.getB().setBank(state_progA_Bank); + program.getB().setProgram(state_progA_Program); + } + auto nameA = program.getA().getCurrentProgramName(); auto gainA = program.getA().getCurrentProgramGain(); auto pinA = PluginFactory::Instance()->Create(nameA); @@ -376,6 +385,7 @@ void loop() { if (!inTestMode && stateTimer && ms - stateTimer > 2000) { Serial.println("writing EEPROM"); EEPROM.write(1, state_progCVProgramEverywhere); + EEPROM.write(2, stereoMode); EEPROM.write(4, state_progA_Bank); EEPROM.write(5, state_progA_Program); EEPROM.write(6, state_progB_Bank); From befce99455d9dd1a3542b91f9328861e8c4dc07f Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 12:17:04 +0200 Subject: [PATCH 10/11] Fix documentation discrepancies and exclude experimental plugins from compilation - Fix incorrect "3 generators (A, B, C)" reference to "2 generators (A and B)" - Exclude 7 unused experimental plugins from compilation to save flash space - Document experimental plugins in README with re-enable instructions - Add EEPROM layout table to README - Clarify stereo mode display behavior in STEREO_GUIDE Co-Authored-By: Claude Opus 4.6 (1M context) --- Banks.cpp | 22 +++++++++++----------- NOISE_PLETHORA_PLUGIN_GUIDE.md | 2 +- NOISE_PLETHORA_STEREO_GUIDE.md | 10 +++++++--- README.md | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Banks.cpp b/Banks.cpp index da2fcd0..dcb7216 100644 --- a/Banks.cpp +++ b/Banks.cpp @@ -45,9 +45,6 @@ int Bank::getSize() { return size; } -//test plugging -#include "P_TestPlugin.hpp" - //Bank A: #include "P_radioOhNo.hpp" #include "P_Rwalk_SineFMFlange.hpp" @@ -84,14 +81,17 @@ int Bank::getSize() { #include "P_Rwalk_BitCrushPW.hpp" #include "P_Rwalk_LFree.hpp" -//Maybe Later: (Unused or incomplete) -#include "P_Rwalk_SineFM.hpp" -#include "P_VarWave.hpp" -#include "P_RwalkVarWave.hpp" -#include "P_Rwalk_ModWave.hpp" -#include "P_Rwalk_WaveTwist.hpp" -#include "P_Rwalk_LBit.hpp" -#include "P_delayPonzo.hpp" +#include "P_TestPlugin.hpp" // hidden test mode (10s button hold) + +//Experimental: excluded from compilation to save flash space. +//To re-enable, uncomment and add to a bank definition in Banks_Def.hpp. +//#include "P_Rwalk_SineFM.hpp" +//#include "P_VarWave.hpp" +//#include "P_RwalkVarWave.hpp" +//#include "P_Rwalk_ModWave.hpp" +//#include "P_Rwalk_WaveTwist.hpp" +//#include "P_Rwalk_LBit.hpp" +//#include "P_delayPonzo.hpp" //Bank D: Resonant Bodies #include "P_CombNoise.hpp" diff --git a/NOISE_PLETHORA_PLUGIN_GUIDE.md b/NOISE_PLETHORA_PLUGIN_GUIDE.md index d1d11ec..88cfc3d 100644 --- a/NOISE_PLETHORA_PLUGIN_GUIDE.md +++ b/NOISE_PLETHORA_PLUGIN_GUIDE.md @@ -4,7 +4,7 @@ ## About This Guide -The Noise Plethora is a Eurorack noise workstation with 3 digital sound generators (A, B, and C), each followed by an analog multimode filter. Generators A and B run interchangeable algorithms organized in banks of 10. Each algorithm has two parameters controlled by the **X** and **Y** knobs (and their corresponding CV inputs, 0–10Vpp). +The Noise Plethora is a Eurorack noise workstation with 2 digital sound generators (A and B), each followed by an analog multimode filter. Generators A and B run interchangeable algorithms organized in banks of 10. Each algorithm has two parameters controlled by the **X** and **Y** knobs (and their corresponding CV inputs, 0–10Vpp). This guide documents all 60 algorithms across 6 banks: diff --git a/NOISE_PLETHORA_STEREO_GUIDE.md b/NOISE_PLETHORA_STEREO_GUIDE.md index eba2e3d..88a80da 100644 --- a/NOISE_PLETHORA_STEREO_GUIDE.md +++ b/NOISE_PLETHORA_STEREO_GUIDE.md @@ -118,10 +118,14 @@ The speed control has a **quadratic response** (`speed^2 * 1.5 Hz`) for fine con ### Display in Stereo Mode -| View | Display | Example | +The 7-segment display has two digits, each with a decimal dot. In stereo mode both dots light up simultaneously as the stereo indicator. Both digits mirror Generator A's current value: + +| View | Display | Meaning | |------|---------|---------| -| Program mode | Both dots on, same program number | `3.3.` | -| Bank mode | Both dots on, same bank letter | `A.A.` | +| Program mode | `3.3.` | Program 3 on both generators, both dots = stereo active | +| Bank mode | `A.A.` | Bank A on both generators, both dots = stereo active | + +In normal (non-stereo) mode only one dot is lit, indicating which generator (A or B) the encoder controls. --- diff --git a/README.md b/README.md index e2756db..cfffdf2 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,22 @@ To specify a port manually: `make upload PORT=/dev/cu.usbmodem12345` - **Display**: 2x 7-segment LED - **Size**: 14HP Eurorack +## Experimental Plugins (not compiled) + +The following plugins are included in the repository but **excluded from compilation** to save flash space. They are incomplete or unused variants that may be developed further in the future: + +| Plugin | File | Description | +|--------|------|-------------| +| delayPonzo | `P_delayPonzo.hpp` | Delay-based experimental algorithm | +| Rwalk_SineFM | `P_Rwalk_SineFM.hpp` | Random walk + sine FM variant | +| VarWave | `P_VarWave.hpp` | Variable waveform oscillator | +| RwalkVarWave | `P_RwalkVarWave.hpp` | Random walk + variable waveform | +| Rwalk_ModWave | `P_Rwalk_ModWave.hpp` | Random walk + modulated waveform | +| Rwalk_WaveTwist | `P_Rwalk_WaveTwist.hpp` | Random walk + wave twist | +| Rwalk_LBit | `P_Rwalk_LBit.hpp` | Random walk + low-bit variant | + +To re-enable any of these, uncomment their `#include` lines in `Banks.cpp` and add them to a bank definition in `Banks_Def.hpp`. + ## Memory Usage (60 programs) | Resource | Used | Free | @@ -111,6 +127,22 @@ To specify a port manually: `make upload PORT=/dev/cu.usbmodem12345` | RAM1 | 331 KB | 193 KB | | RAM2 | 65 KB | 459 KB | +## EEPROM Layout + +State is persisted to EEPROM with a 2-second debounce after the last change. On first boot (or version mismatch), bytes 0–16 are initialized to zero. + +| Byte | Field | Values | +|------|-------|--------| +| 0 | Version tag | Current firmware version constant | +| 1 | CV Program Everywhere | 0 = off, 1 = on | +| 2 | Stereo Mode | 0 = off, 1 = on | +| 3 | *(reserved)* | — | +| 4 | Gen A bank index | 0–5 (banks A–F) | +| 5 | Gen A program index | 0–9 | +| 6 | Gen B bank index | 0–5 (banks A–F) | +| 7 | Gen B program index | 0–9 | +| 8–16 | *(reserved)* | — | + ## License - Original firmware: [CC-BY-NC-SA 3.0](LICENSE.txt) — Befaco / Jeremy Bernstein, 2021 From e5483d614626ccc823a61044cddcfacd8a5cdda4 Mon Sep 17 00:00:00 2001 From: Pablo Alcantar Date: Sun, 12 Apr 2026 16:13:15 +0200 Subject: [PATCH 11/11] Remove VCV Rack references from firmware stereo guide Co-Authored-By: Claude Opus 4.6 (1M context) --- NOISE_PLETHORA_STEREO_GUIDE.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/NOISE_PLETHORA_STEREO_GUIDE.md b/NOISE_PLETHORA_STEREO_GUIDE.md index 88a80da..e08bf8f 100644 --- a/NOISE_PLETHORA_STEREO_GUIDE.md +++ b/NOISE_PLETHORA_STEREO_GUIDE.md @@ -5,8 +5,7 @@ ## Table of Contents - [What It Does](#what-it-does) -- [How to Activate (VCV Rack)](#how-to-activate-vcv-rack) -- [How to Activate (Hardware)](#how-to-activate-hardware) +- [How to Activate](#how-to-activate) - [Controls in Stereo Mode](#controls-in-stereo-mode) - [How It Works Internally](#how-it-works-internally) - [State Behavior](#state-behavior) @@ -22,16 +21,7 @@ The result: a wide stereo field from a single algorithm, where a sine LFO pans t --- -## How to Activate (VCV Rack) - -1. **Right-click** on the Noise Plethora module -2. Under "Stereo," check **"Stereo Mode"** -3. Both display dots light up to confirm stereo is active -4. To deactivate: right-click and uncheck - ---- - -## How to Activate (Hardware) +## How to Activate **Double-click** the Program/Bank encoder button (two clicks within 300 ms):