re-work appegiator patterns

This commit is contained in:
Armin 2026-08-13 01:15:17 +02:00
commit 56fc80c405
4 changed files with 122 additions and 8 deletions

View file

@ -17,6 +17,16 @@ public:
Random,
AsPlayed,
Chord,
UpDownX,
DownUpX,
RandomOnce,
OctaveUp,
OctaveDown,
PinkyUp,
PinkyDown,
UpMajor,
DownMajor,
UpDownMinor,
numPatterns
};
@ -137,6 +147,19 @@ public:
}
private:
// Harmony patterns arpeggiate a third + fifth after each root: minor
// (root, +3, +7) or major (root, +4, +7). Each is a separate step.
bool hasHarmony() const {
return pattern == Up || pattern == Down || pattern == UpDown || pattern == DownUp
|| pattern == UpMajor || pattern == DownMajor || pattern == UpDownMinor;
}
int harmonyThird() const {
switch (pattern) {
case Up: case Down: case UpDownMinor: return 3;
default: return 4;
}
}
void buildPool(std::vector<NotePitch>& pool) const {
bool used[128] = {};
auto addPitch = [&](int pitch, float velocity) {
@ -166,8 +189,9 @@ private:
}
std::vector<NotePitch> buildOrder(const std::vector<NotePitch>& pool) {
// Sequential patterns play by ascending pitch regardless of the
// octave direction; As Played keeps the key-press order.
// Sequential patterns play by ascending pitch; Direction Down inverts
// the traversal so the arpeggio descends. As Played keeps the
// key-press order; Random is direction-agnostic.
std::vector<NotePitch> seq(pool);
std::sort(seq.begin(), seq.end(),
[](const NotePitch& a, const NotePitch& b) { return a.note < b.note; });
@ -178,17 +202,43 @@ private:
switch (pattern) {
case Down:
case DownMajor:
for (int i = n - 1; i >= 0; --i) push(i);
break;
case UpDown:
case UpDownMinor:
for (int i = 0; i < n; ++i) push(i);
for (int i = n - 2; i >= 0; --i) push(i);
break;
case DownUp:
for (int i = n - 1; i >= 0; --i) push(i);
for (int i = 1; i < n; ++i) push(i);
break;
case UpDownX:
for (int i = 0; i < n; ++i) push(i);
for (int i = n - 2; i >= 1; --i) push(i);
break;
case DownUp:
case DownUpX:
for (int i = n - 1; i >= 0; --i) push(i);
for (int i = 1; i < n - 1; ++i) push(i);
break;
case Random:
case RandomOnce: {
if (pattern == RandomOnce) {
std::vector<int> keys;
for (const auto& np : seq) keys.push_back(np.note);
if (keys != randomOncePool || stepIndex % n == 0) {
randomOncePool = keys;
randomOnceOrder = seq;
for (int i = n - 1; i > 0; --i) {
int j = static_cast<int>(randomState % static_cast<uint32_t>(i + 1));
std::swap(randomOnceOrder[i], randomOnceOrder[j]);
randomState = randomState * 1664525u + 1013904223u;
}
}
order = randomOnceOrder;
break;
}
order = seq;
for (int i = n - 1; i > 0; --i) {
int j = static_cast<int>(randomState % static_cast<uint32_t>(i + 1));
@ -196,15 +246,71 @@ private:
randomState = randomState * 1664525u + 1013904223u;
}
break;
}
case OctaveUp:
case OctaveDown:
for (const auto& np : seq) {
bool isRoot = true;
for (const auto& other : seq) {
if (other.note == np.note - 12) { isRoot = false; break; }
}
if (!isRoot) continue;
int step = (pattern == OctaveUp ? 12 : -12);
for (int k = 0; k < octaves; ++k) {
int pitch = np.note + step * k;
pitch = pitch < 0 ? 0 : (pitch > 127 ? 127 : pitch);
auto it = std::find_if(seq.begin(), seq.end(),
[&](const NotePitch& o) { return o.note == pitch; });
if (it != seq.end())
order.push_back(*it);
else
order.push_back({pitch, np.velocity});
}
}
break;
case PinkyUp:
case PinkyDown:
for (int hi = n - 1, lo = 0; hi >= lo; --hi, ++lo) {
if (pattern == PinkyUp) {
push(hi);
if (lo < hi) push(lo);
} else {
push(lo);
if (lo < hi) push(hi);
}
}
break;
case AsPlayed:
order = pool;
break;
case Chord:
case Up:
case UpMajor:
default:
for (int i = 0; i < n; ++i) push(i);
break;
}
if (direction == DirectionDown && pattern != Random && pattern != RandomOnce
&& pattern != AsPlayed)
std::reverse(order.begin(), order.end());
if (hasHarmony()) {
std::vector<NotePitch> expanded;
expanded.reserve(order.size() * 3);
int third = harmonyThird();
for (const auto& np : order) {
expanded.push_back(np);
auto pushClamped = [&](int pitch) {
pitch = pitch < 0 ? 0 : (pitch > 127 ? 127 : pitch);
expanded.push_back({pitch, np.velocity});
};
pushClamped(np.note + third);
pushClamped(np.note + 7);
}
order.swap(expanded);
}
return order;
}
@ -222,4 +328,6 @@ private:
bool needsImmediateStep = false;
uint32_t randomState = 0x12345678u;
std::vector<int> randomOncePool;
std::vector<NotePitch> randomOnceOrder;
};

View file

@ -699,7 +699,10 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
setupCB(arpEnabledBox, arpEnabledAttach, "arpEnabled", {"Off", "On"});
setupCB(arpPatternBox, arpPatternAttach, "arpPattern",
{"Up", "Down", "UpDown", "DownUp", "Random", "As Played", "Chord"});
{"Up / Minor", "Down / Minor", "Up & Down / Major", "Down & Up / Major", "Random",
"As Played", "Chord", "Up & Down X", "Down & Up X", "Random Once", "Octave Up",
"Octave Down", "Pinky Up", "Pinky Down", "Up / Major", "Down / Major",
"Up & Down / Minor"});
setupCB(arpOctavesBox, arpOctavesAttach, "arpOctaves", {"1", "2", "3"});
setupCB(arpDirectionBox, arpDirectionAttach, "arpDirection", {"Up", "Down"});
setupCB(arpRateBox, arpRateAttach, "arpRate", {"1/16", "1/8", "1/4", "1/2", "1", "2"});

View file

@ -280,7 +280,10 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
juce::StringArray{"Off", "On"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"arpPattern", 1}, "Arp Pattern",
juce::StringArray{"Up", "Down", "UpDown", "DownUp", "Random", "As Played", "Chord"}, 0));
juce::StringArray{"Up / Minor", "Down / Minor", "Up & Down / Major",
"Down & Up / Major", "Random", "As Played", "Chord", "Up & Down X",
"Down & Up X", "Random Once", "Octave Up", "Octave Down", "Pinky Up",
"Pinky Down", "Up / Major", "Down / Major", "Up & Down / Minor"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"arpOctaves", 1}, "Arp Octaves",
juce::StringArray{"1", "2", "3"}, 1));