Time & Musical Position
The Python API uses the models below to express time. All time-related APIs use these models.
Ticks
The lowest-level unit of time is the tick. In Margrete, a whole note has a resolution of TICK_RESOLUTION (1920) ticks, so common note values map to these tick counts:
| Duration | Ticks |
|---|---|
| Whole note (1/1) | 1920 |
| Half note (1/2) | 960 |
| Quarter note (1/4) | 480 |
| Eighth note (1/8) | 240 |
| Sixteenth note (1/16) | 120 |

from margrete_rpc.chart.notes import Tap
from margrete_rpc.chart.time import TICK_RESOLUTION
tap = Tap(t=TICK_RESOLUTION // 4, x=0, w=4) # bar 1, beat 2 in 4/4
print(tap.t) # 480You can pass ticks directly anywhere a time argument such as t= is accepted.
Positions
Raw tick math often gets hard to read. A musical position (Position) lets you write time in bars and beats:
| Form | Meaning |
|---|---|
(bar,) | Start of a bar |
(bar, beat) | Beat inside a bar |
(bar, beat, offset) | Beat plus tick offset |
All fields are zero-based. For example, bar 1, beat 1 is (0, 0, 0).
For 4/4:
bar 1, beat 1 ── bar 1, beat 2 ── bar 1, beat 3 ── bar 1, beat 4
tick 0 tick 480 tick 960 tick 1440Any API that takes a time argument can take a musical position (Position) tuple:
from margrete_rpc.chart.notes import Tap
Tap(t=(2,), x=0, w=4) # bar 3, beat 1
Tap(t=(2, 1), x=0, w=4) # bar 3, beat 2
Tap(t=(2, 1, 240), x=0, w=4) # bar 3, beat 2, plus 240 ticksInside an open_edit() block, musical position (Position) tuples are resolved using the chart's actual time signatures:
from margrete_rpc.chart.events import BeatEvent
from margrete_rpc.chart.notes import Tap
with m.open_edit(snapshot=False) as tx:
tx.chart.beats.append(BeatEvent(0, 2, 4)) # 2/4 starting at bar 1
tx.chart.beats.append(BeatEvent(1, 1, 4)) # 1/4 starting at bar 2
tx.chart.notes.append(Tap(t=(2, 0), x=0, w=4))Here (2, 0) still means the start of bar 3, but the resulting tick is affected by the preceding time signatures.
Manual conversion
Use tick_to_pos and pos_to_tick when you need to convert values manually:
from margrete_rpc.chart.events import BeatEvent
from margrete_rpc.chart.time import pos_to_tick, tick_to_pos
beat_events = [BeatEvent(0, 4, 4)]
pos = tick_to_pos(1920, beat_events=beat_events)
print(pos) # Position(bar=1, beat=0, offset=0)
tick = pos_to_tick(2, 3, beat_events=beat_events)
print(tick) # 2 * 1920 + 3 * 480 = 5280When beat_events is omitted, these conversion functions use the current chart's time signatures inside open_edit(). Outside open_edit(), they use the default 4/4 time signature.
If your code runs outside open_edit() and the chart is not plain 4/4, pass time signature events explicitly:
from margrete_rpc.chart.events import BeatEvent
from margrete_rpc.chart.time import pos_to_tick
beats = [BeatEvent(0, 3, 4)] # 3/4
tick = pos_to_tick(10, 0, 0, beat_events=beats)Division
Musical positions describe where something starts. A division (Division) describes how long it lasts.
Use div_to_tick(numerator, denominator) to convert a note value to ticks:
from margrete_rpc.chart.time import div_to_tick, tick_to_div
quarter = div_to_tick(1, 4) # 480 ticks
eighth = div_to_tick(1, 8) # 240 ticks
dotted = div_to_tick(3, 4) # 1440 ticks (three quarter notes)
division = tick_to_div(480) # Division(numerator=1, denominator=4)Some APIs also accept division (Division) tuples, such as align() or an AirCrush gap:
# Snap a note's start to the nearest eighth note
note.align((1, 8))
# AirCrush with a sixteenth-note Step gap
AirCrush(t=0, x=0, w=4, h=4, gap=(1, 16), ...)Positive durations smaller than 1 tick (1/1920), such as 1/2048, are converted to 1 tick.
Using TimeCalculator
For repeated manual time conversions, create a TimeCalculator once instead of passing beat_events to every helper call:
from margrete_rpc.chart.time import TimeCalculator
calc = TimeCalculator(tx.chart.beats)
pos = calc.tick_to_pos(3840)
tick = calc.pos_to_tick(1, 2, 0)You usually only need TimeCalculator in code that runs outside open_edit(). Inside open_edit(), ticks are calculated automatically using the chart's time signatures.