From 89204493136e018a213a2bee7ac94795b7f7a050 Mon Sep 17 00:00:00 2001 From: Curlymorphic Date: Fri, 29 May 2020 23:31:21 +0100 Subject: [PATCH] Tyrant polyphonic trigger gate and shuffle --- README.md | 2 +- src/composites/PolyShiftRegister.h | 172 ++++++++++++------ ...stPolyShiftRegister.cpp => testTyrant.cpp} | 122 ++++++++++++- 3 files changed, 232 insertions(+), 64 deletions(-) rename test/{testPolyShiftRegister.cpp => testTyrant.cpp} (64%) diff --git a/README.md b/README.md index 81722ac..872e8b5 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ A monophonic in, polyphonic out shift register, with many probability options. - Shuffle Probability controls the values in the buffer being reordered - Three accent controls - Accent A & B apply a fixed offset, Accent RNG applies a random offset with the maximum value defined by the offset controls -- If Accent CV's have no input or a monophonic input all channels are affected simultaneously, If the probability cv inputs have polyphonic inputs the channels are effected independently +- If any of the probability CV's have no input or a monophonic input all channels are affected simultaneously, If the probability cv inputs have polyphonic inputs the channels are effected independently - The Reset input sets the current channel count to 1 and samples the input, the channel count is increased on each trigger input, until the desired channel count is reached
diff --git a/src/composites/PolyShiftRegister.h b/src/composites/PolyShiftRegister.h index 512aa7f..182ed7a 100644 --- a/src/composites/PolyShiftRegister.h +++ b/src/composites/PolyShiftRegister.h @@ -100,7 +100,8 @@ class PolyShiftRegisterComp : public TBase }; static constexpr int maxChannels = 16; - std::vector channelData; + //16 historc values kept per channel to allow for individual shuffles + std::vector> channelData; dsp::SchmittTrigger clockTrigger; dsp::SchmittTrigger resetTrigger; int currentChannels = 1; @@ -133,11 +134,15 @@ class PolyShiftRegisterComp : public TBase // must be called after setSampleRate void init() { - defaultGenerator.seed (time (NULL)); + defaultGenerator.seed (time (nullptr)); channelData.resize (maxChannels); for (auto& cd : channelData) - cd = 0.0f; + { + cd.resize (maxChannels); + for (auto& c : cd) + c = -0.0f; + } triggerRng.resize (maxChannels); for (auto& t : triggerRng) @@ -156,11 +161,11 @@ class PolyShiftRegisterComp : public TBase a = 0.0f; } - void shift (float in) + void shift (float in, int bufferChannel = 0) { for (auto i = maxChannels; i > 0; --i) - channelData[i] = channelData[i - 1]; - channelData[0] = in; + channelData[bufferChannel][i] = channelData[bufferChannel][i - 1]; + channelData[bufferChannel][0] = in; } void triggerRngGenerator() @@ -169,58 +174,80 @@ class PolyShiftRegisterComp : public TBase triggerRng[i] = rand01(); } - void generateAccents() + void generateAccents (std::vector& accentOffsets, + ParamIds accentProbParam, + InputIds accentProbCv, + ParamIds accentOffsetParam, + InputIds accentOffsetCv, + int bufferChannel, + bool rng = false) { - if (TBase::inputs[ACCENT_A_PROB_INPUT].getChannels() > 1) - { - for (auto c = 0; c < currentChannels; ++c) - { - accentAOffsets[c] = fixedAccent (ACCENT_A_PROB_PARAM, ACCENT_A_PROB_INPUT, ACCENT_A_OFFSET_PARAM, ACCENT_A_OFFSET_INPUT, c); - } - } - else - { - auto accent = fixedAccent (ACCENT_A_PROB_PARAM, ACCENT_A_PROB_INPUT, ACCENT_A_OFFSET_PARAM, ACCENT_A_OFFSET_INPUT, 0); - for (auto& a : accentAOffsets) - a = accent; - } - - if (TBase::inputs[ACCENT_B_PROB_INPUT].getChannels() > 1) + if (TBase::inputs[accentProbCv].getChannels() > 1) { - for (auto c = 0; c < currentChannels; ++c) - { - accentBOffsets[c] = fixedAccent (ACCENT_B_PROB_PARAM, ACCENT_B_PROB_INPUT, ACCENT_B_OFFSET_PARAM, ACCENT_B_OFFSET_INPUT, c); - } + auto scale = rng ? rand01() : 1.0f; + accentOffsets[bufferChannel] = scale + * fixedAccent (accentProbParam, + accentProbCv, + accentOffsetParam, + accentProbCv, + bufferChannel); } else { - auto accent = fixedAccent (ACCENT_B_PROB_PARAM, ACCENT_B_PROB_INPUT, ACCENT_B_OFFSET_PARAM, ACCENT_B_OFFSET_INPUT, 0); - for (auto& a : accentBOffsets) + auto scale = rng ? rand01() : 1.0f; + auto accent = scale + * fixedAccent (accentProbParam, + accentProbCv, + accentOffsetParam, + accentProbCv, + 0); + for (auto& a : accentOffsets) a = accent; } + } - if (TBase::inputs[ACCENT_RNG_PROB_INPUT].getChannels() > 1) - { - for (auto c = 0; c < currentChannels; ++c) - { - accentRngOffsets[c] = rand01() * fixedAccent (ACCENT_RNG_PROB_PARAM, ACCENT_RNG_PROB_INPUT, ACCENT_RNG_OFFSET_PARAM, ACCENT_RNG_MAX_INPUT, c); - } - } - else - { - auto accent = rand01() * fixedAccent (ACCENT_RNG_PROB_PARAM, ACCENT_RNG_PROB_INPUT, ACCENT_RNG_OFFSET_PARAM, ACCENT_RNG_MAX_INPUT, 0); - for (auto& a : accentRngOffsets) - a = accent; - } + void generateAccents (int bufferChannel) + { + generateAccents (accentAOffsets, + ACCENT_A_PROB_PARAM, + ACCENT_A_PROB_INPUT, + ACCENT_A_OFFSET_PARAM, + ACCENT_A_OFFSET_INPUT, + bufferChannel); + + generateAccents (accentBOffsets, + ACCENT_B_PROB_PARAM, + ACCENT_B_PROB_INPUT, + ACCENT_B_OFFSET_PARAM, + ACCENT_B_OFFSET_INPUT, + bufferChannel); + + generateAccents (accentRngOffsets, + ACCENT_RNG_PROB_PARAM, + ACCENT_RNG_PROB_INPUT, + ACCENT_RNG_OFFSET_PARAM, + ACCENT_RNG_MAX_INPUT, + bufferChannel, + true); } - float fixedAccent (ParamIds probParam, InputIds probInput, ParamIds offsetParam, InputIds offsetInput, int c) + float fixedAccent (ParamIds probParam, + InputIds probInput, + ParamIds offsetParam, + InputIds offsetInput, + int c) { auto ret = 0.0f; - auto accentProb = clamp (TBase::params[probParam].getValue() + TBase::inputs[probInput].getPolyVoltage (c) / 10.0f, 0.0f, 1.0f); + auto accentProb = clamp (TBase::params[probParam].getValue() + + TBase::inputs[probInput].getPolyVoltage (c) / 10.0f, + 0.0f, + 1.0f); auto useAccent = accentProb > rand01(); if (useAccent) - ret = clamp (TBase::params[offsetParam].getValue() + TBase::inputs[offsetInput].getPolyVoltage (c), -10.0f, 10.0f); + ret = clamp (TBase::params[offsetParam].getValue() + + TBase::inputs[offsetInput].getPolyVoltage (c), + -10.0f, + 10.0f); return ret; } @@ -235,23 +262,48 @@ class PolyShiftRegisterComp : public TBase template inline void PolyShiftRegisterComp::step() { - //channels, trigger and shufffle are monophonic as these all act on a single buffer not per output channel - auto channels = static_cast (clamp (TBase::params[CHANNELS_PARAM].getValue() + TBase::inputs[CHANNELS_INPUT].getVoltage(), 1.0f, static_cast (maxChannels))); - auto triggerProb = clamp (TBase::params[TRIGGER_PROB_PARAM].getValue() + TBase::inputs[TRIGGER_PROB_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); - auto shuffleProb = clamp (TBase::params[SHUFFLE_PROB_PARAM].getValue() + TBase::inputs[SHUFFLE_PROB_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); - + //channels, monophonic as these all act on a single buffer not per output channel + auto channels = static_cast (clamp (TBase::params[CHANNELS_PARAM].getValue() + + TBase::inputs[CHANNELS_INPUT].getVoltage(), + 1.0f, + static_cast (maxChannels))); + + //trigger and shuffle act per channel buffer + auto triggered = clockTrigger.process (TBase::inputs[TRIGGER_INPUT].getVoltage()); + auto monoTriggerProb = TBase::inputs[TRIGGER_PROB_INPUT].getChannels() < 2; + float triggerProb = clamp (TBase::params[TRIGGER_PROB_PARAM].getValue() + + TBase::inputs[TRIGGER_PROB_INPUT].getVoltage() / 10.0f, + 0.0f, + 1.0f); auto ignoreTrigger = triggerProb > rand01(); - if (clockTrigger.process (TBase::inputs[TRIGGER_INPUT].getVoltage()) && ! ignoreTrigger) + + for (auto c = 0; c < maxChannels; ++c) { - shift (TBase::inputs[MAIN_INPUT].getVoltage()); - currentChannels++; - currentChannels = clamp (currentChannels, 1, channels); - triggerRngGenerator(); - auto useShuffle = shuffleProb > rand01(); - if (useShuffle) - std::random_shuffle (channelData.begin(), channelData.end()); - - generateAccents(); + triggerProb = clamp (TBase::params[TRIGGER_PROB_PARAM].getValue() + + TBase::inputs[TRIGGER_PROB_INPUT].getPolyVoltage (c) / 10.0f, + 0.0f, + 1.0f); + + auto shuffleProb = clamp (TBase::params[SHUFFLE_PROB_PARAM].getValue() + + TBase::inputs[SHUFFLE_PROB_INPUT].getPolyVoltage (c) / 10.0f, + 0.0f, + 1.0f); + + if (! monoTriggerProb) + ignoreTrigger = triggerProb > rand01(); + + if (triggered && ! ignoreTrigger) + { + shift (TBase::inputs[MAIN_INPUT].getVoltage(), c); + + currentChannels++; + currentChannels = clamp (currentChannels, 1, channels); + triggerRngGenerator(); + auto useShuffle = shuffleProb > rand01(); + if (useShuffle) + std::random_shuffle (channelData[c].begin(), channelData[c].end()); + generateAccents (c); + } } if (resetTrigger.process (TBase::inputs[RESET_INPUT].getVoltage())) @@ -260,7 +312,7 @@ inline void PolyShiftRegisterComp::step() //output channels for (auto c = 0; c < std::min (currentChannels, channels); ++c) { - auto out = channelData[c]; + auto out = channelData[c][c]; out += accentAOffsets[c]; out += accentBOffsets[c]; out += accentRngOffsets[c]; diff --git a/test/testPolyShiftRegister.cpp b/test/testTyrant.cpp similarity index 64% rename from test/testPolyShiftRegister.cpp rename to test/testTyrant.cpp index 476b8ef..353f770 100644 --- a/test/testPolyShiftRegister.cpp +++ b/test/testTyrant.cpp @@ -26,6 +26,7 @@ #include "digital.hpp" #include "math.hpp" #include "testSignal.h" +#include namespace ts = sspo::TestSignal; @@ -53,7 +54,7 @@ static void testShift() auto seq = a + b + c; auto trig = ts::makeClockTrigger (100, 3); assertEQ (psr.currentChannels, 1); - for (auto i = 0; i < 300; ++i) + for (auto i = 0; i < int (trig.size()); ++i) { psr.inputs[psr.MAIN_INPUT].setVoltage (seq[i]); psr.inputs[psr.TRIGGER_INPUT].setVoltage (trig[i]); @@ -66,6 +67,84 @@ static void testShift() assertClose (psr.outputs[psr.MAIN_OUTPUT].getVoltage (2), 1.0f, FLT_EPSILON); } +static void testShiftMonoCv (float triggerProbCv) +{ + std::vector oldSignal{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + std::vector newSignal; + PSR psr; + psr.init(); + psr.step(); + psr.params[psr.CHANNELS_PARAM].setValue (16); + psr.inputs[psr.TRIGGER_PROB_INPUT].setVoltage (triggerProbCv, 0); + psr.inputs[psr.TRIGGER_PROB_INPUT].setChannels (1); + auto a = ts::makeFixed (100, 1.0f); + auto b = ts::makeFixed (100, 1.5f); + auto c = ts::makeFixed (100, 3.3f); + auto seq = a + b + c + a + b + c + a + b + c + a + b + c + a + b + c; + const int stepcount = 15; + const int interval = 100; + auto trig = ts::makeClockTrigger (interval, stepcount); + assertEQ (psr.currentChannels, 1); + for (auto j = 0; j < stepcount; ++j) + { + newSignal.resize (0); + for (auto i = 0; i < int (interval); ++i) + { + psr.inputs[psr.MAIN_INPUT].setVoltage (seq[i]); + psr.inputs[psr.TRIGGER_INPUT].setVoltage (trig[i]); + psr.step(); + } + //test newbuffer = oldbuffer or newbuffershifted + //between elements 1 and psr.currentChannels + + //get new buffer + for (auto k = 0; k < stepcount; ++k) + newSignal.push_back (psr.outputs[psr.MAIN_OUTPUT].getVoltage (k)); + //compare same + auto same = newSignal == oldSignal; + //compare shifter + auto shifted = std::equal (newSignal.begin() + 1, + newSignal.end(), + oldSignal.begin()); + //assert or + assert (same || shifted); + oldSignal = newSignal; + } +} + +static void testShiftMonoCv() +{ + testShiftMonoCv (0.0f); + testShiftMonoCv (8.0f); +} + +static void testShiftPolyCv() +{ + PSR psr; + psr.init(); + psr.step(); + psr.params[psr.CHANNELS_PARAM].setValue (3); + psr.inputs[psr.TRIGGER_PROB_INPUT].setChannels (3); + psr.inputs[psr.TRIGGER_PROB_INPUT].setVoltage (10.0, 1); + auto a = ts::makeFixed (100, 1.0f); + auto b = ts::makeFixed (100, 1.5f); + auto c = ts::makeFixed (100, 3.3f); + auto seq = a + b + c; + auto trig = ts::makeClockTrigger (100, 3); + assertEQ (psr.currentChannels, 1); + for (auto i = 0; i < int (trig.size()); ++i) + { + psr.inputs[psr.MAIN_INPUT].setVoltage (seq[i]); + psr.inputs[psr.TRIGGER_INPUT].setVoltage (trig[i]); + psr.step(); + } + + assertEQ (psr.currentChannels, 3); + assertClose (psr.outputs[psr.MAIN_OUTPUT].getVoltage (0), 3.3f, FLT_EPSILON); + assertClose (psr.outputs[psr.MAIN_OUTPUT].getVoltage (1), 0.0f, FLT_EPSILON); + assertClose (psr.outputs[psr.MAIN_OUTPUT].getVoltage (2), 1.0f, FLT_EPSILON); +} + static void testReset() { PSR psr; @@ -90,7 +169,7 @@ static void testReset() assertEQ (psr.currentChannels, 1); } -static void testShuffle() +static void testShuffleNoCv() { PSR psr; psr.init(); @@ -120,6 +199,40 @@ static void testShuffle() assertClose (shuffleCount, 500, 200); } +static void testShufflePolyCv() +{ + PSR psr; + psr.init(); + psr.step(); + psr.params[psr.CHANNELS_PARAM].setValue (3); + auto a = ts::makeFixed (100, 1.0f); + auto b = ts::makeFixed (100, 1.5f); + auto c = ts::makeFixed (100, 3.3f); + auto seq = a + b + c; + auto trig = ts::makeClockTrigger (100, 3); + assertEQ (psr.currentChannels, 1); + psr.params[psr.SHUFFLE_PROB_PARAM].setValue (0.0); + psr.inputs[psr.SHUFFLE_PROB_INPUT].setChannels (3); + psr.inputs[psr.SHUFFLE_PROB_INPUT].setVoltage (5.0, 1); + + auto shuffleCount = 0; + for (auto i = 0; i < 1000; ++i) + { + for (auto i = 0; i < 300; ++i) + { + psr.inputs[psr.MAIN_INPUT].setVoltage (seq[i]); + psr.inputs[psr.TRIGGER_INPUT].setVoltage (trig[i]); + psr.step(); + } + assertClose (psr.outputs[psr.MAIN_OUTPUT].getVoltage (0), 3.3f, 0.0001f); + assertClose (psr.outputs[psr.MAIN_OUTPUT].getVoltage (2), 1.0f, 0.0001f); + + if (psr.outputs[psr.MAIN_OUTPUT].getVoltage (1) != 1.5f) + shuffleCount++; + } + assertClose (shuffleCount, 500, 100); +} + static void testAccentA() { PSR psr; @@ -226,8 +339,11 @@ void testPolyShiftRegister() printf ("Testing Poly Shift Register Tyrant"); test01(); testShift(); + testShiftMonoCv(); + testShiftPolyCv(); testReset(); - testShuffle(); + testShuffleNoCv(); + testShufflePolyCv(); testAccentA(); testAccentB(); testAccentRNG();