rework arpeggiator patterns

This commit is contained in:
Armin 2026-08-13 01:39:02 +02:00
commit 7c444ad849
4 changed files with 71 additions and 18 deletions

View file

@ -27,6 +27,9 @@ public:
UpMajor,
DownMajor,
UpDownMinor,
C3C4A,
C3C4B,
C3C4C,
numPatterns
};
@ -189,14 +192,42 @@ private:
}
std::vector<NotePitch> buildOrder(const std::vector<NotePitch>& pool) {
// 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; });
int n = static_cast<int>(seq.size());
// "Down" patterns (and any harmony pattern with Direction Down) always
// descend from each held note through the chord tones of the octave
// below it: root, fifth below, third below, then the next octave's
// root. Built from the held notes directly so nothing ever plays
// above them.
bool wantDownFigure = hasHarmony()
&& (direction == DirectionDown || pattern == Down || pattern == DownMajor);
if (wantDownFigure) {
std::vector<NotePitch> down;
int thirdBelow = 12 - harmonyThird();
std::vector<int> tops(heldNotes.begin(), heldNotes.end());
std::sort(tops.begin(), tops.end(), [](int a, int b) { return a > b; });
for (int root : tops) {
if (std::find(tops.begin(), tops.end(), root + 12) != tops.end())
continue;
float vel = velocities[root];
for (int k = 0; k < octaves; ++k) {
auto pushClamped = [&](int pitch) {
pitch = pitch < 0 ? 0 : (pitch > 127 ? 127 : pitch);
down.push_back({pitch, vel});
};
pushClamped(root - 12 * k);
if (k < octaves - 1) {
pushClamped(root - 12 * k - 5);
pushClamped(root - 12 * k - thirdBelow);
}
}
}
return down;
}
std::vector<NotePitch> order;
auto push = [&](int i) { order.push_back(seq[i]); };
@ -283,6 +314,26 @@ private:
case AsPlayed:
order = pool;
break;
case C3C4A:
case C3C4B:
case C3C4C: {
int anchor = heldNotes.empty() ? 48 : heldNotes.back();
int up = anchor + 12;
up = up > 127 ? 127 : up;
float vel = heldNotes.empty() ? 0.9f : velocities[heldNotes.back()];
order.clear();
if (pattern == C3C4A) {
order = {{anchor, vel}, {anchor, vel}, {up, vel}, {anchor, vel},
{up, vel}, {anchor, vel}, {anchor, vel}, {up, vel}};
} else if (pattern == C3C4B) {
order = {{up, vel}, {anchor, vel}, {anchor, vel}, {anchor, vel},
{anchor, vel}, {anchor, vel}, {anchor, vel}, {anchor, vel}};
} else {
order = {{anchor, vel}, {anchor, vel}, {anchor, vel}, {up, vel},
{anchor, vel}, {anchor, vel}, {anchor, vel}, {anchor, vel}};
}
break;
}
case Chord:
case Up:
case UpMajor:
@ -291,10 +342,6 @@ private:
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);
@ -311,6 +358,11 @@ private:
order.swap(expanded);
}
if (direction == DirectionDown && pattern != Random && pattern != RandomOnce
&& pattern != AsPlayed && pattern != C3C4A && pattern != C3C4B && pattern != C3C4C)
std::sort(order.begin(), order.end(),
[](const NotePitch& a, const NotePitch& b) { return a.note > b.note; });
return order;
}

View file

@ -702,10 +702,10 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
{"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"});
"Up & Down / Minor", "C3/C4 1", "C3/C4 2", "C3/C4 3"});
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"});
setupCB(arpRateBox, arpRateAttach, "arpRate", {"1/32", "1/16", "1/8", "1/4", "1/2", "1", "2"});
setupCombo(uiScaleBox);
uiScaleBox.addItem("100%", 1);

View file

@ -283,7 +283,8 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
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));
"Pinky Down", "Up / Major", "Down / Major", "Up & Down / Minor",
"C3/C4 1", "C3/C4 2", "C3/C4 3"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"arpOctaves", 1}, "Arp Octaves",
juce::StringArray{"1", "2", "3"}, 1));
@ -292,7 +293,7 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
juce::StringArray{"Up", "Down"}, 0));
layout.add(std::make_unique<juce::AudioParameterChoice>(
juce::ParameterID{"arpRate", 1}, "Arp Rate",
juce::StringArray{"1/16", "1/8", "1/4", "1/2", "1", "2"}, 1));
juce::StringArray{"1/32", "1/16", "1/8", "1/4", "1/2", "1", "2"}, 1));
return layout;
}
@ -413,8 +414,8 @@ void ChromaFlockProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::
bool arpOn = getRaw("arpEnabled") > 0.5f;
if (arpOn) {
static const double arpRateBeats[] = {0.25, 0.5, 1.0, 2.0, 4.0, 8.0};
int rateIdx = juce::jlimit(0, 5, juce::roundToInt(getRaw("arpRate")));
static const double arpRateBeats[] = {0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0};
int rateIdx = juce::jlimit(0, 6, juce::roundToInt(getRaw("arpRate")));
arp.setParameters(true,
juce::roundToInt(getRaw("arpPattern")),
juce::roundToInt(getRaw("arpOctaves")) + 1,