BeatGrid
BeatGrid(
length,
beats_per_measure=4,
beat_unit=Fraction(1, 4),
start_measure=1,
start_mn=None,
anacrusis_quarters=None,
uid=None,
name=None,
)A metrical grid as a ContinuousLogicalTimeline in quarters.
A BeatGrid represents metrical structure: measures, beats, and their numbering. It is designed to be added as a child to any parent timeline (physical, logical, or graphical).
The coordinate system uses quarter notes (Fractions) for exact rhythmic representation. Built-in C-Maps automatically convert quarters to measure numbers and beat positions.
Architecture: BeatGrid now uses the generalized MetricMap internally, which correctly handles: - Proper integer types for measure counts (MC) - Proper Fraction types for beat positions - Anacrusis (pickup measures) - Varying time signatures (via MetricMap.from_boundaries)
Attributes: beats_per_measure: Number of beats per measure. beat_unit: The note value of one beat (e.g., Fraction(1, 4) for quarter note). start_measure: The number of the first measure (default 1). quarters_per_measure: Derived: quarters per measure.
C-Maps (automatically created): - quarters -> mc (MetricMap): Integer measure count - quarters -> beat (BeatInMeasureMap): Beat position as Fraction (1-indexed) - quarters -> {mc, beat} (MetricalPositionMap): Combined output
Examples: >>> from fractions import Fraction >>> from timetoalign import ContinuousPhysicalTimeline, TimeUnit >>> >>> # Create a beat grid for 4/4 time, 222 measures >>> grid = BeatGrid( … length=Fraction(888, 1), # 888 quarter notes … beats_per_measure=4, … ) >>> >>> # Query measure and beat at quarter 100 >>> grid.measure_at(100) # -> 26 (integer!) >>> grid.beat_at(100) # -> Fraction(1, 1) (proper Fraction!) >>> grid.metrical_position(100) # -> {“mc”: 26, “beat”: Fraction(1, 1)} >>> >>> # Attach to audio timeline >>> audio = ContinuousPhysicalTimeline(length=300.0, unit=TimeUnit.seconds) >>> audio.add_child(grid, offset=1.3) # First beat at 1.3 seconds >>> >>> # Create from tempo >>> grid2 = BeatGrid.from_tempo( … tempo_bpm=120.0, … beats_per_measure=4, … length_quarters=Fraction(888, 1), … )
Attributes
| Name | Description |
|---|---|
| beat_unit | Note value of one beat (fraction of whole note). |
| beats_per_measure | Number of beats per measure. |
| meter_map | The underlying MetricMap (for advanced access). |
| n_beats | Total number of beats in this grid. |
| n_measures | Number of complete measures in this grid. |
| quarters_per_beat | Quarter notes per beat. |
| quarters_per_measure | Quarter notes per measure. |
| start_measure | MC (measure count) of the first measure. |
| start_mn | MN (measure number label) of the first measure. |
| tempo_bpm | Tempo in BPM, if created via from_tempo(). |
Methods
| Name | Description |
|---|---|
| beat_at | Get the beat position within the measure at a given quarter-note position. |
| beat_at_float | Get the beat position as a float (for backward compatibility). |
| beat_at_seconds | Get the beat number within the measure at a given time in seconds. |
| beat_quarters | All beat positions in quarters. Vectorized O(1). |
| beat_seconds | All beat times in seconds. Vectorized O(1). |
| downbeat_seconds | Alias for measure_seconds(). All downbeat times in seconds. |
| export_to_csv | Export BeatGrid data to a CSV file. |
| from_tempo | Create a BeatGrid from tempo information. |
| materialize_beats | Add Beat events to this timeline at each beat position. |
| materialize_measures | Add Measure events to this timeline at each measure boundary. |
| measure_at | Get the measure count (MC) at a given quarter-note position. |
| measure_at_seconds | Get the measure number at a given time in seconds. |
| measure_quarters | All measure start positions in quarters. Vectorized O(1). |
| measure_seconds | All measure start times in seconds. Vectorized O(1). |
| metrical_position | Get the full metrical position (mc and beat) at a given quarter position. |
| mn_at | Get the measure number label (MN) at a given quarter-note position. |
| quarter_at | Get the quarter-note position for a given measure and beat. |
beat_at
BeatGrid.beat_at(quarters)Get the beat position within the measure at a given quarter-note position.
Args: quarters: Position in quarter notes.
Returns: The beat position as Fraction (1-indexed, e.g., Fraction(3, 2) for beat 1.5).
beat_at_float
BeatGrid.beat_at_float(quarters)Get the beat position as a float (for backward compatibility).
Args: quarters: Position in quarter notes.
Returns: The beat position (1-indexed, may be fractional).
beat_at_seconds
BeatGrid.beat_at_seconds(seconds)Get the beat number within the measure at a given time in seconds.
Args: seconds: Time position in seconds.
Returns: Beat number (1-indexed).
Raises: RuntimeError: If no tempo information is available. ValueError: If seconds is before the first beat.
beat_quarters
BeatGrid.beat_quarters()All beat positions in quarters. Vectorized O(1).
Returns: numpy array of beat positions in quarter notes.
Examples: >>> grid = BeatGrid(length=16, beats_per_measure=4) >>> grid.beat_quarters() array([ 0., 1., 2., 3., 4., 5., …])
beat_seconds
BeatGrid.beat_seconds()All beat times in seconds. Vectorized O(1).
Requires the grid to have been created with from_tempo() and start_seconds, or to have a tempo_map attached.
Returns: numpy array of beat times in seconds.
Raises: RuntimeError: If no tempo information is available.
Examples: >>> grid = BeatGrid.from_tempo(tempo_bpm=120, length_seconds=60, start_seconds=0.5) >>> grid.beat_seconds()[:4] array([0.5 , 1.0 , 1.5 , 2.0 ])
downbeat_seconds
BeatGrid.downbeat_seconds()Alias for measure_seconds(). All downbeat times in seconds.
export_to_csv
BeatGrid.export_to_csv(filepath, *, format='default', labels='beats', **kwargs)Export BeatGrid data to a CSV file.
Extends the base Timeline.export_to_csv() with special formats for audio annotation tools.
Args: filepath: Output CSV file path. format: Output format. Options: - “default”: Standard timestamp table (inherited behavior). - “sonic_visualiser”: Sonic Visualiser / Audacity label track. Two columns (TIME, LABEL) with header row. - “tilia”: Tilia beat track format. Four columns (time, measure, beat, is_first_in_measure). labels: What to export when using “sonic_visualiser” format: - “beats”: All beat positions with labels like “M1B1”, “M1B2”. - “measures”: Measure start positions with labels like “M1”, “M2”. - “both”: Both beats and measures. **kwargs: Additional arguments passed to base export_to_csv() when using “default” format.
Returns: Number of rows written.
Raises: RuntimeError: If format requires tempo but none is available. ValueError: If format is not recognized.
Examples: >>> grid = BeatGrid.from_tempo(tempo_bpm=120, length_seconds=60)
>>> # Export for Sonic Visualiser
>>> grid.export_to_csv("beats.csv", format="sonic_visualiser")
120
>>> # Export for Tilia
>>> grid.export_to_csv("beats.csv", format="tilia")
120
>>> # Standard timestamp table
>>> grid.export_to_csv("data.csv", format="default")
120
from_tempo
BeatGrid.from_tempo(
tempo_bpm,
beats_per_measure=4,
beat_unit=Fraction(1, 4),
length_seconds=None,
length_quarters=None,
start_seconds=0.0,
start_measure=1,
start_mn=None,
anacrusis_quarters=None,
uid=None,
name=None,
)Create a BeatGrid from tempo information.
You must provide either length_seconds or length_quarters.
Args: tempo_bpm: Tempo in beats per minute. beats_per_measure: Number of beats per measure. Default 4. beat_unit: Note value of one beat. Default 1/4 (quarter note). length_seconds: Duration in seconds (converted using tempo). If start_seconds > 0, this should be the TOTAL audio duration; the grid will span from start_seconds to length_seconds. length_quarters: Duration in quarter notes. start_seconds: Offset in seconds where the first beat occurs. Default 0.0. Used by beat_seconds() and measure_seconds(). start_measure: MC of the first measure. Default 1. start_mn: MN label of the first measure. Default: same as start_measure. anacrusis_quarters: If set, first measure is shorter (pickup). uid: Explicit unique identifier. name: Human-readable name.
Returns: A new BeatGrid instance with vectorized accessors for beat/measure times.
Raises: ValueError: If neither length_seconds nor length_quarters is provided. ValueError: If both length_seconds and length_quarters are provided.
Examples: >>> # Audio track: 279 seconds, first beat at 0.092s, 160 BPM, 4/4 >>> grid = BeatGrid.from_tempo( … tempo_bpm=160.0, … beats_per_measure=4, … length_seconds=279.0, … start_seconds=0.092, … ) >>> grid.n_measures 186 >>> grid.beat_seconds()[:4] array([0.092, 0.467, 0.842, 1.217]) >>> grid.measure_seconds()[:4] array([0.092, 1.592, 3.092, 4.592])
materialize_beats
BeatGrid.materialize_beats(include_downbeats_only=False)Add Beat events to this timeline at each beat position.
Args: include_downbeats_only: If True, only create events for beat 1 (downbeats).
Returns: Number of beat events created.
materialize_measures
BeatGrid.materialize_measures()Add Measure events to this timeline at each measure boundary.
Creates IntervalEvents for each complete measure.
Returns: Number of measure events created.
measure_at
BeatGrid.measure_at(quarters)Get the measure count (MC) at a given quarter-note position.
Args: quarters: Position in quarter notes.
Returns: The measure count (integer, 1-indexed by default).
measure_at_seconds
BeatGrid.measure_at_seconds(seconds)Get the measure number at a given time in seconds.
Args: seconds: Time position in seconds.
Returns: Measure count (MC, 1-indexed by default).
Raises: RuntimeError: If no tempo information is available. ValueError: If seconds is before the first beat.
measure_quarters
BeatGrid.measure_quarters()All measure start positions in quarters. Vectorized O(1).
Returns: numpy array of measure start positions in quarter notes.
Examples: >>> grid = BeatGrid(length=16, beats_per_measure=4) >>> grid.measure_quarters() array([ 0., 4., 8., 12.])
measure_seconds
BeatGrid.measure_seconds()All measure start times in seconds. Vectorized O(1).
Requires the grid to have been created with from_tempo() and start_seconds, or to have a tempo_map attached.
Returns: numpy array of measure start times in seconds.
Raises: RuntimeError: If no tempo information is available.
Examples: >>> grid = BeatGrid.from_tempo(tempo_bpm=120, beats_per_measure=4, … length_seconds=60, start_seconds=0.5) >>> grid.measure_seconds()[:4] array([0.5 , 2.5 , 4.5 , 6.5 ])
metrical_position
BeatGrid.metrical_position(quarters)Get the full metrical position (mc and beat) at a given quarter position.
Args: quarters: Position in quarter notes.
Returns: Dictionary with ‘mc’ (int), ‘beat’ (Fraction), and ‘mn’ (str) keys.
mn_at
BeatGrid.mn_at(quarters)Get the measure number label (MN) at a given quarter-note position.
Args: quarters: Position in quarter notes.
Returns: The measure number label (string like “1”, “0”, “1a”).
quarter_at
BeatGrid.quarter_at(measure, beat=Fraction(1, 1))Get the quarter-note position for a given measure and beat.
Args: measure: Measure count (MC, uses start_measure as reference). beat: Beat within the measure (1-indexed). Default Fraction(1, 1).
Returns: Position in quarter notes.
Raises: ValueError: If measure < start_measure or beat < 1.