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:
| Field | Meaning |
|---|---|
t | Timing. Pass a raw tick int, or a Position tuple such as (bar, beat, offset). |
x | Left lane index. Margrete's standard ground field is 16 lanes wide, from 0 to 15. |
w | Width in lanes. A note with x=4, w=4 covers lanes 4, 5, 6, and 7. |
h | Air 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:
| Form | Meaning |
|---|---|
(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)
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:
| Field | Meaning |
|---|---|
h | Air height at the begin or joint. |
gap | Segment spacing, as raw ticks or a Division tuple. |
color | Crush 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.
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 unchangedThe attached air note's geometry must match its parent:
- For a ground note, it must match the ground note's
t,x, andw. - For a long note, it must match the long note's final joint
t,x, andw.
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 validCommon 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/wdo not match the parent attachment point.