Examples

Imports

The top-level openmusickit namespace holds what any piece of music is built from, whatever its musical system: the graph, the objects placed in it, and the values that belong to no one system. A musical system is opted into separately. Western Standard Music Notation (WSMN) has its types in wsmn and its ready-made vocabulary (pitches, intervals, chord types, durations, time signatures, marks, clefs) in wsmn.symbols.

import openmusickit as omk
from openmusickit.systems import wsmn
from openmusickit.systems.wsmn.symbols import *

Tones

In WSMN, a tone is a TonalVector: a tuple of (diatonic letter index, chromatic pitch class, octave). Middle C is octave 0 internally, but strings and display default to the usual middle C = C4 convention.

c4 = wsmn.TonalVector.from_string("C4")
fs5 = wsmn.TonalVector.from_string("F#5")
bb3 = wsmn.TonalVector.from_string("Bb3")

print(repr(c4))
print(repr(fs5))
print(f"{c4}  {fs5}  {bb3}")
print(f"{fs5:ascii}")
TonalVector((0, 0, 0))
TonalVector((3, 6, 1))
C4  F♯5  B♭3
F#5

Pitch-class symbols with no octave are ready-made in wsmn.symbols. An x is a sharp and a b is a flat.

print(f"{C}  {Cx}  {Eb}  {G}")
print(f"{C.qualify_octave(0)}")
C  C♯  E♭  G
C4

Intervals and transposition

A TonalVector is also an interval, and the same class represents both. Transpose a tone by adding an interval to it, or by calling transpose with a direction.

print(f"{c4 + M3}")
print(f"{c4 + P5}")
print(f"{c4.transpose(M3, wsmn.TonalDirection.DOWN)}")
print(f"{c4 - m7}")
E4
G4
A♭3
D3

The interval between two tones is their distance.

e4 = c4 + M3
gap = c4.distance(e4)
print(gap.interval.unicode, "=", abs(gap), "half steps")
major 3 = 4 half steps

Durations

A MetricalDuration is a fraction of a whole note, with optional dots. wsmn.symbols provides the common ones by name.

print(repr(wsmn.MetricalDuration(1, 4)))
print(repr(wsmn.MetricalDuration(3, 4)))
print(dotted_half.rational_length)
print(repr(quarter + eighth))
MetricalDuration(1, 4)
MetricalDuration(1, 2, dots=1)
3/4
MetricalDuration(1, 4, dots=1)

Notes and rests

A NoteEvent holds a set of tones (one for a single note, several for a chord in one voice) and a duration. A Rest is a NoteEvent holding a silent tone.

note = omk.NoteEvent(tones={c4}, duration=quarter)
rest = omk.Rest(quarter)

print(note)
print(rest)
print(rest.is_rest)
NoteEvent(tones=[TonalVector((0, 0, 0))], duration=MetricalDuration(1, 4))
Rest(duration=MetricalDuration(1, 4))
True

Transposing a note transforms its tones in place.

note.transform_tones(wsmn.TonalVector.transpose, P5)
print(f"{next(iter(note.tones))}")
G4

Chords and keys

A chord is a chord type realized at a root. Calling a root with a chord type, or a chord type with a root, does the same thing.

print(C(maj), G(dom7), D(min_))
print(C(maj) / E)
print([f"{t}" for t in C(maj)])
C G7 Dmin
C/E
['C', 'E', 'G']

A key is a tonic and a mode.

d_major = wsmn.Key.of(D, Major)
print(d_major.name)
print(d_major.signature)
print([f"{t}" for t in d_major.tones])
D Major
KeySignature(c=1, f=1)
['D', 'E', 'F♯', 'G', 'A', 'B', 'C♯']

Lyrics

parse_lyrics splits text into syllables. Hyphens split a word into syllables and whitespace separates words.

for syllable in omk.parse_lyrics("Twin-kle, twin-kle, lit-tle star"):
    print(f"{str(syllable):10}  {syllable.placement.value:8}  {syllable.word}")
Twin -      beginning  Twinkle,
- kle,      end       Twinkle,
twin -      beginning  twinkle,
- kle,      end       twinkle,
lit -       beginning  little
- tle       end       little
star        whole     star

A small score graph

Musical objects become nodes in an OmkGraph. A line is a chain of events joined by NEXT edges. Independent lines are related in time by pinning one event to another with a SIMULTANEOUS edge. Lyrics are a line of syllables, each attached to the note where it begins with a LYRIC edge. A Score carries the work’s metadata and names the component: it points at the heads of its lines. A clef, like a key or a meter, is a zero-duration ClefEvent at the point in the line where it takes effect.

Here is the first phrase of Twinkle, Twinkle, Little Star: a melody under a treble clef, its lyrics, and a chord line above it.

graph = omk.OmkGraph(omk.GraphMeta())
score = omk.Score(title="Twinkle, Twinkle, Little Star", lyricist="Jane Taylor")

# The melody: a clef, then one line of NoteEvents.
pitches = "C4 C4 G4 G4 A4 A4 G4  F4 F4 E4 E4 D4 D4 C4".split()
rhythm = [quarter] * 6 + [half] + [quarter] * 6 + [half]
melody = [
    omk.NoteEvent(tones={wsmn.TonalVector.from_string(p)}, duration=d)
    for p, d in zip(pitches, rhythm, strict=True)
]
clef = omk.ClefEvent(clef=treble_clef)
graph.add_line([clef, *melody])
graph.add_line_to_score(score, clef)  # the clef is the head of the line

# The harmony: its own line, pinned to the first note of the melody.
chords = [
    omk.ChordEvent(chord=C(maj), duration=whole),
    omk.ChordEvent(chord=F(maj), duration=half),
    omk.ChordEvent(chord=C(maj), duration=half),
    omk.ChordEvent(chord=F(maj), duration=half),
    omk.ChordEvent(chord=C(maj), duration=half),
    omk.ChordEvent(chord=G(dom7), duration=half),
    omk.ChordEvent(chord=C(maj), duration=half),
]
graph.add_line(chords)
graph.add_simultaneous(melody[0], chords[0])
graph.add_line_to_score(score, chords[0])

# The lyrics: a line of syllables, zipped onto the notes one syllable per onset.
verse = omk.LyricSection(section_type="verse", section_number=1)
syllables = graph.add_lyrics(
    "Twin-kle, twin-kle, lit-tle star, how I won-der what you are",
    section=verse,
)
graph.zip_lyrics_to_objects(syllables[0], melody[0])

Walking a line yields its events in order. Every position is relative to some reference event; here the reference is the first note, and onsets are shown as fractions of a whole note.

def lyric_on(event):
    return " ".join(
        str(s) for s in syllables
        if list(graph.edges_between(event, s, omk.EdgeType.LYRIC))
    )

print(f"{'onset':<8}{'tone':<6}{'duration':<28}lyric")
for event in graph.walk_line(melody[0]):
    onset = graph.relative_onset(melody[0], event).rational_length
    tone = next(iter(event.tones))
    print(f"{str(onset):<8}{format(tone, 'ascii'):<6}{event.duration!r:<28}{lyric_on(event)}")
onset   tone  duration                    lyric
0       C4    MetricalDuration(1, 4)      Twin -
1/4     C4    MetricalDuration(1, 4)      - kle,
1/2     G4    MetricalDuration(1, 4)      twin -
3/4     G4    MetricalDuration(1, 4)      - kle,
1       A4    MetricalDuration(1, 4)      lit -
5/4     A4    MetricalDuration(1, 4)      - tle
3/2     G4    MetricalDuration(1, 2)      star,
2       F4    MetricalDuration(1, 4)      how
9/4     F4    MetricalDuration(1, 4)      I
5/2     E4    MetricalDuration(1, 4)      won -
11/4    E4    MetricalDuration(1, 4)      - der
3       D4    MetricalDuration(1, 4)      what
13/4    D4    MetricalDuration(1, 4)      you
7/2     C4    MetricalDuration(1, 2)      are

The chord line is walked the same way, and its onsets are still measured from the first melody note because the two lines are pinned together.

for event in graph.walk_line(chords[0]):
    onset = graph.relative_onset(melody[0], event).rational_length
    print(f"{str(onset):<8}{event.chord}")
0       C
1       F
3/2     C
2       F
5/2     C
3       G7
7/2     C

The score names the whole: its title, every event of every line it holds, and the clef, which takes no time.

print(score.title)
print(len(list(graph.walk_score(score))), "events in the score")
print(graph.relative_onset(clef, melody[0]))
Twinkle, Twinkle, Little Star
22 events in the score
ZeroDuration()

Transposing a span of the graph transforms every tone in it.

graph.transform_tones(melody[0], melody[-1], wsmn.TonalVector.transpose, P5)
print(" ".join(format(next(iter(e.tones)), "ascii") for e in graph.walk_line(melody[0])))
G4 G4 D5 D5 E5 E5 D5 C5 C5 B4 B4 A4 A4 G4

Drawing the graph

A graph can be drawn. GraphRenderer lays it out with Graphviz: NEXT advances along the page, everything that a timing walk places at one instant shares a column, chords sit above the line they are pinned to, lyrics below the line they are sung to, and a score is a title block. to_dot() needs nothing installed; to_svg() and to_png() run Graphviz’s dot, which must be on the PATH. This page hands the DOT to Quarto, which renders it with its own copy of Graphviz. The melody is drawn as it now stands, a fifth up.

from openmusickit.visualization import GraphRenderer

# Quarto sizes a dot figure by these options (inches), else squeezes it into 7 by 5;
# the values are the drawing's natural size, and the div lets a wide one scroll.
print(":::: {.omk-drawing}")
print("```{dot}\n//| fig-width: 12\n//| fig-height: 2.6\n" + GraphRenderer(graph).to_dot() + "\n```")
print("::::")

Twinkle, Twinkle, Little Star lyrics: Jane Taylor 1feb4b3c-c614-43db-a28b-7fc21a9e9534 C 𝅝 33e13758-3e79-4061-8589-425e9bb0b379 F 𝅗𝅥 1feb4b3c-c614-43db-a28b-7fc21a9e9534->33e13758-3e79-4061-8589-425e9bb0b379 8b4c1669-0a44-415d-8a1b-3cddfeeaa587 G 4 ♩ d02c9b0d-0a51-4f2b-a072-c0fa94c2d30c C 𝅗𝅥 33e13758-3e79-4061-8589-425e9bb0b379->d02c9b0d-0a51-4f2b-a072-c0fa94c2d30c ec34119c-dab9-4ea7-8938-08f66f15c138 E 5 ♩ 257406c4-8cf0-4cb0-8a41-446d1146aa60 F 𝅗𝅥 d02c9b0d-0a51-4f2b-a072-c0fa94c2d30c->257406c4-8cf0-4cb0-8a41-446d1146aa60 0ec50370-5e97-4951-a45d-8ef54397c9a1 D 5 𝅗𝅥 a6a95bb9-99f3-4347-b53f-bad506b462d0 C 𝅗𝅥 257406c4-8cf0-4cb0-8a41-446d1146aa60->a6a95bb9-99f3-4347-b53f-bad506b462d0 2fb000cd-bc93-4e2a-8d0d-9ab74948590f C 5 ♩ 58137fb7-8bd8-4e14-a153-3c39004aebe1 G7 𝅗𝅥 a6a95bb9-99f3-4347-b53f-bad506b462d0->58137fb7-8bd8-4e14-a153-3c39004aebe1 2d39e8fa-5a3a-499b-bb4f-19e736325cae B 4 ♩ cf2bd96d-fc07-425d-b982-7d59e92a4a4b C 𝅗𝅥 58137fb7-8bd8-4e14-a153-3c39004aebe1->cf2bd96d-fc07-425d-b982-7d59e92a4a4b 3d2536c5-05ac-4c6b-a6f7-b4c6f903bdc1 A 4 ♩ 178767fb-ba7c-4d1e-9c12-6e940a33925d G 4 𝅗𝅥 1d513456-d991-4336-8148-1fd9b4410ff5 𝄞 1d513456-d991-4336-8148-1fd9b4410ff5->8b4c1669-0a44-415d-8a1b-3cddfeeaa587 8b4c1669-0a44-415d-8a1b-3cddfeeaa587->1feb4b3c-c614-43db-a28b-7fc21a9e9534 eed68530-2f71-4af2-a77a-19d7142c38b9 G 4 ♩ 8b4c1669-0a44-415d-8a1b-3cddfeeaa587->eed68530-2f71-4af2-a77a-19d7142c38b9 31b4be13-1988-4832-876c-f64ffc603c40 Twin - 8b4c1669-0a44-415d-8a1b-3cddfeeaa587->31b4be13-1988-4832-876c-f64ffc603c40 741ae91a-bb43-4667-a17e-311980b8de79 D 5 ♩ eed68530-2f71-4af2-a77a-19d7142c38b9->741ae91a-bb43-4667-a17e-311980b8de79 16f29309-948a-4339-85c7-008515633b32 - kle, eed68530-2f71-4af2-a77a-19d7142c38b9->16f29309-948a-4339-85c7-008515633b32 e1647589-bcc4-4d83-9b08-e6eef37c0707 D 5 ♩ 741ae91a-bb43-4667-a17e-311980b8de79->e1647589-bcc4-4d83-9b08-e6eef37c0707 0c62db75-7965-46e2-99b4-9013a400c783 twin - 741ae91a-bb43-4667-a17e-311980b8de79->0c62db75-7965-46e2-99b4-9013a400c783 e1647589-bcc4-4d83-9b08-e6eef37c0707->ec34119c-dab9-4ea7-8938-08f66f15c138 68012650-6a8f-41c3-b166-3947bc10ffbd - kle, e1647589-bcc4-4d83-9b08-e6eef37c0707->68012650-6a8f-41c3-b166-3947bc10ffbd cfe9955b-dd8b-46f6-a1cf-3de4cba5e90b E 5 ♩ ec34119c-dab9-4ea7-8938-08f66f15c138->cfe9955b-dd8b-46f6-a1cf-3de4cba5e90b 6f456058-9db0-43a5-a0bb-b82b48afa0bd lit - ec34119c-dab9-4ea7-8938-08f66f15c138->6f456058-9db0-43a5-a0bb-b82b48afa0bd cfe9955b-dd8b-46f6-a1cf-3de4cba5e90b->0ec50370-5e97-4951-a45d-8ef54397c9a1 d86fc901-5f75-49db-8149-5940f486b02b - tle cfe9955b-dd8b-46f6-a1cf-3de4cba5e90b->d86fc901-5f75-49db-8149-5940f486b02b 0ec50370-5e97-4951-a45d-8ef54397c9a1->2fb000cd-bc93-4e2a-8d0d-9ab74948590f 21afad13-8d72-45b6-9a28-d893d47aa454 star, 0ec50370-5e97-4951-a45d-8ef54397c9a1->21afad13-8d72-45b6-9a28-d893d47aa454 a51bf37b-bd8b-494f-9a6e-4dbf21ca1b26 C 5 ♩ 2fb000cd-bc93-4e2a-8d0d-9ab74948590f->a51bf37b-bd8b-494f-9a6e-4dbf21ca1b26 9b0cbf0a-aee0-4d73-9e14-20e1c79a2a23 how 2fb000cd-bc93-4e2a-8d0d-9ab74948590f->9b0cbf0a-aee0-4d73-9e14-20e1c79a2a23 a51bf37b-bd8b-494f-9a6e-4dbf21ca1b26->2d39e8fa-5a3a-499b-bb4f-19e736325cae ac42bfcf-cdf0-4c7c-8bb2-9cfe82e7ffa5 I a51bf37b-bd8b-494f-9a6e-4dbf21ca1b26->ac42bfcf-cdf0-4c7c-8bb2-9cfe82e7ffa5 f4d44f72-86f2-4a14-b509-10f0be4f9cd2 B 4 ♩ 2d39e8fa-5a3a-499b-bb4f-19e736325cae->f4d44f72-86f2-4a14-b509-10f0be4f9cd2 d1f26e89-1604-48c4-9ea3-1bd7a7c066ef won - 2d39e8fa-5a3a-499b-bb4f-19e736325cae->d1f26e89-1604-48c4-9ea3-1bd7a7c066ef f4d44f72-86f2-4a14-b509-10f0be4f9cd2->3d2536c5-05ac-4c6b-a6f7-b4c6f903bdc1 a216d2a4-42e5-4b82-91b8-bfd04b984793 - der f4d44f72-86f2-4a14-b509-10f0be4f9cd2->a216d2a4-42e5-4b82-91b8-bfd04b984793 a22ffc12-e623-48db-aadd-8a3c4203b0be A 4 ♩ 3d2536c5-05ac-4c6b-a6f7-b4c6f903bdc1->a22ffc12-e623-48db-aadd-8a3c4203b0be 90a1b357-394b-494f-93a4-8a08aa78540a what 3d2536c5-05ac-4c6b-a6f7-b4c6f903bdc1->90a1b357-394b-494f-93a4-8a08aa78540a a22ffc12-e623-48db-aadd-8a3c4203b0be->178767fb-ba7c-4d1e-9c12-6e940a33925d 8af2f324-e02d-4498-afe1-85350b758be4 you a22ffc12-e623-48db-aadd-8a3c4203b0be->8af2f324-e02d-4498-afe1-85350b758be4 5bf07ec2-0633-4ee4-8a98-5dd7f45a8a79 are 178767fb-ba7c-4d1e-9c12-6e940a33925d->5bf07ec2-0633-4ee4-8a98-5dd7f45a8a79 8bb32ee1-5927-459f-b8a6-ce08e6c896c2 ⌊verse 1 8bb32ee1-5927-459f-b8a6-ce08e6c896c2-end verse 1⌋ 8bb32ee1-5927-459f-b8a6-ce08e6c896c2->31b4be13-1988-4832-876c-f64ffc603c40 8bb32ee1-5927-459f-b8a6-ce08e6c896c2-end->5bf07ec2-0633-4ee4-8a98-5dd7f45a8a79

A drum kit

Unpitched percussion has no pitch to name, so a PercussionTone is a relative pitch and a stroke, either or both left out: PercussionTone() is a plain hit on whatever the part is, and the ready-made ones have names. The instrument is the PercussionPart, which may carry a palette of the tones it is expected to use. A drum kit is one part per drum, each on its own line, held together by a LineGroup: the origin its lines are measured from, so they need no pins to be aligned. Percussion tones have their own symbols module, because some of their names (heel, toe) are also the names of marks.

from openmusickit.systems.wsmn.percussion.symbols import (
    bass_drums, closed, hi_hats, hit, open_hat, snare_drums,
)


def line(*tones, duration):
    return [
        omk.Rest(duration) if tone is None else omk.NoteEvent(tones={tone}, duration=duration)
        for tone in tones
    ]


kit = omk.OmkGraph(omk.GraphMeta())
hi_hat = line(*[closed] * 7, open_hat, duration=eighth)
snare = line(None, hit, None, hit, duration=quarter)
kick = line(hit, None, hit, None, duration=quarter)
for drum in (hi_hat, snare, kick):
    kit.add_line(drum)

drums = omk.LineGroup(name="Drum kit")
kit.add_group(drums, [hi_hat[0], snare[0], kick[0]])
kit.add_stint(omk.PercussionPart(name="Hi-hat", palette=hi_hats), omk.Stint(), hi_hat[0])
kit.add_stint(omk.PercussionPart(name="Snare", palette=snare_drums), omk.Stint(), snare[0])
kit.add_stint(omk.PercussionPart(name="Kick", palette=bass_drums), omk.Stint(), kick[0])

Each line is walked on its own, and onsets on any line can be measured from any event of another, because the group relates them.

for name, drum in (("hi-hat", hi_hat), ("snare", snare), ("kick", kick)):
    cells = []
    for event in kit.walk_line(drum[0]):
        onset = kit.relative_onset(hi_hat[0], event).rational_length
        sound = "rest" if event.is_rest else f"{next(iter(event.tones))}"
        cells.append(f"{str(onset):>4} {sound}")
    print(f"{name:<7}", "  |  ".join(cells))
hi-hat     0 closed  |   1/8 closed  |   1/4 closed  |   3/8 closed  |   1/2 closed  |   5/8 closed  |   3/4 closed  |   7/8 open
snare      0 rest  |   1/4 hit  |   1/2 rest  |   3/4 hit
kick       0 hit  |   1/4 rest  |   1/2 hit  |   3/4 rest

A tone prints as its readable form, and a remap over a line is an ordinary tone operation.

print(f"{closed}", "/", f"{open_hat}", "/", f"{hit}")
kit.transform_tones(hi_hat[0], None, lambda tone: open_hat if tone == closed else tone)
print(" ".join(f"{next(iter(e.tones))}" for e in kit.walk_line(hi_hat[0])))
print(len(list(kit.walk_group(drums))), "events in the kit")
closed / open / hit
open open open open open open open open
16 events in the kit

Drawn top to bottom, the three lines run down the page in the order the group holds them, their heads on one row because the group is their common origin, and each part sits before the line it plays.

from openmusickit.visualization import SequenceDirection, VisualizationConfig

config = VisualizationConfig(direction=SequenceDirection.TOP_TO_BOTTOM)
print(":::: {.omk-drawing}")
print("```{dot}\n//| fig-width: 2.5\n//| fig-height: 6.2\n" + GraphRenderer(kit, config).to_dot() + "\n```")
print("::::")

ec736405-d4df-4525-a1c2-a17a5377f2f4 open ♪ 63d2125c-dd63-4c62-bac1-10b6ecac898a open ♪ ec736405-d4df-4525-a1c2-a17a5377f2f4->63d2125c-dd63-4c62-bac1-10b6ecac898a 16478314-1c4f-49ef-97bd-bb806b3d36e5 𝄽 50012f8e-afd1-4fdc-80c0-d51dc506a264 open ♪ 63d2125c-dd63-4c62-bac1-10b6ecac898a->50012f8e-afd1-4fdc-80c0-d51dc506a264 8ab8865d-028f-463c-8e32-86d1fc119174 open ♪ 50012f8e-afd1-4fdc-80c0-d51dc506a264->8ab8865d-028f-463c-8e32-86d1fc119174 25c00fbd-7e59-4e1e-85e7-b69e1887ba43 hit ♩ 5f4a45a4-c41b-4c16-858b-51232ed085c9 open ♪ 8ab8865d-028f-463c-8e32-86d1fc119174->5f4a45a4-c41b-4c16-858b-51232ed085c9 9d44a906-4738-4a30-98fe-e290f769e540 open ♪ 5f4a45a4-c41b-4c16-858b-51232ed085c9->9d44a906-4738-4a30-98fe-e290f769e540 1fa871bb-2dfc-4b51-9684-f77fd1814358 𝄽 992c3ac7-eb1b-4113-9237-8f7e88d14095 open ♪ 9d44a906-4738-4a30-98fe-e290f769e540->992c3ac7-eb1b-4113-9237-8f7e88d14095 f9032efd-f8ca-499c-96c0-310789e97cda open ♪ 992c3ac7-eb1b-4113-9237-8f7e88d14095->f9032efd-f8ca-499c-96c0-310789e97cda 6559a7b9-ed79-4432-98c9-93604e584fde hit ♩ 16478314-1c4f-49ef-97bd-bb806b3d36e5->25c00fbd-7e59-4e1e-85e7-b69e1887ba43 7b9630d6-855e-4b7e-80b4-abf6e62cd489 hit ♩ 25c00fbd-7e59-4e1e-85e7-b69e1887ba43->1fa871bb-2dfc-4b51-9684-f77fd1814358 85f898d9-7ecf-45cc-bbd4-f4903572f2f9 𝄽 1fa871bb-2dfc-4b51-9684-f77fd1814358->6559a7b9-ed79-4432-98c9-93604e584fde 4b1e2405-83dd-46ae-bae2-bbaf76010e93 hit ♩ 8f5f7425-a38a-4797-b621-9a8c2f656edf 𝄽 7b9630d6-855e-4b7e-80b4-abf6e62cd489->85f898d9-7ecf-45cc-bbd4-f4903572f2f9 85f898d9-7ecf-45cc-bbd4-f4903572f2f9->4b1e2405-83dd-46ae-bae2-bbaf76010e93 4b1e2405-83dd-46ae-bae2-bbaf76010e93->8f5f7425-a38a-4797-b621-9a8c2f656edf 0effe33b-e5a1-4218-866e-059a2af0978d Drum kit 0effe33b-e5a1-4218-866e-059a2af0978d->ec736405-d4df-4525-a1c2-a17a5377f2f4 0effe33b-e5a1-4218-866e-059a2af0978d->16478314-1c4f-49ef-97bd-bb806b3d36e5 0effe33b-e5a1-4218-866e-059a2af0978d->7b9630d6-855e-4b7e-80b4-abf6e62cd489 2df369fc-a636-4629-9c78-8fdafedbcf7e Hi-hat d9e4d863-c367-40c2-9b62-6682f69615ba stint 2df369fc-a636-4629-9c78-8fdafedbcf7e->d9e4d863-c367-40c2-9b62-6682f69615ba 32f7af81-8173-43b6-b028-47a547dabd3a Snare d9e4d863-c367-40c2-9b62-6682f69615ba->ec736405-d4df-4525-a1c2-a17a5377f2f4 96324e08-b09e-4c45-9afc-d930e916d451 stint 32f7af81-8173-43b6-b028-47a547dabd3a->96324e08-b09e-4c45-9afc-d930e916d451 566e26cf-73bd-4dd1-87c6-2d080f199930 Kick 96324e08-b09e-4c45-9afc-d930e916d451->16478314-1c4f-49ef-97bd-bb806b3d36e5 56220711-4c34-4625-acd2-add6bb45bd31 stint 566e26cf-73bd-4dd1-87c6-2d080f199930->56220711-4c34-4625-acd2-add6bb45bd31 56220711-4c34-4625-acd2-add6bb45bd31->7b9630d6-855e-4b7e-80b4-abf6e62cd489