mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 04:10:47 +02:00
re-work appegiator patterns
This commit is contained in:
parent
278a14053a
commit
56fc80c405
4 changed files with 122 additions and 8 deletions
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue