Margrete RPC
Basic

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:

DurationTicks
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
Note tick list
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)  # 480

Pass 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:

FormMeaning
(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 1440

Any 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 beat

Inside 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.

Time-signature changed

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 = 5280

When 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/4

For 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.

On this page