Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 69 additions & 8 deletions src/NoisePlethora.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ struct NoisePlethora : Module {

ProgramSelector programSelector; // tracks banks and programs for both sections A/B, including which is the "active" section
ProgramSelector programSelectorWithCV; // as above, but also with CV for program applied as an offset - works like Plaits Model CV input

// Stereo mode: B mirrors A's algorithm, XB/YB become stereo pan width/speed
bool stereoMode = false;
float stereoLFOPhase = 0.f;
float stereoGainL = 1.f;
float stereoGainR = 1.f;
// UI / UX for A/B
std::string textDisplayA = " ", textDisplayB = " ";
bool isDisplayActiveA = false, isDisplayActiveB = false;
Expand Down Expand Up @@ -274,6 +280,31 @@ struct NoisePlethora : Module {
updateParamsTimer.trigger(updateTimeSecs);
}

// Stereo mode: sync B's program to A and update stereo LFO
if (stereoMode && updateParams) {
// In stereo mode A is the master: ensure the program knob always edits A,
// even if mode was flipped to B by a context-menu selection
programSelector.setMode(SECTION_A);

// Sync B to A's algorithm
std::string_view aName = programSelectorWithCV.getA().getCurrentProgramName();
if (aName != algorithmName[SECTION_B]) {
// Copy A's bank/program to B
programSelector.getB().setBank(programSelector.getA().getBank());
programSelector.getB().setProgram(programSelector.getA().getProgram());
}

// Update stereo LFO: XB = pan width, YB = pan speed
// Real L/R panning via amplitude modulation
float width = params[X_B_PARAM].getValue();
float speed = params[Y_B_PARAM].getValue();
stereoLFOPhase += speed * speed * 1.5f * updateTimeSecs; // quadratic: max 1.5 Hz
if (stereoLFOPhase > 1.f) stereoLFOPhase -= 1.f;
float pan = width * std::sin(2.f * M_PI * stereoLFOPhase);
stereoGainL = clamp(1.f - pan, 0.f, 2.f) * 0.5f;
stereoGainR = clamp(1.f + pan, 0.f, 2.f) * 0.5f;
}

// process A, B and C
processTopSection(SECTION_A, X_A_PARAM, Y_A_PARAM,
FILTER_TYPE_A_PARAM, CUTOFF_A_PARAM, CUTOFF_CV_A_PARAM, RES_A_PARAM,
Expand Down Expand Up @@ -335,8 +366,17 @@ struct NoisePlethora : Module {

float out = 0.f;
if (algorithm[SECTION] && outputs[OUTPUT].isConnected()) {
float cvX = params[X_PARAM].getValue() + rescale(inputs[X_INPUT].getVoltage(), -10.f, +10.f, -1.f, 1.f);
float cvY = params[Y_PARAM].getValue() + rescale(inputs[Y_INPUT].getVoltage(), -10.f, +10.f, -1.f, 1.f);
float cvX, cvY;

if (stereoMode && SECTION == SECTION_B) {
// Stereo mode: B reads A's params (same pitch and timbre)
cvX = params[X_A_PARAM].getValue() + rescale(inputs[X_A_INPUT].getVoltage(), -10.f, +10.f, -1.f, 1.f);
cvY = params[Y_A_PARAM].getValue() + rescale(inputs[Y_A_INPUT].getVoltage(), -10.f, +10.f, -1.f, 1.f);
}
else {
cvX = params[X_PARAM].getValue() + rescale(inputs[X_INPUT].getVoltage(), -10.f, +10.f, -1.f, 1.f);
cvY = params[Y_PARAM].getValue() + rescale(inputs[Y_INPUT].getVoltage(), -10.f, +10.f, -1.f, 1.f);
}

// update parameters of the algorithm
if (updateParams) {
Expand Down Expand Up @@ -371,7 +411,12 @@ struct NoisePlethora : Module {
}
}

outputs[OUTPUT].setVoltage(Saturator<float>::process(out) * 5.f);
// Apply stereo panning gain
float stereoGain = 1.f;
if (stereoMode) {
stereoGain = (SECTION == SECTION_A) ? stereoGainL : stereoGainR;
}
outputs[OUTPUT].setVoltage(Saturator<float>::process(out) * 5.f * stereoGain);
}

// process section C
Expand Down Expand Up @@ -425,15 +470,19 @@ struct NoisePlethora : Module {
else if (programKnobMode == BANK_MODE) {
textDisplayA = 'A' + programSelectorWithCV.getA().getBank();
}
isDisplayActiveA = programSelectorWithCV.getMode() == SECTION_A;
isDisplayActiveA = stereoMode || (programSelectorWithCV.getMode() == SECTION_A);

if (programKnobMode == PROGRAM_MODE) {
if (stereoMode) {
// In stereo mode, B shows same as A
textDisplayB = textDisplayA;
}
else if (programKnobMode == PROGRAM_MODE) {
textDisplayB = std::to_string(programSelectorWithCV.getB().getProgram());
}
else if (programKnobMode == BANK_MODE) {
textDisplayB = 'A' + programSelectorWithCV.getB().getBank();
}
isDisplayActiveB = programSelectorWithCV.getMode() == SECTION_B;
isDisplayActiveB = stereoMode || (programSelectorWithCV.getMode() == SECTION_B);
}

// handle convoluted logic for the multifunction Program knob
Expand Down Expand Up @@ -567,6 +616,11 @@ struct NoisePlethora : Module {
if (blockDCJ) {
blockDC = json_boolean_value(blockDCJ);
}

json_t* stereoModeJ = json_object_get(rootJ, "stereoMode");
if (stereoModeJ) {
stereoMode = json_boolean_value(stereoModeJ);
}
}

json_t* dataToJson() override {
Expand All @@ -577,6 +631,7 @@ struct NoisePlethora : Module {

json_object_set_new(rootJ, "bypassFilters", json_boolean(bypassFilters));
json_object_set_new(rootJ, "blockDC", json_boolean(blockDC));
json_object_set_new(rootJ, "stereoMode", json_boolean(stereoMode));

return rootJ;
}
Expand Down Expand Up @@ -833,7 +888,7 @@ struct NoisePlethoraWidget : ModuleWidget {

// build the two algorithm selection menus programmatically
menu->addChild(createMenuLabel("Algorithms"));
std::vector<std::string> bankAliases = {"Textures", "HH Clusters", "Harsh & Wild", "Test"};
std::vector<std::string> bankAliases = {"Textures", "HH Clusters", "Harsh & Wild", "Resonant Bodies", "Chaos Machines", "Stochastic"};
char programNames[] = "AB";
for (int sectionId = 0; sectionId < 2; ++sectionId) {

Expand All @@ -859,7 +914,10 @@ struct NoisePlethoraWidget : ModuleWidget {
if (implemented) {
menu->addChild(createMenuItem(algorithmName.data(), currentProgramAndBank ? CHECKMARK_STRING : "",
[ = ]() {
module->setAlgorithm(sectionId, algorithmName);
// In stereo mode A is the master and B mirrors it, so route
// Program B picks to A to avoid silently-overridden selections
const int targetSection = module->stereoMode ? 0 : sectionId;
module->setAlgorithm(targetSection, algorithmName);
}));
}
else {
Expand All @@ -874,6 +932,9 @@ struct NoisePlethoraWidget : ModuleWidget {

}

menu->addChild(createMenuLabel("Stereo"));
menu->addChild(createBoolPtrMenuItem("Stereo Mode (B mirrors A, XB=pan width, YB=pan speed)", "", &module->stereoMode));

menu->addChild(createMenuLabel("Filters"));
menu->addChild(createBoolPtrMenuItem("Remove DC", "", &module->blockDC));
menu->addChild(createBoolPtrMenuItem("Bypass Filters", "", &module->bypassFilters));
Expand Down
63 changes: 41 additions & 22 deletions src/noise-plethora/plugins/Banks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,36 +77,55 @@ int Bank::getSize() {
#include "P_Rwalk_BitCrushPW.hpp"
#include "P_Rwalk_LFree.hpp"

// Bank D: Test / other
//#include "P_TestPlugin.hpp"
//#include "P_TeensyAlt.hpp"
//#include "P_WhiteNoise.hpp"
//#include "P_Rwalk_LBit.hpp"
//#include "P_Rwalk_SineFM.hpp"
//#include "P_VarWave.hpp"
//#include "P_RwalkVarWave.hpp"
//#include "P_Rwalk_ModWave.hpp"
//#include "P_Rwalk_WaveTwist.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
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<Bank, numBanks> banks { bank1, bank2, bank3 }; //, 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<Bank, programsPerBank> banks { bank1, bank2, bank3, bank4, bank5, bank6, bank7, bank8, bank9, bank10 };
static const Bank bank4 BANKS_DEF_4;
static const Bank bank5 BANKS_DEF_5;
static const Bank bank6 BANKS_DEF_6;
static std::array<Bank, numBanks> banks { bank1, bank2, bank3, bank4, bank5, bank6 };

Bank& getBankForIndex(int i) {
if (i < 0)
i = 0;
if (i >= programsPerBank)
i = (programsPerBank - 1);
if (i >= numBanks)
i = (numBanks - 1);
return banks[i];
}
2 changes: 1 addition & 1 deletion src/noise-plethora/plugins/Banks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <array>

static const int programsPerBank = 10;
static const int numBanks = 3;
static const int numBanks = 6;

struct Bank {

Expand Down
41 changes: 36 additions & 5 deletions src/noise-plethora/plugins/Banks_Def.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,44 @@
}

#define BANKS_DEF_4 { \
{ "TestPlugin", 1.0 }, \
{ "WhiteNoise", 1.0 }, \
{ "TeensyAlt", 1.0 } \
{ "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 { \
{ "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 } \
}
#define BANKS_DEF_5

// #define BANKS_DEF_6
// #define BANKS_DEF_7
// #define BANKS_DEF_8
// #define BANKS_DEF_9
Expand Down
55 changes: 55 additions & 0 deletions src/noise-plethora/plugins/P_BitShift.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include "NoisePlethoraPlugin.hpp"

class BitShift : public NoisePlethoraPlugin {

public:

BitShift() { }

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

void processGraphAsBlock(TeensyBuffer& blockBuffer) override {

waveform1.update(nullptr, nullptr, &wf1Block);
waveform2.update(nullptr, nullptr, &wf2Block);
combine1.update(&wf1Block, &wf2Block, &combineBlock);

blockBuffer.pushBuffer(combineBlock.data, AUDIO_BLOCK_SAMPLES);
}

AudioStream& getStream() override {
return combine1;
}
unsigned char getPort() override {
return 0;
}

private:
audio_block_t wf1Block, wf2Block, combineBlock;

AudioSynthWaveformModulated waveform1;
AudioSynthWaveformModulated waveform2;
AudioEffectDigitalCombine combine1;
};

REGISTER_PLUGIN(BitShift);
Loading