Time & Position
Convert between raw ticks, musical positions, and note divisions.
Margrete stores time as integer ticks. The Python API lets you work at that level when you need exact control, or use musical tuple forms for charting code that is easier to read. Notes and events both use this model; see Notes and Events for chart-editing examples.
Ticks
The lowest-level unit of time is the tick. Margrete RPC uses a resolution of TICK_RESOLUTION ticks per whole note, 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 0, beat 1 in 4/4
print(tap.t) # 480Pass raw ticks anywhere a time argument such as t= is accepted.
Positions
Raw tick math often gets hard to read. A Position lets you write time as a musical location instead:
| 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. Missing fields default to zero, and offset is measured in ticks inside the beat.
For 4/4:
bar 0, beat 0 ──── bar 0, beat 1 ──── bar 0, beat 2 ──── bar 0, beat 3
tick 0 tick 480 tick 960 tick 1440Any API that takes a time argument can take a position tuple:
from margrete_rpc.chart.notes import Tap
Tap(t=(2,), x=0, w=4) # bar 2, beat 0
Tap(t=(2, 1), x=0, w=4) # bar 2, beat 1
Tap(t=(2, 1, 240), x=0, w=4) # bar 2, beat 1, halfway through the beatInside with m.open_edit() blocks, positions follow the chart's 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.events.beat.append(BeatEvent(0, 2, 4)) # 2/4 at bar 0
tx.chart.events.beat.append(BeatEvent(1, 1, 4)) # 1/4 at bar 1
tx.chart.notes.append(Tap(t=(2, 0), x=0, w=4))Here (2, 0) still means the start of bar 2, even though the first two bars have different lengths.
Manual conversion
Use tick_to_pos and pos_to_tick when you need to convert values yourself:
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, conversions follow the current chart inside an edit transaction. Outside a transaction, they fall back to default 4/4.
For scripts running outside a transaction, pass beat events explicitly when the chart is not plain 4/4:
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
Positions describe where something starts. Durations describe how long something 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)APIs that take durations also accept a division tuple:
# Snap a note's start to the nearest eighth note
note.align((1, 8))
# AirCrush with a dotted-quarter gap between segments
AirCrush(t=0, x=0, w=4, h=4, gap=(3, 4), ...)div_to_tick raises ValueError if the fraction doesn't land exactly on a whole tick. This can
happen with denominators that do not divide 1920, such as 1/7, or denominators larger than the
tick resolution, such as 1/2048.
Working with time signatures
Time signatures live in tx.chart.events.beat as BeatEvent objects. Each event starts at a bar and applies until the next event:
from margrete_rpc.chart.events import BeatEvent
with m.open_edit(snapshot=False) as tx:
beats = tx.chart.events.beat
beats.append(BeatEvent(0, 4, 4))
beats.append(BeatEvent(16, 3, 4))
for event in beats:
print(f"Bar {event.bar}: {event.beats_per_bar}/{event.beat_unit}")
# Bar 0: 4/4
# Bar 16: 3/4For repeated manual conversions, build a TimeCalculator once instead of passing beat_events to every helper call:
from margrete_rpc.chart.time import TimeCalculator
calc = TimeCalculator(tx.chart.events.beat)
pos = calc.tick_to_pos(3840)
tick = calc.pos_to_tick(1, 2, 0)You usually only need TimeCalculator in utility code that runs outside an edit transaction. Inside a transaction, tuple positions already resolve with the chart's beat events.