Add VU meter, grid-quantized shuffle, and compact UI layout

This commit is contained in:
Armin 2026-07-20 19:27:07 +02:00
commit bd2f2dcbe6
4 changed files with 103 additions and 98 deletions

View file

@ -65,16 +65,20 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
static constexpr int shuffleDivBars[] = { 0, 4, 8, 16, 32 };
const int divBars = shuffleDivBars[juce::jlimit(0, 4, shuffleDivIdx)];
// Try to get bar position from play head
// Try to get transport info from play head
bool hasTransport = false;
int64_t currentBar = 0;
double ppq = 0.0;
double bpm = 120.0;
if (auto* ph = getPlayHead())
{
auto info = ph->getPosition();
if (info.hasValue() && info->getPpqPosition().hasValue())
{
hasTransport = true;
double ppq = *info->getPpqPosition();
ppq = *info->getPpqPosition();
if (info->getBpm().hasValue())
bpm = *info->getBpm();
double beatsPerBar = 4.0;
if (info->getTimeSignature().hasValue())
beatsPerBar = (double)(*info->getTimeSignature()).numerator;
@ -84,8 +88,14 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
const bool barAllowed = (divBars == 0) || !hasTransport || (currentBar % divBars == 0);
// 16th note grid quantization (DAW shuffle is reflected in PPQ)
const double ppqPerSample = bpm / (60.0 * currentSampleRate);
const double sixteenthPpq = 0.25;
auto nextGridPpq = [&](double fromPpq) -> double {
return std::ceil((fromPpq + 0.0001) / sixteenthPpq) * sixteenthPpq;
};
// Process pending shuffle events
const int baseDelay = (int)(currentSampleRate * 0.05 * globalGroove); // up to 50ms at max groove
for (int i = numPendingShuffle - 1; i >= 0; --i)
{
pendingShuffle[i].delaySamples -= numSamples;
@ -96,18 +106,18 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
}
}
// Independent shuffle: generate events even without MIDI input
// Independent shuffle: generate events even without MIDI input, on the same track
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
if (globalShuffleAmt > 0.0f && barAllowed)
if (globalShuffleAmt > 0.0f && barAllowed && lastTriggeredTrack >= 0)
{
std::uniform_int_distribution<int> trackDist(0, NUM_TRACKS - 1);
float prob = globalShuffleAmt * 0.15f; // base probability per block
float prob = globalShuffleAmt * 0.15f;
if (dist(rng) < prob && numPendingShuffle < MAX_PENDING_SHUFFLE)
{
int target = trackDist(rng);
float vel = (0.3f + dist(rng) * 0.5f) * globalShuffleAmt;
int delay = (int)(baseDelay * (0.5f + dist(rng) * 0.5f));
pendingShuffle[numPendingShuffle++] = { target, vel, delay };
int stepsAhead = std::uniform_int_distribution<int>(1, 4)(rng);
double gridPpq = nextGridPpq(ppq) + stepsAhead * sixteenthPpq;
int delay = juce::jmax(1, (int)((gridPpq - ppq) / ppqPerSample));
pendingShuffle[numPendingShuffle++] = { lastTriggeredTrack, vel, delay };
}
}
@ -120,6 +130,7 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
if (t >= 0 && t < NUM_TRACKS)
{
handleNoteOn(t, msg.getFloatVelocity());
lastTriggeredTrack = t;
const float shuffleAmt = apvts.getRawParameterValue(
"t" + juce::String(t) + "_shuffle")->load();
@ -127,26 +138,17 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
if (effShuffle > 0.0f && barAllowed)
{
int maxEvents = juce::jmax(1, (int)(effShuffle * 3.0f + 0.5f));
int numEvents = std::uniform_int_distribution<int>(0, maxEvents)(rng);
int numEvents = juce::jmax(1, (int)(effShuffle * 3.0f + 0.5f));
numEvents = std::uniform_int_distribution<int>(0, numEvents)(rng);
for (int e = 0; e < numEvents; ++e)
{
bool sameTrack = dist(rng) < 0.35f;
int target = t;
if (!sameTrack)
{
target = std::uniform_int_distribution<int>(0, NUM_TRACKS - 1)(rng);
if (target == t)
target = (t + 1 + std::uniform_int_distribution<int>(0, NUM_TRACKS - 2)(rng)) % NUM_TRACKS;
}
float vel = msg.getFloatVelocity() * (0.3f + dist(rng) * 0.7f) * effShuffle;
int stepsAhead = std::uniform_int_distribution<int>(1, 4)(rng);
double gridPpq = nextGridPpq(ppq) + stepsAhead * sixteenthPpq;
int delay = juce::jmax(1, (int)((gridPpq - ppq) / ppqPerSample));
if (numPendingShuffle < MAX_PENDING_SHUFFLE)
{
int delay = (int)(baseDelay * (0.5f + dist(rng) * 0.5f));
pendingShuffle[numPendingShuffle++] = { target, vel, delay };
}
pendingShuffle[numPendingShuffle++] = { t, vel, delay };
}
}
}
@ -822,7 +824,7 @@ juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::creat
layout.add(std::make_unique<AudioParameterFloat>(
"global_shuffleAmount", "Shuffle Amt",
NormalisableRange<float>(0.0f, 1.0f, 0.01f), 1.0f));
NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.0f));
layout.add(std::make_unique<AudioParameterFloat>(
"global_groove", "Groove",