from __future__ import annotations from fractions import Fraction from typing import NamedTuple STANDARD_TUNING = ((6, 40), (5, 45), (4, 50), (3, 55), (2, 59), (1, 64)) MIN_FRET = 0 MAX_FRET = 20 MAX_FRET_SPAN = 4 WRITTEN_GUITAR_OCTAVE_OFFSET = 12 class TabPosition(NamedTuple): string_number: int fret: int midi: int def within_fret_span(positions) -> bool: fretted = [position.fret for position in positions if position.fret > 0] return not fretted or max(fretted) - min(fretted) <= MAX_FRET_SPAN def positions_for_pitch(midi: Fraction) -> tuple[TabPosition, ...]: if midi.denominator != 1: return () return tuple(TabPosition(string, int(midi - open_midi), int(midi)) for string, open_midi in STANDARD_TUNING if MIN_FRET <= midi - open_midi <= MAX_FRET)