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

@ -51,7 +51,7 @@ target_compile_definitions(Trommelkiste PRIVATE
JUCE_WEB_BROWSER=0 JUCE_WEB_BROWSER=0
JUCE_USE_CURL=0 JUCE_USE_CURL=0
JUCE_VST3_CAN_REPLACE_VST2=0 JUCE_VST3_CAN_REPLACE_VST2=0
JUCE_DISPLAY_SPLASH_SCREEN=0
) )
target_link_libraries(Trommelkiste target_link_libraries(Trommelkiste

View file

@ -9,7 +9,7 @@ static const char* trackNames[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "R
static const char* noteNames[] = {"C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4"}; static const char* noteNames[] = {"C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4"};
static constexpr int ROW_H = 52; static constexpr int ROW_H = 52;
static constexpr int TOP = 100; static constexpr int TOP = 82;
static constexpr int COL_LBL = 8; static constexpr int COL_LBL = 8;
static constexpr int COL_KNOB = 44; static constexpr int COL_KNOB = 44;
static constexpr int KNOB_W = 44; static constexpr int KNOB_W = 44;
@ -22,7 +22,7 @@ static constexpr int COL_REVERB = COL_DELAY + KNOB_W + KNOB_GAP;
static constexpr int COL_RINGMOD = COL_REVERB + KNOB_W + KNOB_GAP; static constexpr int COL_RINGMOD = COL_REVERB + KNOB_W + KNOB_GAP;
static constexpr int COL_DESTR = COL_RINGMOD + KNOB_W + KNOB_GAP; static constexpr int COL_DESTR = COL_RINGMOD + KNOB_W + KNOB_GAP;
static constexpr int COL_SHUFFLE = COL_DESTR + KNOB_W + KNOB_GAP; static constexpr int COL_SHUFFLE = COL_DESTR + KNOB_W + KNOB_GAP;
static constexpr int TOTAL_W = COL_SHUFFLE + KNOB_W + 8; static constexpr int TOTAL_W = COL_SHUFFLE + KNOB_W;
// Header knob x positions (compact layout) // Header knob x positions (compact layout)
static constexpr int HDR_KNOB = 36; static constexpr int HDR_KNOB = 36;
@ -60,30 +60,13 @@ void TrommelkisteLookAndFeel::drawRotarySlider(Graphics& g, int x, int y, int w,
// Main body — vertical gradient (light top, dark bottom) for 3D cylinder feel // Main body — vertical gradient (light top, dark bottom) for 3D cylinder feel
{ {
ColourGradient grad(Colour(0xFF444444), cx, cy - radius, ColourGradient grad(Colour(0xFF323232), cx, cy - radius,
Colour(0xFF2C2C2C), cx, cy + radius, true); Colour(0xFF1C1C1C), cx, cy + radius, true);
grad.addColour(0.35, Colour(0xFF3D3D3D)); grad.addColour(0.10, Colour(0x883D3D3D));
g.setGradientFill(grad); g.setGradientFill(grad);
g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f);
} }
// Top highlight ring (specular edge)
{
Path ring;
ring.addCentredArc(cx, cy, radius - 0.5f, radius - 0.5f, 0.0f, 0.0f, MathConstants<float>::pi, true);
g.setColour(Colour(0x1EFFFFFF));
g.strokePath(ring, PathStrokeType(1.5f, PathStrokeType::curved, PathStrokeType::rounded));
}
// Bottom dark ring
{
Path ring;
ring.addCentredArc(cx, cy, radius - 0.5f, radius - 0.5f, 0.0f,
MathConstants<float>::pi, MathConstants<float>::pi * 2.0f, true);
g.setColour(Colour(0x1E000000));
g.strokePath(ring, PathStrokeType(1.5f, PathStrokeType::curved, PathStrokeType::rounded));
}
// Subtle specular highlight blob (top-left) // Subtle specular highlight blob (top-left)
{ {
ColourGradient spec(Colour(0x18FFFFFF), cx - radius * 0.35f, cy - radius * 0.5f, ColourGradient spec(Colour(0x18FFFFFF), cx - radius * 0.35f, cy - radius * 0.5f,
@ -207,7 +190,7 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
setLookAndFeel(&lnf); setLookAndFeel(&lnf);
lnf.fontScale = 1.5f; lnf.fontScale = 1.5f;
uiScale = 1.5f; uiScale = 1.5f;
setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H + 12)); setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H));
setResizable(false, false); setResizable(false, false);
setWantsKeyboardFocus(false); setWantsKeyboardFocus(false);
@ -388,7 +371,7 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p)
globalShuffleDiv.setRange(0, 4, 1); globalShuffleDiv.setRange(0, 4, 1);
globalShuffleDiv.setScrollWheelEnabled(false); globalShuffleDiv.setScrollWheelEnabled(false);
globalShuffleDiv.formatFn = [](double v) { globalShuffleDiv.formatFn = [](double v) {
static const char* labels[] = { "1", "4", "8", "16", "32" }; static const char* labels[] = { "OFF", "4", "8", "16", "32" };
return labels[juce::jlimit(0, 4, (int)v)]; return labels[juce::jlimit(0, 4, (int)v)];
}; };
addAndMakeVisible(globalShuffleDiv); addAndMakeVisible(globalShuffleDiv);
@ -433,7 +416,7 @@ void TrommelkisteEditor::timerCallback()
const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0; const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0;
for (int i = 0; i < NUM_TRACKS; ++i) for (int i = 0; i < NUM_TRACKS; ++i)
{ {
if (proc.trackActive[i]) if (proc.trackActive[i].exchange(false))
trackActivatedTime[i] = now; trackActivatedTime[i] = now;
} }
repaint(); repaint();
@ -447,7 +430,7 @@ void TrommelkisteEditor::scaleChanged()
{ {
uiScale = scales[selId - 1]; uiScale = scales[selId - 1];
lnf.fontScale = uiScale; lnf.fontScale = uiScale;
setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H + 12)); setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H));
} }
} }
@ -479,52 +462,71 @@ void TrommelkisteEditor::paint(Graphics& g)
g.setFont(fontBold(16.0f, uiScale)); g.setFont(fontBold(16.0f, uiScale));
g.drawText("TROMMELKISTE", scaled(10), scaled(4), scaled(400), scaled(20), Justification::centredLeft); g.drawText("TROMMELKISTE", scaled(10), scaled(4), scaled(400), scaled(20), Justification::centredLeft);
g.setColour(lnf.accent.withAlpha(0.3f)); // VU meter
g.fillRect(scaled(10), scaled(71), scaled(getWidth() - 20), scaled(1)); {
const float level = proc.outputLevel.load(std::memory_order_relaxed);
const float meterX = 10.0f;
const float meterY = 30.0f;
const float meterW = 134.0f;
const float meterH = 6.0f;
// Section labels // Background
g.setColour(Colours::green.brighter(0.6f)); g.setColour(Colour(0xFF111111));
g.setFont(font(8.0f, uiScale)); g.fillRect(scaled((int) meterX), scaled((int) meterY), scaled((int) meterW), scaled((int) meterH));
int masterCenter = (HDR_VOL + HDR_DLYSYNC + HDR_KNOB) / 2;
int shuffleCenter = (HDR_DIV + HDR_GRV + HDR_KNOB) / 2; // Level bar (dB normalized: -40 dB..0 dB maps to 0..1)
g.drawText("MASTER", scaled(masterCenter - 20), scaled(0), scaled(40), scaled(8), Justification::centred); float norm = 0.0f;
g.drawText("SHUFFLE", scaled(shuffleCenter - 20), scaled(0), scaled(40), scaled(8), Justification::centred); if (level > 0.001f)
{
float db = 20.0f * std::log10(level);
norm = jlimit(0.0f, 1.0f, (db + 40.0f) / 40.0f);
}
float barW = meterW * norm;
g.setColour(lnf.accent);
g.fillRect(scaled((int) meterX), scaled((int) meterY), scaled((int) barW), scaled((int) meterH));
}
g.setColour(lnf.accent.withAlpha(0.3f));
g.fillRect(scaled(10), scaled(60), getWidth() - scaled(15), scaled(1));
g.setColour(Colours::white.withAlpha(0.9f)); g.setColour(Colours::white.withAlpha(0.9f));
g.setFont(font(8.0f, uiScale)); g.setFont(font(8.0f, uiScale));
g.drawText("VOL", scaled(HDR_VOL), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("VOL", scaled(HDR_VOL), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("PAN", scaled(HDR_PAN), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("PAN", scaled(HDR_PAN), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("RING", scaled(HDR_RING), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("RING", scaled(HDR_RING), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("DEST", scaled(HDR_DEST), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("DEST", scaled(HDR_DEST), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("DLY", scaled(HDR_DELAY), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("DLY", scaled(HDR_DELAY), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("REV", scaled(HDR_REVERB), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("REV", scaled(HDR_REVERB), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("SYNC", scaled(HDR_DLYSYNC), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("SYNC", scaled(HDR_DLYSYNC), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
// Shuffle section separator // Shuffle section separator
g.setColour(lnf.accent.withAlpha(0.5f)); g.setColour(lnf.accent.withAlpha(0.5f));
g.fillRect(scaled(HDR_DIV - 8), scaled(6), scaled(1), scaled(65)); g.fillRect(scaled(HDR_DIV - 8), scaled(6), scaled(1), scaled(46));
g.setColour(Colour(0xFF3A3A1E).withAlpha(0.4f));
g.fillRect(scaled(HDR_RING), scaled(8), scaled(HDR_KNOB), scaled(HDR_KNOB));
g.setColour(Colours::white.withAlpha(0.9f)); g.setColour(Colours::white.withAlpha(0.9f));
g.setFont(font(8.0f, uiScale)); g.setFont(font(8.0f, uiScale));
g.drawText("EVERY", scaled(HDR_DIV), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("DIV", scaled(HDR_DIV), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("AMT", scaled(HDR_SHFL), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("SHFL", scaled(HDR_SHFL), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.drawText("GROOVE", scaled(HDR_GRV), scaled(51), scaled(HDR_KNOB), scaled(10), Justification::centred); g.drawText("GRV", scaled(HDR_GRV), scaled(46), scaled(HDR_KNOB), scaled(10), Justification::centred);
g.setColour(Colours::white.withAlpha(0.9f)); g.setColour(Colours::white.withAlpha(0.9f));
g.setFont(font(9.0f, uiScale)); g.setFont(font(9.0f, uiScale));
g.drawText("LVL", scaled(COL_KNOB), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("LVL", scaled(COL_KNOB), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("LEN", scaled(COL_KNOB + 1*(KNOB_W + KNOB_GAP)), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("LEN", scaled(COL_KNOB + 1*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("VEL", scaled(COL_KNOB + 2*(KNOB_W + KNOB_GAP)), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("VEL", scaled(COL_KNOB + 2*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("PIT", scaled(COL_KNOB + 3*(KNOB_W + KNOB_GAP)), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("PIT", scaled(COL_KNOB + 3*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("TONE", scaled(COL_KNOB + 4*(KNOB_W + KNOB_GAP)), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("TONE", scaled(COL_KNOB + 4*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("PAN", scaled(COL_KNOB + 5*(KNOB_W + KNOB_GAP)), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("PAN", scaled(COL_KNOB + 5*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("DECAY", scaled(COL_DECAY), scaled(78), scaled(68), scaled(12), Justification::centred); g.drawText("DECAY", scaled(COL_DECAY), scaled(65), scaled(68), scaled(12), Justification::centred);
g.drawText("SAMPLE", scaled(COL_FILE), scaled(78), scaled(SAMPLE_W), scaled(12), Justification::centred); g.drawText("SAMPLE", scaled(COL_FILE), scaled(65), scaled(SAMPLE_W), scaled(12), Justification::centred);
g.drawText("DELAY", scaled(COL_DELAY), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("DELAY", scaled(COL_DELAY), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("REVERB", scaled(COL_REVERB), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("REVERB", scaled(COL_REVERB), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("RING", scaled(COL_RINGMOD), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("RING", scaled(COL_RINGMOD), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("DESTR", scaled(COL_DESTR), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("DESTR", scaled(COL_DESTR), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
g.drawText("SHFL", scaled(COL_SHUFFLE), scaled(78), scaled(KNOB_W), scaled(12), Justification::centred); g.drawText("SHFL", scaled(COL_SHUFFLE), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred);
const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0; const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0;
for (int i = 0; i < NUM_TRACKS; ++i) for (int i = 0; i < NUM_TRACKS; ++i)
@ -559,11 +561,11 @@ void TrommelkisteEditor::paint(Graphics& g)
g.setColour(Colours::white); g.setColour(Colours::white);
g.setFont(fontBold(12.0f, uiScale)); g.setFont(fontBold(12.0f, uiScale));
g.drawText(trackNames[i], scaled(COL_LBL), scaled(ry + 6), scaled(32), scaled(16), Justification::centredLeft); g.drawText(trackNames[i], scaled(COL_LBL), scaled(ry + 14), scaled(32), scaled(16), Justification::centredLeft);
g.setColour(Colour(0xFF666666)); g.setColour(Colour(0xFF666666));
g.setFont(font(9.0f, uiScale)); g.setFont(font(9.0f, uiScale));
g.drawText(noteNames[i], scaled(COL_LBL), scaled(ry + 24), scaled(32), scaled(14), Justification::centredLeft); g.drawText(noteNames[i], scaled(COL_LBL), scaled(ry + 28), scaled(32), scaled(14), Justification::centredLeft);
const double elapsed = now - trackActivatedTime[i]; const double elapsed = now - trackActivatedTime[i];
if (elapsed < 0.4) if (elapsed < 0.4)
@ -585,16 +587,16 @@ void TrommelkisteEditor::resized()
// Header knobs // Header knobs
const int hk = HDR_KNOB; const int hk = HDR_KNOB;
masterVolume.setBounds(scaled(HDR_VOL), scaled(13), scaled(hk), scaled(hk)); masterVolume.setBounds(scaled(HDR_VOL), scaled(8), scaled(hk), scaled(hk));
masterPan.setBounds(scaled(HDR_PAN), scaled(13), scaled(hk), scaled(hk)); masterPan.setBounds(scaled(HDR_PAN), scaled(8), scaled(hk), scaled(hk));
globalRingFreq.setBounds(scaled(HDR_RING), scaled(13), scaled(hk), scaled(hk)); globalRingFreq.setBounds(scaled(HDR_RING), scaled(8), scaled(hk), scaled(hk));
globalDestruction.setBounds(scaled(HDR_DEST), scaled(13), scaled(hk), scaled(hk)); globalDestruction.setBounds(scaled(HDR_DEST), scaled(8), scaled(hk), scaled(hk));
masterDelay.setBounds(scaled(HDR_DELAY), scaled(13), scaled(hk), scaled(hk)); masterDelay.setBounds(scaled(HDR_DELAY), scaled(8), scaled(hk), scaled(hk));
masterReverb.setBounds(scaled(HDR_REVERB), scaled(13), scaled(hk), scaled(hk)); masterReverb.setBounds(scaled(HDR_REVERB), scaled(8), scaled(hk), scaled(hk));
delaySyncDiv.setBounds(scaled(HDR_DLYSYNC), scaled(13), scaled(hk), scaled(hk)); delaySyncDiv.setBounds(scaled(HDR_DLYSYNC), scaled(8), scaled(hk), scaled(hk));
globalShuffleDiv.setBounds(scaled(HDR_DIV), scaled(13), scaled(hk), scaled(hk)); globalShuffleDiv.setBounds(scaled(HDR_DIV), scaled(8), scaled(hk), scaled(hk));
globalShuffleAmt.setBounds(scaled(HDR_SHFL), scaled(13), scaled(hk), scaled(hk)); globalShuffleAmt.setBounds(scaled(HDR_SHFL), scaled(8), scaled(hk), scaled(hk));
globalGroove.setBounds(scaled(HDR_GRV), scaled(13), scaled(hk), scaled(hk)); globalGroove.setBounds(scaled(HDR_GRV), scaled(8), scaled(hk), scaled(hk));
for (int i = 0; i < NUM_TRACKS; ++i) for (int i = 0; i < NUM_TRACKS; ++i)
{ {

View file

@ -65,16 +65,20 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
static constexpr int shuffleDivBars[] = { 0, 4, 8, 16, 32 }; static constexpr int shuffleDivBars[] = { 0, 4, 8, 16, 32 };
const int divBars = shuffleDivBars[juce::jlimit(0, 4, shuffleDivIdx)]; 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; bool hasTransport = false;
int64_t currentBar = 0; int64_t currentBar = 0;
double ppq = 0.0;
double bpm = 120.0;
if (auto* ph = getPlayHead()) if (auto* ph = getPlayHead())
{ {
auto info = ph->getPosition(); auto info = ph->getPosition();
if (info.hasValue() && info->getPpqPosition().hasValue()) if (info.hasValue() && info->getPpqPosition().hasValue())
{ {
hasTransport = true; hasTransport = true;
double ppq = *info->getPpqPosition(); ppq = *info->getPpqPosition();
if (info->getBpm().hasValue())
bpm = *info->getBpm();
double beatsPerBar = 4.0; double beatsPerBar = 4.0;
if (info->getTimeSignature().hasValue()) if (info->getTimeSignature().hasValue())
beatsPerBar = (double)(*info->getTimeSignature()).numerator; 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); 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 // 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) for (int i = numPendingShuffle - 1; i >= 0; --i)
{ {
pendingShuffle[i].delaySamples -= numSamples; 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); 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;
float prob = globalShuffleAmt * 0.15f; // base probability per block
if (dist(rng) < prob && numPendingShuffle < MAX_PENDING_SHUFFLE) if (dist(rng) < prob && numPendingShuffle < MAX_PENDING_SHUFFLE)
{ {
int target = trackDist(rng);
float vel = (0.3f + dist(rng) * 0.5f) * globalShuffleAmt; float vel = (0.3f + dist(rng) * 0.5f) * globalShuffleAmt;
int delay = (int)(baseDelay * (0.5f + dist(rng) * 0.5f)); int stepsAhead = std::uniform_int_distribution<int>(1, 4)(rng);
pendingShuffle[numPendingShuffle++] = { target, vel, delay }; 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) if (t >= 0 && t < NUM_TRACKS)
{ {
handleNoteOn(t, msg.getFloatVelocity()); handleNoteOn(t, msg.getFloatVelocity());
lastTriggeredTrack = t;
const float shuffleAmt = apvts.getRawParameterValue( const float shuffleAmt = apvts.getRawParameterValue(
"t" + juce::String(t) + "_shuffle")->load(); "t" + juce::String(t) + "_shuffle")->load();
@ -127,26 +138,17 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce:
if (effShuffle > 0.0f && barAllowed) if (effShuffle > 0.0f && barAllowed)
{ {
int maxEvents = juce::jmax(1, (int)(effShuffle * 3.0f + 0.5f)); int numEvents = juce::jmax(1, (int)(effShuffle * 3.0f + 0.5f));
int numEvents = std::uniform_int_distribution<int>(0, maxEvents)(rng); numEvents = std::uniform_int_distribution<int>(0, numEvents)(rng);
for (int e = 0; e < numEvents; ++e) 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; 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) if (numPendingShuffle < MAX_PENDING_SHUFFLE)
{ pendingShuffle[numPendingShuffle++] = { t, vel, delay };
int delay = (int)(baseDelay * (0.5f + dist(rng) * 0.5f));
pendingShuffle[numPendingShuffle++] = { target, vel, delay };
}
} }
} }
} }
@ -822,7 +824,7 @@ juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::creat
layout.add(std::make_unique<AudioParameterFloat>( layout.add(std::make_unique<AudioParameterFloat>(
"global_shuffleAmount", "Shuffle Amt", "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>( layout.add(std::make_unique<AudioParameterFloat>(
"global_groove", "Groove", "global_groove", "Groove",

View file

@ -126,6 +126,7 @@ private:
static constexpr int MAX_PENDING_SHUFFLE = 32; static constexpr int MAX_PENDING_SHUFFLE = 32;
PendingShuffleEvent pendingShuffle[MAX_PENDING_SHUFFLE]; PendingShuffleEvent pendingShuffle[MAX_PENDING_SHUFFLE];
int numPendingShuffle = 0; int numPendingShuffle = 0;
int lastTriggeredTrack = -1;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteProcessor) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteProcessor)
}; };