Margrete RPC
Basic

Notes

Create, inspect, and modify notes.

Note classes live in margrete_rpc.chart.notes. In normal edit transactions, tx.chart.notes contains these Note objects when Margrete RPC recognizes the note tree, and RawNote objects for structures that need low-level handling.

Most Note work follows the same pattern:

from margrete_rpc import Margrete
from margrete_rpc.chart.notes import Tap

m = Margrete()

with m.open_edit() as tx:
    tx.chart.notes.append(Tap(t=(0, 0), x=4, w=4))

The edit is sent to Margrete when the with block exits normally. If the block raises an exception, no changes are applied.

Mental model

A note is defined by its timing and lane geometry:

FieldMeaning
tTiming. Pass a raw tick int, or a Position tuple such as (bar, beat, offset).
xLeft lane index. Margrete's standard ground field is 16 lanes wide, from 0 to 15.
wWidth in lanes. A note with x=4, w=4 covers lanes 4, 5, 6, and 7.
hAir height. Only air-long notes and air-crush notes use this.

Positioning

Every Note accepts t as either a raw tick or a Position tuple:

FormMeaning
(bar,)Start of a bar
(bar, beat)Beat inside a bar
(bar, beat, offset)Beat plus tick offset

Position values are zero-based. (2, 0, 0) means bar 2, beat 0, offset 0 in the Python API, which is the third bar if you are counting bars from 1 by hand.

Inside with m.open_edit() blocks, Position tuples resolve with the chart's actual time signatures. Outside an edit transaction, they use the default 4/4 context unless you call the time helpers with explicit beat events. See the Time & Position guide for the full timing model and the TimeCalculator reference for reusable conversions.

from margrete_rpc.chart.notes import Tap

with m.open_edit(snapshot=False) as tx:
    tx.chart.notes.append(Tap(t=(2, 0, 0), x=0, w=4))
    tx.chart.notes.append(Tap(t=(2, 1), x=4, w=4))

    pos = tx.chart.notes[0].p  # Position(bar=2, beat=0, offset=0)
->
Positioning notes example

Ground notes

Ground notes use t, x, and w.

from margrete_rpc.chart.notes import Damage, Extap, Flick, Tap

tap = Tap(t=(0, 0), x=4, w=4)
flick = Flick(t=(0, 1), x=4, w=4, dir="left")
extap = Extap(t=(0, 2), x=4, w=4, dir="up")
damage = Damage(t=(0, 3), x=4, w=4)

Tap

Use Tap for a basic ground tap.

from margrete_rpc.chart.notes import Tap

note = Tap(t=(0, 0), x=4, w=4)

Flick

Use Flick for left/right flicks. dir accepts FlickDirection or a string.

from margrete_rpc.chart.notes import Flick, FlickDirection

left = Flick(t=(0, 0), x=4, w=4, dir=FlickDirection.LEFT)
right = Flick(t=(0, 1), x=4, w=4, dir="right")
auto = Flick(t=(0, 2), x=4, w=4)  # defaults to "auto"

FlickDirection values are "auto", "left", and "right".

Extap

Use Extap for directional ex-taps. dir accepts ExtapDirection or a string.

from margrete_rpc.chart.notes import Extap, ExtapDirection

up = Extap(t=(0, 0), x=4, w=4, dir=ExtapDirection.UP)
spin = Extap(t=(0, 1), x=4, w=4, dir="rotate_left")

ExtapDirection values are "up", "down", "center", "left", "right", "rotate_left", "rotate_right", "in_out", and "out_in".

Damage

Use Damage for penalty notes.

from margrete_rpc.chart.notes import Damage

note = Damage(t=(0, 0), x=4, w=4)

Long notes

Long notes have a begin position and one or more joints. A joint is a point later in time with its own geometry.

Use with_step() and with_ctrl() when you want builder-style code that returns a new copy each time:

from margrete_rpc.chart.notes import Slide

slide = (
    Slide(t=(0, 0), x=0, w=4)
    .with_ctrl(t=(0, 2), x=8, w=4)
    .with_step(t=(1, 0), x=4, w=4)
)

Use add_step() and add_ctrl() when mutating an existing note in place:

slide = Slide(t=(0, 0), x=0, w=4)
slide.add_ctrl(t=(0, 2), x=8, w=4)
slide.add_step(t=(1, 0), x=4, w=4)

Joints must be added in increasing time order. Long notes must have at least one joint before being sent to Margrete.

For generated slide paths instead of hand-written joints, see Curved Slides.

Hold

A Hold starts at the begin point and ends at a single step joint.

from margrete_rpc.chart.notes import Hold

hold = Hold(t=(0, 0), x=4, w=4).with_step(t=(1, 0), x=4, w=4)

Calling with_step() again on a hold replaces its end joint in the returned copy.

Slide

A Slide can move through step and control joints.

from margrete_rpc.chart.notes import Slide

slide = (
    Slide(t=(0, 0), x=0, w=4)
    .with_ctrl(t=(0, 2), x=8, w=4)
    .with_step(t=(1, 0), x=4, w=4)
)

The final joint will automatically become a step joint before being sent to Margrete.

AirCrush

AirCrush is an air-lane long note. It uses the same begin/joint structure as a slide, plus:

FieldMeaning
hAir height at the begin or joint.
gapSegment spacing, as raw ticks or a Division tuple.
colorCrush color, as ColorValue or a string.
from margrete_rpc.chart.notes import AirCrush

crush = (
    AirCrush(t=(0, 0), x=4, w=4, h=80, gap=(1, 8), color="blue")
    .with_ctrl(t=(0, 2), x=6, w=4, h=100)
    .with_ctrl(t=(1, 0), x=8, w=4, h=80)
)

gap=(1, 8) means one eighth note. You can read the tick value with crush.gap and the beat-fraction view with crush.interval.

AirCrush interval

ColorValue string values are "default", "red", "orange", "yellow", "green", "sky", "blue", "violet", "pink", "white", "black", "grass", "sky_blue", "cobalt_blue", "purple", and "none".

Air notes

Air notes are attached to a ground note or long note through the parent note's .air property.

You can use add_air() or assign .air directly:

from margrete_rpc.chart.notes import Air, AirDirection, Tap

tap = Tap(t=(0, 0), x=4, w=4)
air = Air(AirDirection.UP, t=(0, 0), x=4, w=4)

tap.air = air  # or
tap.add_air(air)

Use with_air() when you want to keep the original note unchanged:

tap_with_air = tap.with_air(Air("up", t=(0, 0), x=4, w=4))  # tap unchanged

The attached air note's geometry must match its parent:

  • For a ground note, it must match the ground note's t, x, and w.
  • For a long note, it must match the long note's final joint t, x, and w.

AirDirection values are "up", "down", "up_left", "up_right", "down_left", and "down_right".

Air on ground notes

Attach Air above a tap, flick, extap, or damage note.

from margrete_rpc.chart.notes import Air, Flick

flick = Flick(t=(0, 0), x=4, w=4, dir="right").with_air(
    Air("up_right", t=(0, 0), x=4, w=4)
)

Air on long notes

Attach Air, AirSlide, or AirHold to a long note's end joint.

from margrete_rpc.chart.notes import AirHold, Hold

hold = Hold(t=(0, 0), x=4, w=4).with_step(t=(1, 0), x=6, w=4)

hold_with_air = hold.with_air(
    AirHold(t=(1, 0), x=6, w=4, h=80)
    .with_step(t=(1, 2), x=6, w=4, h=120)
)

AirSlide works the same way, but represents a sliding air path:

from margrete_rpc.chart.notes import AirSlide, Slide

slide = Slide(t=(0, 0), x=0, w=4).with_step(t=(1, 0), x=8, w=4)

slide_with_air = slide.with_air(
    AirSlide(t=(1, 0), x=8, w=4, h=80)
    .with_ctrl(t=(1, 2), x=10, w=4, h=120)
    .with_step(t=(2, 0), x=8, w=4, h=80)
)

RawNote

RawNote is the low-level Margrete representation. You usually do not need it for normal scripting.

from margrete_rpc.chart.notes import R, RawNote, Tap

# Build a raw tap manually.
raw = R.tap(t=0, x=4, w=4)

# Convert `Note` to `RawNote` before low-level inspection.
typed = Tap(t=(0, 0), x=4, w=4)
raw = typed.to_raw()

Validation

Call note.validate() when you want to catch invalid notes early. to_raw() also validates by default.

note.validate()                       # raises ValueError if invalid
raw = note.to_raw()                   # validates, then returns a RawNote tree
raw = note.to_raw(skip_validation=True)  # skip validation when you already know it is valid

Common validation failures include:

  • zero or negative width,
  • long-note joints that are not in increasing time order,
  • missing joints on long notes,
  • attached air notes whose t/x/w do not match the parent attachment point.

On this page