Implemented note duration visualization feature that shows:
- Blue LED emitter for note-on triggers
- Full-brightness colored cells for note duration (sustain tail)
- Sustain tail at 40-60% brightness following note-on
- Fade effect on last 2-3 rows of sustain
NOTE_OFF = 97- MOD note-off commandNOTE_CUT = 98- MOD note-cut commandNOTE_FADE = 99- MOD note-fade commandNOTE_MIN = 1- Minimum valid noteNOTE_MAX = 96- Maximum valid note
interface NoteDurationInfo {
duration: number; // Total duration in rows (1-255)
rowOffset: number; // Offset from note start (0 = note-on row)
isNoteOff: boolean; // Whether this is a note-off/cut/fade row
}calculateNoteDurations(matrix) - DURA-001
- Scans pattern data per-channel to calculate note spans
- Detects note-off commands (97, 98, 99) and volume-zero cuts
- Returns 2D array of duration info for each cell
Updated packPatternMatrixHighPrecision() - DURA-002
- Calls
calculateNoteDurations()to get note span data - Packs duration into packedA bits 8-15
- Packs rowOffset and isNoteOff flag into packedB bits 8-14
packedA: [note:8][inst:8][duration:8][volPacked:8]
- note: 8 bits (0-255, where 97=note-off)
- inst: 8 bits (bit 7 is expression-only flag)
- duration: 8 bits (1-255 rows)
- volPacked: 4 bits volCmd + 4 bits volVal (upper nibbles)
packedB: [effCmd:8][effVal:8][durationFlags:7][reserved:1][volCmd:8]
- effCmd: 8 bits (effect command)
- effVal: 8 bits (effect value)
- durationFlags: 7 bits [rowOffset:6][isNoteOff:1]
- reserved: 1 bit
- volCmd: 8 bits (full volume command for shader)
const NOTE_MIN: u32 = 1u;
const NOTE_MAX: u32 = 96u;
const NOTE_OFF: u32 = 97u;
const NOTE_CUT: u32 = 98u;
const NOTE_FADE: u32 = 99u;struct NoteDurationInfo {
duration: u32, // Total note duration in rows
rowOffset: u32, // How many rows from note start (0 = note-on)
isNoteOff: bool, // Whether this cell is the note-off row
}unpackDurationInfo(packedA, packedB) - DURA-003
- Extracts duration from packedA bits 8-15
- Extracts rowOffset and isNoteOff from packedB bits 8-14
calculateSustainBrightness(info, baseIntensity) - DURA-004
- Returns full brightness on note-on row
- Returns 40-60% brightness during middle of sustain
- Fades from 60% to 30% over last 3 rows
isSustaining(info, hasNote) - DURA-005
- Returns true if cell is within the note's sustain period
calculateBlueIntensity(info, hasNote, isPlayhead, trigger, beatPhase) - DURA-006
- Bright blue flash (100%+) on note trigger
- Dim blue glow (60%) on playhead row
- Fading blue glow (40%→20%) during sustain tail
- Reads new packed format with duration data
- Uses
calculateBlueIntensity()for top emitter - Uses
calculateSustainBrightness()for middle emitter brightness - Shows full note color on note-on rows
- Shows 40-60% brightness during sustain
- Shows fade effect on last 2-3 rows of sustain
| Task | Description | Status |
|---|---|---|
| DURA-001 | Implement note duration calculation | ✅ Complete |
| DURA-002 | Add noteDuration field to packed cell struct | ✅ Complete |
| DURA-003 | Add isSustained boolean to row data | ✅ Complete |
| DURA-004 | Implement blue LED emitter for note triggers | ✅ Complete |
| DURA-005 | Tie glow intensity to volume/expression | ✅ Partial (structure ready) |
| DURA-006 | Add current row pulse on note trigger | ✅ Complete |
- Note trigger row: Bright blue flash (100% + bloom)
- Playhead on trigger: Blue glow (60%)
- Sustain tail: Dim blue glow fading 40% → 20%
- Muted: 20% intensity
- Note-on row: Full brightness (80% + bloom)
- Sustain middle: 40-60% brightness
- Last 3 rows: Fade 60% → 30%
- Muted: 25% of calculated brightness
- Expression-only: 60% + bloom
- Note-on + expression: 100% + bloom (DURA enhancement)
- Note + expression: 40% + bloom
To test the implementation:
- Load a MOD file with sustained notes
- Observe blue LED flash on note triggers
- Observe sustain tail with reduced brightness (40-60%)
- Observe fade effect on last 2-3 rows of sustain
- Check that note-off commands properly terminate the sustain visualization
✅ TypeScript compilation successful ✅ Vite build successful ✅ No errors