midmid3 / midmid /constraints.py
markury's picture
Initial commit
d171350
"""Difficulty constraints — enforce per-difficulty fret and chord rules."""
from midmid.datatypes import NoteEvent
ALLOWED_FRETS = {
"easy": {0, 1, 2},
"medium": {0, 1, 2, 3},
"hard": {0, 1, 2, 3, 4},
"expert": {0, 1, 2, 3, 4},
}
MAX_CHORD_SIZE = {
"easy": 1,
"medium": 2,
"hard": 3,
"expert": 5,
}
MIN_NOTE_SPACING = {
"easy": 192,
"medium": 96,
"hard": 48,
"expert": 0,
}
def enforce_constraints(
notes: list[NoteEvent], difficulty: str, resolution: int = 192,
) -> list[NoteEvent]:
allowed = ALLOWED_FRETS.get(difficulty, {0, 1, 2, 3, 4})
max_chord = MAX_CHORD_SIZE.get(difficulty, 5)
min_spacing = MIN_NOTE_SPACING.get(difficulty, 0)
result = []
for note in notes:
filtered = note.fret_set & allowed
if not filtered:
for fret in sorted(note.fret_set):
closest = min(allowed, key=lambda a: abs(a - fret))
filtered.add(closest)
break
if not filtered:
continue
if len(filtered) > max_chord:
filtered = set(sorted(filtered)[:max_chord])
if min_spacing > 0 and result:
if note.tick - result[-1].tick < min_spacing:
continue
if result and result[-1].sustain_ticks > 0:
prev_end = result[-1].tick + result[-1].sustain_ticks
if note.tick < prev_end:
continue
result.append(NoteEvent(
tick=note.tick,
fret_set=filtered,
sustain_ticks=note.sustain_ticks,
is_hopo=note.is_hopo,
))
sixteenth = resolution // 4
if len(result) >= 2 and result[-2].sustain_ticks > 0:
prev = result[-2]
max_sustain = note.tick - prev.tick - sixteenth
max_sustain = (max_sustain // sixteenth) * sixteenth
if max_sustain < sixteenth:
max_sustain = 0
if prev.sustain_ticks > max_sustain:
result[-2] = NoteEvent(
tick=prev.tick,
fret_set=prev.fret_set,
sustain_ticks=max_sustain,
is_hopo=prev.is_hopo,
)
return result