Skip to content

Commit ffaf2c0

Browse files
Enkerliclaude
andcommitted
Comprehensive documentation and code cleanup for Steps mode
DOCUMENTATION ADDED: - Detailed Steps mode documentation in code comments explaining microrhythm functionality - README.md: New "Pattern Timing Modes" section with Steps/Beats/Bars explanations - CLAUDE.md: Complete Steps mode implementation history and technical details - Subdivision parameter documentation with beat fraction examples CODE CLEANUP: - Removed unused variable currentBPMInt causing compiler warnings - Removed duplicate rotatePatternBySteps() function - replaced with UPIParser::rotatePattern() - Eliminated function duplication and improved code maintainability - Debug file I/O already disabled for production performance STEPS MODE DOCUMENTATION: - Function: Each step = subdivision (16th, 8th, 8th triplet, etc.) - Pattern length completely ignored in Steps mode - Formula: subdivision_duration × pattern_steps = total_duration - Examples: 8×16th=2beats, 9×8th=4.5beats, 9×8th_triplet=3beats - Use case: Microrhythmic variations by changing subdivision Code is now well-documented and cleaned for other developers. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8fad72b commit ffaf2c0

File tree

4 files changed

+83
-25
lines changed

4 files changed

+83
-25
lines changed

CLAUDE.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,31 @@
2121

2222
## Recent Issues Resolved
2323

24+
### CRITICAL: Steps Mode Implementation (July 2025)
25+
**Problem**: Steps mode was broken - step indicator jumped around and subdivision parameter had no effect.
26+
27+
**Root Cause**: Multiple issues:
28+
1. Two different Steps mode calculations with inconsistent logic
29+
2. Main timing calculation used old logic: `patternLengthInBeats = lengthValue / 4.0` (wrong)
30+
3. Transport sync used correct logic: `patternLengthInBeats = subdivisionBeatsPerStep * patternSteps` (right)
31+
4. Step calculation used wrong modulo approach causing jumping
32+
33+
**Solution**:
34+
1. **Unified Steps Mode Logic**: Both main and transport calculations now use identical logic
35+
2. **Pattern Length Ignored**: In Steps mode, pattern length value is completely ignored
36+
3. **Subdivision Control**: Each step represents selected subdivision (16th, 8th, 8th triplet, etc.)
37+
4. **Fixed Step Calculation**: Used `fmod()` for proper pattern cycling instead of integer modulo
38+
5. **Removed Debug File I/O**: Eliminated performance-killing debug operations from audio callbacks
39+
40+
**Steps Mode Formula**: `total_pattern_duration = subdivision_duration × pattern_steps`
41+
42+
**Examples**:
43+
- 8 steps × 16th notes = 8 × 0.25 = 2 beats (half note duration)
44+
- 9 steps × 8th notes = 9 × 0.5 = 4.5 beats total
45+
- 9 steps × 8th triplets = 9 × (1/3) = 3 beats total
46+
47+
**Result**: Steps mode now provides proper "microrhythm" functionality where the same pattern can be played at different rhythmic resolutions.
48+
2449
### CRITICAL: MIDI Trigger Logic Fix (July 2025)
2550
**Problem**: Patterns with both progressive transformations and scenes (like `{1000000}E(1,16)E>16|1010101010101010`) only triggered progressive transformations via MIDI input, never advancing to scenes, unlike Enter key behavior.
2651

Plugin/Source/PluginProcessor.cpp

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ void RhythmPatternExplorerAudioProcessor::processBlock (juce::AudioBuffer<float>
220220

221221
// Log every 50 calls or when BPM changes, with focus on 200+ BPM
222222
debugCallCount++;
223-
int currentBPMInt = static_cast<int>(currentBPM);
224223

225224
// More frequent logging at high BPMs
226225
bool shouldLog = false;
@@ -378,6 +377,19 @@ void RhythmPatternExplorerAudioProcessor::processBlock (juce::AudioBuffer<float>
378377
switch (lengthUnit) {
379378
case 0: // Steps mode - each step represents a subdivision, pattern length is IGNORED
380379
{
380+
// STEPS MODE DOCUMENTATION:
381+
// In Steps mode, each step represents a specific subdivision (16th, 8th, 8th triplet, etc.)
382+
// The subdivision parameter determines what each step represents.
383+
// Pattern length value is completely ignored - only subdivision and pattern size matter.
384+
//
385+
// Examples:
386+
// - 8 steps × 16th notes = 8 × 0.25 = 2 beats total (half note duration)
387+
// - 9 steps × 8th notes = 9 × 0.5 = 4.5 beats total
388+
// - 9 steps × 8th triplets = 9 × (1/3) = 3 beats total
389+
//
390+
// This creates "microrhythm" functionality where the same pattern can be
391+
// played at different rhythmic resolutions by changing the subdivision.
392+
381393
// Convert subdivision index to beat fraction per step
382394
double subdivisionBeatsPerStep = getSubdivisionInBeats(subdivisionIndex);
383395

@@ -743,6 +755,7 @@ void RhythmPatternExplorerAudioProcessor::syncPositionWithHost(const juce::Audio
743755
switch (lengthUnit) {
744756
case 0: // Steps mode - each step represents a subdivision, pattern length is IGNORED
745757
{
758+
// Steps mode: Same logic as main calculation - subdivision determines step duration
746759
double subdivisionBeatsPerStep = getSubdivisionInBeats(subdivisionIndex);
747760
patternLengthInBeats = subdivisionBeatsPerStep * patternSteps;
748761

@@ -969,7 +982,7 @@ void RhythmPatternExplorerAudioProcessor::setUPIInput(const juce::String& upiPat
969982
if (progressiveOffset != 0)
970983
{
971984
auto currentPattern = patternEngine.getCurrentPattern();
972-
auto rotatedPattern = rotatePatternBySteps(currentPattern, progressiveOffset);
985+
auto rotatedPattern = UPIParser::rotatePattern(currentPattern, progressiveOffset);
973986
patternEngine.setPattern(rotatedPattern);
974987

975988
// Debug log rotation
@@ -1253,26 +1266,6 @@ void RhythmPatternExplorerAudioProcessor::checkMidiInputForTriggers(juce::MidiBu
12531266
}
12541267
}
12551268

1256-
std::vector<bool> RhythmPatternExplorerAudioProcessor::rotatePatternBySteps(const std::vector<bool>& pattern, int steps)
1257-
{
1258-
if (pattern.empty())
1259-
return pattern;
1260-
1261-
std::vector<bool> rotated(pattern.size());
1262-
int size = static_cast<int>(pattern.size());
1263-
1264-
// Normalize steps to be within pattern size
1265-
steps = steps % size;
1266-
if (steps < 0) steps += size;
1267-
1268-
for (int i = 0; i < size; ++i)
1269-
{
1270-
int newIndex = (i + steps) % size;
1271-
rotated[newIndex] = pattern[i];
1272-
}
1273-
1274-
return rotated;
1275-
}
12761269

12771270
void RhythmPatternExplorerAudioProcessor::advanceProgressiveLengthening()
12781271
{
@@ -1379,7 +1372,7 @@ void RhythmPatternExplorerAudioProcessor::applyCurrentScenePattern()
13791372
{
13801373
// Apply progressive offset by rotating the generated pattern
13811374
auto currentPattern = patternEngine.getCurrentPattern();
1382-
auto rotatedPattern = rotatePatternBySteps(currentPattern, progressiveOffset);
1375+
auto rotatedPattern = UPIParser::rotatePattern(currentPattern, progressiveOffset);
13831376
patternEngine.setPattern(rotatedPattern);
13841377

13851378
logDebug(DebugCategory::SCENE_CYCLING,
@@ -1503,8 +1496,19 @@ float RhythmPatternExplorerAudioProcessor::getPatternLengthValue() const
15031496

15041497
double RhythmPatternExplorerAudioProcessor::getSubdivisionInBeats(int subdivisionIndex) const
15051498
{
1506-
// Convert subdivision choice to beat fractions
1499+
// SUBDIVISION PARAMETER DOCUMENTATION:
1500+
// This function converts subdivision parameter choices to beat fractions for Steps mode.
1501+
// Each subdivision represents how much of a beat one step occupies.
1502+
//
15071503
// Subdivision choices: {"64th Triplet", "64th", "32nd Triplet", "32nd", "16th Triplet", "16th", "8th Triplet", "8th", "Quarter Triplet", "Quarter", "Half Triplet", "Half", "Whole"}
1504+
// Default: index 5 = "16th" = 0.25 beats per step
1505+
//
1506+
// Examples:
1507+
// - 16th notes: 0.25 beats per step (4 steps = 1 beat)
1508+
// - 8th notes: 0.5 beats per step (2 steps = 1 beat)
1509+
// - 8th triplets: 1/3 beats per step (3 steps = 1 beat)
1510+
// - Quarter notes: 1.0 beats per step (1 step = 1 beat)
1511+
15081512
static const double subdivisionBeats[] = {
15091513
1.0/24.0, // 64th Triplet (1/64 * 2/3 = 1/96, but musically 1/24 makes more sense)
15101514
1.0/16.0, // 64th

Plugin/Source/PluginProcessor.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,6 @@ class RhythmPatternExplorerAudioProcessor : public juce::AudioProcessor
290290
static const char* getLogFile(DebugCategory category);
291291

292292
// Pattern manipulation
293-
std::vector<bool> rotatePatternBySteps(const std::vector<bool>& pattern, int steps);
294293
std::vector<bool> generateBellCurveRandomSteps(int numSteps);
295294
std::vector<bool> lengthenPattern(const std::vector<bool>& pattern, int additionalSteps);
296295

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,36 @@ python3 -m http.server 8000
8787
- **Visual Feedback**: Red for accented onsets, green for regular onsets
8888
- **Universal Support**: Works with all pattern types (E, P, R, B, W, D)
8989

90+
### Pattern Timing Modes (v0.03e+)
91+
Three distinct modes for controlling pattern timing and duration:
92+
93+
#### **Steps Mode** (Microrhythm Control)
94+
- **Function**: Each step represents a specific subdivision (16th, 8th, 8th triplet, etc.)
95+
- **Subdivision Parameter**: Determines what each step represents
96+
- **Pattern Length**: **Completely ignored** - only subdivision and pattern size matter
97+
- **Calculation**: `subdivision_duration × pattern_steps = total_duration`
98+
99+
**Examples:**
100+
```
101+
8 steps × 16th notes = 8 × 0.25 = 2 beats (half note duration)
102+
9 steps × 8th notes = 9 × 0.5 = 4.5 beats total
103+
9 steps × 8th triplets = 9 × (1/3) = 3 beats total
104+
```
105+
106+
**Use Case**: Create microrhythmic variations by changing subdivision while keeping the same pattern.
107+
108+
#### **Beats Mode** (Divisive Timing)
109+
- **Function**: Pattern fits exactly within specified number of beats
110+
- **Pattern Length**: Determines total duration in beats
111+
- **Calculation**: `pattern_fits_in_X_beats`
112+
- **Use Case**: Ensure patterns align with specific beat durations
113+
114+
#### **Bars Mode** (Metric Timing)
115+
- **Function**: Pattern fits exactly within specified number of bars (assumes 4/4 time)
116+
- **Pattern Length**: Determines total duration in bars
117+
- **Calculation**: `pattern_fits_in_X_bars × 4_beats_per_bar`
118+
- **Use Case**: Create patterns that span multiple bars precisely
119+
90120
### UPI (Universal Pattern Input) Notation
91121

92122
#### Basic Pattern Generation

0 commit comments

Comments
 (0)