Sequencing
==========
The sequencing system lets you compose multi-part arrangements with
durations, time signatures, and instrument voices. This is where
PyTheory goes from theory tool to composition tool.
At the center of everything is the ``Score``. Think of it as your
arrangement, your song, your sketch pad. It holds the tempo, the time
signature, the drum pattern, and every instrument part you create. If
you've ever used a DAW, the Score is your session file. If you haven't,
it's the sheet of paper where the whole piece lives. Everything you
compose -- melodies, chord progressions, bass lines, arpeggios -- gets
added to a Score before you can hear it, export it, or do anything
useful with it.
Duration
--------
In music, all rhythm boils down to one convention: the quarter note
equals one beat. Everything else is relative to that. A whole note is
four beats. An eighth note is half a beat. This is how musicians have
communicated timing for centuries, and it's how PyTheory works too.
Once you internalize "quarter note = 1 beat," durations become
intuitive arithmetic.
A ``Duration`` represents a note length in beats (quarter note = 1 beat):
.. code-block:: pycon
>>> from pytheory import Duration
>>> Duration.WHOLE.value
4.0
>>> Duration.HALF.value
2.0
>>> Duration.QUARTER.value
1.0
>>> Duration.EIGHTH.value
0.5
>>> Duration.SIXTEENTH.value
0.25
>>> Duration.DOTTED_HALF.value
3.0
>>> Duration.DOTTED_QUARTER.value
1.5
>>> Duration.TRIPLET_QUARTER.value
0.6666666666666666
Duration supports arithmetic — multiply, divide, and add to create
compound durations:
.. code-block:: pycon
>>> Duration.WHOLE * 2
8.0
>>> Duration.HALF + Duration.QUARTER
3.0
>>> Duration.WHOLE / 2
2.0
Time Signatures
---------------
If you're not a musician, time signatures can seem mysterious. They're
not. The top number tells you how many beats are in a bar. The bottom
number tells you which note value gets one beat. That's it.
In practice, you only need to know a handful:
- **4/4** -- four beats per bar. This is the default. Almost all pop,
rock, hip hop, electronic, and R&B music is in 4/4. If you're not
sure, use this.
- **3/4** -- three beats per bar. The waltz feel. Think "Blue Danube"
or Radiohead's "Everything in Its Right Place."
- **6/8** -- six eighth notes per bar, grouped in two sets of three.
Each group feels like one big swaying beat. Folk music, slow jams,
ballads.
- **12/8** -- twelve eighth notes per bar, grouped in four sets of
three. The slow blues shuffle, the gospel feel, "At Last" by Etta
James. Each "big beat" has a triplet swing baked into it.
A ``TimeSignature`` holds the meter of a piece -- how many beats per
measure and which note value gets one beat:
.. code-block:: pycon
>>> from pytheory.rhythm import TimeSignature
>>> ts = TimeSignature.from_string("4/4")
>>> ts.beats_per_measure
4.0
>>> TimeSignature.from_string("3/4").beats_per_measure
3.0
>>> TimeSignature.from_string("6/8").beats_per_measure
3.0
>>> TimeSignature.from_string("12/8").beats_per_measure
6.0
The ``beats_per_measure`` is always in quarter-note units. In 6/8,
there are 6 eighth notes per bar = 3 quarter-note beats. In 12/8,
12 eighth notes = 6 quarter-note beats, grouped in four dotted-quarter
pulses.
Score Basics
------------
A ``Score`` is a sequence of notes and rests with a time signature and
tempo. Use ``.add()`` and ``.rest()`` for fluent chaining:
.. code-block:: python
from pytheory import Score, Duration, Tone
score = Score("4/4", bpm=120)
score.add(Tone.from_string("C4", system="western"), Duration.QUARTER)
score.add(Tone.from_string("E4", system="western"), Duration.QUARTER)
score.add(Tone.from_string("G4", system="western"), Duration.HALF)
.. code-block:: pycon
>>> score.total_beats
4.0
>>> score.measures
1.0
>>> score.duration_ms
2000.0
Rests
~~~~~
Add silence with ``.rest()``:
.. code-block:: python
score = Score("4/4", bpm=120)
score.add(Tone.from_string("C4", system="western"), Duration.HALF)
score.rest(Duration.HALF)
.. code-block:: pycon
>>> score.measures
1.0
Chords
~~~~~~
Chords work just like tones — pass any ``Chord`` object:
.. code-block:: python
from pytheory import Score, Duration, Key
key = Key("C", "major")
chords = key.progression("I", "V", "vi", "IV")
score = Score("4/4", bpm=120)
for chord in chords:
score.add(chord, Duration.WHOLE)
.. raw:: html
.. code-block:: pycon
>>> score.measures
4.0
>>> score.duration_ms
8000.0
Compound Time
~~~~~~~~~~~~~
12/8 is a compound meter — 12 eighth notes per bar grouped in four
groups of three. Each group feels like one "big beat":
.. code-block:: python
from pytheory import Score, Duration, Key
key = Key("A", "minor")
chords = key.random_progression(4)
score = Score("12/8", bpm=120)
for c in chords:
score.add(c, Duration.DOTTED_HALF)
score.add(c, Duration.DOTTED_HALF)
.. code-block:: pycon
>>> score.measures
4.0
Parts
-----
Parts are like tracks in a DAW. Each one has its own instrument sound
(synth waveform + envelope), its own volume level, and its own effects
chain. When you call ``play_score()``, all the parts get mixed together
into a single audio stream -- just like hitting play in Logic or
Ableton. You might have a pad part holding down chords, a lead part
playing a melody, and a bass part holding down the low end. Each one
is independent: different synth, different envelope, different effects.
The ``Part`` class lets you layer multiple instrument voices -- each with
its own synth waveform, ADSR envelope, and volume level. Create parts
with ``Score.part()``:
.. code-block:: python
from pytheory import Score, Key, Duration, Chord
from pytheory.play import play_score
score = Score("4/4", bpm=140)
chords = score.part("chords", synth="sine", envelope="pad", volume=0.35)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.5)
bass = score.part("bass", synth="triangle", envelope="pluck", volume=0.45)
Adding Notes to Parts
~~~~~~~~~~~~~~~~~~~~~
Parts accept note strings directly — no need to wrap in
``Tone.from_string()``. ``.add()`` and ``.rest()`` return self for
fluent chaining:
.. code-block:: python
lead.add("E5", Duration.QUARTER).add("D5", Duration.EIGHTH).rest(Duration.EIGHTH)
Raw float beats work too — useful for swing and tuplets:
.. code-block:: python
lead.add("C5", 0.67).add("B4", 0.33).add("A4", 1.0)
Chords and Tone objects work the same way:
.. code-block:: python
for chord in Key("A", "minor").progression("i", "iv", "V", "i"):
chords.add(chord, Duration.WHOLE)
for note in ["A2", "C3", "E3", "A2", "D2", "F2", "A2", "D2"]:
bass.add(note, Duration.QUARTER)
Polyphonic Hold
---------------
``Part.hold()`` adds a note without advancing the beat position —
the next note starts at the *same* time. This enables polyphonic
overlap on a single part: piano sustain, sitar drone under melody,
guitar strum texture.
.. code-block:: python
piano = score.part("piano", instrument="piano", reverb=0.3)
# Hold a C major chord for 8 beats
piano.hold("C3", Duration.WHOLE * 2, velocity=60)
piano.hold("E3", Duration.WHOLE * 2, velocity=55)
piano.hold("G3", Duration.WHOLE * 2, velocity=55)
# Melody plays simultaneously on top
for n in ["E4", "G4", "C5", "G4", "E4", "D4", "C4", "E4"]:
piano.add(n, Duration.QUARTER, velocity=80)
.. raw:: html
Arpeggiator
------------
An arpeggiator takes a chord and plays its notes one at a time, in a
pattern, automatically. You hold down a chord and it ripples through
the notes -- up, down, up-and-down, random. It's one of the most
iconic sounds in electronic music. The bubbly bass lines of acid house,
the cascading runs of 80s synth pop (think "Jump" or "Take On Me"),
the hypnotic patterns of trance -- all arpeggiators. It turns a simple
three-note chord into a rhythmic, melodic engine.
``Part.arpeggio()`` takes a chord and sequences through its notes
automatically -- like a hardware arpeggiator on a synth:
.. code-block:: python
lead = score.part(
"lead",
synth="saw",
legato=True,
glide=0.03,
distortion=0.8,
lowpass=1000,
lowpass_q=5.0,
)
lead.arpeggio(
Chord.from_symbol("Cm"),
bars=2,
pattern="up",
division=Duration.SIXTEENTH,
octaves=2,
)
Parameters:
- ``chord``: A Chord object or string like ``"Am"``.
- ``bars``: Number of bars to fill (default 1).
- ``pattern``: ``"up"``, ``"down"``, ``"updown"``, ``"downup"``, ``"random"``.
- ``division``: Step length (default ``Duration.SIXTEENTH``).
- ``octaves``: Octave span (default 1). With 2, the pattern repeats one octave up.
Chain arpeggios through a progression:
.. code-block:: python
for sym in ["Cm", "Fm", "Abm", "Gm"]:
lead.arpeggio(sym, bars=2, pattern="updown", octaves=2)
.. raw:: html
Combined with legato, glide, distortion, and a resonant lowpass, this
produces the classic acid/trance arpeggiator sound.
Legato and Glide
----------------
Normally, every note you play has its own life cycle -- the sound
attacks, sustains, and releases before the next note begins. You hear
each note as a separate event. Legato changes that. The Italian word
means "tied together," and that's exactly what it does: the envelope
flows continuously from one note to the next with no retriggering. The
pitch changes, but the sound never dies and restarts.
Glide (also called portamento) takes this further. Instead of the pitch
jumping instantly from one note to the next, it *slides* -- a smooth,
continuous pitch sweep. This is THE sound of the Roland TB-303, the
little silver box that accidentally invented acid house. A saw wave
with legato, glide, a resonant lowpass filter, and some distortion --
that's the entire genre right there.
By default, each note gets its own attack/release envelope. ``legato=True``
renders the entire part as one continuous waveform -- the pitch changes
at note boundaries but the envelope flows unbroken. Add ``glide`` for
portamento (pitch slides between notes):
.. code-block:: python
acid = score.part(
"acid",
synth="saw",
legato=True,
glide=0.04,
lowpass=3000,
lowpass_q=6.0,
distortion=0.3,
)
acid.add("C2", 0.25).add("C3", 0.25).add("G2", 0.25).add("C2", 0.25)
.. raw:: html
- ``legato``: If True, no envelope retrigger between notes (default False).
- ``glide``: Portamento time in seconds (default 0, instant).
0.03--0.05 = quick 303 slide, 0.1--0.2 = slow glide.
Complete Example
----------------
A full multi-part arrangement — rock beat with piano chords, saw
lead, and filtered bass:
.. code-block:: python
from pytheory import Score, Key, Duration, Chord
from pytheory.play import play_score
score = Score("4/4", bpm=120)
score.drums("rock", repeats=8, fill="rock", fill_every=4)
# Piano chords with reverb
piano = score.part("piano", instrument="piano", volume=0.4, reverb=0.3)
# Saw lead with delay
lead = score.part(
"lead", synth="saw", envelope="pluck", volume=0.4,
delay=0.2, delay_time=0.33, reverb=0.2, lowpass=3000,
)
# Filtered bass
bass = score.part("bass", synth="triangle", envelope="pluck",
volume=0.45, lowpass=1200)
for chord in Key("G", "major").progression("I", "V", "vi", "IV") * 2:
piano.add(chord, Duration.WHOLE)
lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5)
lead.add("G5", 1).add("E5", 1)
lead.add("D5", 0.5).add("B4", 0.5).add("A4", 1)
lead.add("G4", 2).rest(2)
lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5)
lead.add("G5", 1).add("A5", 1)
lead.add("G5", 0.5).add("E5", 0.5).add("D5", 1)
lead.add("B4", 2).rest(2)
for n in ["G2", "G2", "D2", "D2", "E2", "E2", "C2", "C2"] * 2:
bass.add(n, Duration.HALF)
play_score(score)
.. raw:: html
Velocity
--------
Real music has dynamics — accents are louder, ghost notes are barely
there, phrases crescendo and decrescendo. Every note can have its own
velocity (1–127, where 100 is the default):
.. code-block:: python
lead.add("C5", Duration.QUARTER, velocity=120) # loud accent
lead.add("D5", Duration.QUARTER, velocity=40) # ghost note
lead.add("E5", Duration.QUARTER) # default (100)
The arpeggiator also accepts velocity:
.. code-block:: python
lead.arpeggio("Am", bars=2, pattern="up", velocity=80)
Articulations
-------------
Articulations change *how* a note is played — its attack, duration, and
weight. A staccato note is short and bouncy. A marcato note hits hard.
A legato note melts into the next one. This is the difference between
a melody that sounds like a MIDI file and one that sounds like a
musician played it.
Pass ``articulation=`` to ``Part.add()``:
.. code-block:: python
piano.add("C4", Duration.QUARTER, articulation="staccato") # short, bouncy
piano.add("D4", Duration.QUARTER, articulation="legato") # smooth, overlaps
piano.add("E4", Duration.QUARTER, articulation="marcato") # heavy accent
piano.add("F4", Duration.QUARTER, articulation="tenuto") # held, soft attack
piano.add("G4", Duration.QUARTER, articulation="accent") # louder
piano.add("C5", Duration.HALF, articulation="fermata") # held longer
.. raw:: html
What each articulation does:
- **staccato** — plays ~40% of the note duration with a quick fade-out. Short and detached.
- **legato** — extends ~15% into the next note. Smooth and connected.
- **marcato** — 25% velocity boost + sharper attack. Heavy and accented.
- **tenuto** — full duration with a softer attack ramp. Held and deliberate.
- **accent** — 20% velocity boost, no duration change.
- **fermata** — stretches the note 50% longer.
Articulations work on ``Part.hold()`` and ``Part.hit()`` too.
Dynamic Curves
--------------
Real music breathes — phrases get louder, get quieter, swell and
recede. Dynamic curves let you shape the velocity across a sequence
of notes instead of setting each one manually.
.. code-block:: python
# Crescendo: quiet to loud
piano.crescendo(["C4","D4","E4","F4","G4","A4","B4","C5"],
Duration.QUARTER, start_vel=30, end_vel=110)
# Decrescendo: loud to quiet
piano.decrescendo(["C5","B4","A4","G4","F4","E4","D4","C4"],
Duration.QUARTER, start_vel=110, end_vel=30)
# Swell: up then back down (orchestral < > shape)
strings.swell(["C4","D4","E4","F4","G4","F4","E4","D4"],
Duration.QUARTER, low_vel=35, peak_vel=110)
# Custom curve: explicit velocity per note
piano.dynamics(["C4","E4","G4","C5"], Duration.QUARTER,
velocities=[50, 80, 110, 90])
.. raw:: html
Four methods:
- **crescendo()** — linear velocity ramp from ``start_vel`` to ``end_vel``.
- **decrescendo()** — same thing, but typically loud to quiet.
- **swell()** — ramps up to the midpoint, then back down. The classic
orchestral crescendo-decrescendo.
- **dynamics()** — the general form. Pass a ``(start, end)`` tuple for
a linear ramp, or a list of velocities for a custom curve.
All four accept ``articulation=`` to combine dynamics with articulations:
.. code-block:: python
# Staccato crescendo — bouncy notes getting louder
piano.crescendo(["C4","E4","G4","C5","E5","G5","C6","E6"],
Duration.EIGHTH, start_vel=40, end_vel=110,
articulation="staccato")
Part.hit() — Manual Drum Placement
-----------------------------------
The pattern system is great for grooves, but sometimes you want to
place individual drum hits with full control — articulations, effects,
and all. ``Part.hit()`` puts a drum sound into a Part's note stream:
.. code-block:: python
from pytheory import DrumSound
kit = score.part("kit", synth="sine", volume=0.7)
kit.hit(DrumSound.KICK, Duration.QUARTER, articulation="accent")
kit.hit(DrumSound.CLOSED_HAT, Duration.EIGHTH, velocity=60)
kit.hit(DrumSound.SNARE, Duration.EIGHTH, articulation="marcato")
Because hits go through the normal Part renderer, they get humanize,
effects, and articulations for free. Use this for custom beats that
don't fit a preset pattern, or for one-shot accent hits layered on
top of a pattern.
Rudiments — Flam, Diddle, Cheese
---------------------------------
Marching percussion rudiments as methods on any Part:
.. code-block:: python
from pytheory import DrumSound
p = score.part("snares", synth="sine", volume=0.9)
# Flam: grace note + main hit (gap controls tightness)
p.flam(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
# Diddle: two equal strokes in one note duration
p.diddle(DrumSound.MARCH_SNARE, Duration.EIGHTH, velocity=60)
# Cheese: flam + diddle combined
p.cheese(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
Ensemble
--------
Any Part can be rendered as an ensemble — multiple players with
per-player timing tendencies and micro pitch drift:
.. code-block:: python
# 8-player snare line
snares = score.part("snares", synth="sine", volume=0.9, ensemble=8)
# 20-player string section
strings = score.part("strings", instrument="string_ensemble", ensemble=20)
# Single player (default)
solo = score.part("solo", instrument="violin")
Each ensemble voice gets a consistent timing personality (some rush,
some drag) plus small per-note wobble, and slightly different tuning.
The result sounds like a real section — together but alive.
Solo snare, then an 8-player section plays the same pattern:
.. raw:: html
Swing and Groove
----------------
Perfectly quantized music sounds robotic. Swing delays every other
subdivision by a percentage, giving the rhythm a human, shuffled feel.
Jazz swings hard. Bossa nova swings gently. Hip hop has its own pocket.
Set swing on the Score (applies to everything) or per-Part:
.. code-block:: python
# Triplet swing — lazy jazz feel
score = Score("4/4", bpm=100, swing=0.55)
# Per-part override — the lead swings harder than the bass
lead = score.part("lead", synth="saw", swing=0.6)
bass = score.part("bass", synth="sine", swing=0.4)
Swing values:
- **0.0** = perfectly straight (default)
- **0.3** = subtle shuffle (pop, R&B)
- **0.5** = triplet feel (jazz, blues)
- **0.67** = hard swing (bebop)
Tempo Changes
-------------
Real music doesn't stay at one tempo. Songs speed up for energy,
slow down for endings, and sometimes shift abruptly. Use
``score.set_tempo()`` to change BPM at the current position:
.. code-block:: python
score = Score("4/4", bpm=90)
# Verse: slow and moody
lead.add("D5", Duration.WHOLE)
lead.add("F5", Duration.WHOLE)
# Chorus: speeds up
score.set_tempo(110)
lead.add("A5", Duration.WHOLE)
lead.add("D6", Duration.WHOLE)
# Outro: slows way down
score.set_tempo(70)
lead.add("D5", Duration.WHOLE)
The tempo map engine handles the math — beat positions are converted
to sample positions accounting for every tempo change.
Fades
-----
``Part.fade_in()`` and ``Part.fade_out()`` ramp the volume over a
number of bars. They work by generating automation points, so they
integrate naturally with the rest of the automation system:
.. code-block:: python
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
volume=0.3,
reverb=0.5,
)
# Fade in over first 4 bars
pad.fade_in(bars=4)
for chord in chords:
pad.add(chord, Duration.WHOLE)
# Fade out over last 2 bars
pad.fade_out(bars=2)
pad.rest(Duration.WHOLE)
pad.rest(Duration.WHOLE)
Parameter Ramps
---------------
Fades only control volume. ``Part.ramp()`` smoothly sweeps *any*
parameter from its current value to a target — filters, reverb,
distortion, chorus, delay, anything ``.set()`` accepts. This is how
you build filter sweeps, gradual effect sends, and EDM buildups.
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=200, lowpass_q=3.0)
# Open the filter over 8 bars
lead.ramp(over=Duration.WHOLE * 8, lowpass=8000)
# Ramp multiple params at once
pad.ramp(over=Duration.WHOLE * 4, reverb=0.5, chorus=0.3)
# Close the filter with distortion fading in
lead.ramp(over=Duration.WHOLE * 4, lowpass=400, distortion=0.5)
Four interpolation curves:
- **linear** — constant rate of change (default).
- **ease_in** — starts slow, accelerates. Good for buildups.
- **ease_out** — starts fast, decelerates. Good for releases.
- **ease_in_out** — slow at both ends. Smooth and natural.
.. code-block:: python
# EDM buildup: slow start, accelerating filter sweep
lead.ramp(over=Duration.WHOLE * 8, curve="ease_in", lowpass=8000)
# Smooth reverb wash fading in and settling
pad.ramp(over=Duration.WHOLE * 4, curve="ease_in_out", reverb=0.6)
.. raw:: html
``ramp()`` generates automation points every quarter-beat by default.
Set ``resolution=0.125`` for smoother curves (every 32nd note), or
``resolution=1.0`` for lighter automation (every beat).
Combine with ``lfo()`` for cyclic modulation and ``ramp()`` for
one-shot sweeps — together they cover the full range of parameter
automation.
Humanize
--------
Perfectly quantized music sounds like a machine made it — because it
did. Real musicians are never exactly on the beat. Their timing drifts
by a few milliseconds, their velocity varies from note to note. These
imperfections are what make music feel *alive*.
The ``humanize`` parameter adds random micro-variations in both timing
and velocity at render time. The score data stays clean and
deterministic — the randomness is only applied during playback.
.. code-block:: python
# Subtle — like a very tight session player
lead = score.part("lead", synth="saw", humanize=0.1)
# Natural — like a good live take
rhodes = score.part("rhodes", synth="fm", humanize=0.3)
# Loose — like a late-night jam after a few drinks
bass = score.part("bass", synth="sine", humanize=0.5)
Humanize values:
- **0.0** = perfectly quantized (default)
- **0.1** = subtle, studio-tight
- **0.2–0.3** = natural, like a real player
- **0.4–0.5** = loose, relaxed, human
- **0.6+** = sloppy (sometimes that's what you want)
Combine with swing for the most realistic feel:
.. code-block:: python
score = Score("4/4", bpm=95, swing=0.45)
lead = score.part(
"lead",
synth="saw",
envelope="pluck",
humanize=0.3,
delay=0.2,
reverb=0.25,
)
Song Structure
--------------
Real songs aren't one long stream of notes — they have verses,
choruses, bridges, drops. The section system lets you name blocks
of your arrangement, then repeat them without rewriting everything.
This is how actual songwriting works: you write a verse, you write
a chorus, then you arrange them — verse, verse, chorus, verse,
chorus, chorus, outro. The sections are the building blocks;
the arrangement is the order you play them in.
Define sections with ``score.section()`` and repeat them with
``score.repeat()``:
.. code-block:: python
score = Score("4/4", bpm=124)
score.drums("house", repeats=16)
pad = score.part("pad", synth="supersaw", envelope="pad")
lead = score.part("lead", synth="saw", envelope="pluck")
bass = score.part("bass", synth="sine", lowpass=300)
# ── Define the verse ──
score.section("verse")
for sym in ["Cm", "Ab", "Eb", "Bb"]:
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
lead.add("C5", 1).add("Eb5", 1).rest(2)
for n in ["C1", "C1", "Ab0", "Ab0", "Eb1", "Eb1", "Bb0", "Bb0"]:
bass.add(n, Duration.HALF)
# ── Define the chorus ──
score.section("chorus")
lead.set(lowpass=5000, reverb=0.3)
for sym in ["Cm", "Fm", "Ab", "Gm"]:
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
lead.add("C6", 1).add("Bb5", 1).add("G5", 1).rest(1)
for n in ["C1", "C1", "F1", "F1", "Ab0", "Ab0", "G1", "G1"]:
bass.add(n, Duration.HALF)
score.end_section()
# ── Arrange: verse, chorus, verse, chorus, chorus ──
score.repeat("verse")
score.repeat("chorus")
score.repeat("verse")
score.repeat("chorus", times=2)
Use any names you want — ``"intro"``, ``"verse"``, ``"chorus"``,
``"bridge"``, ``"drop"``, ``"breakdown"``, ``"outro"``, or anything
that makes sense for your song. The names are just labels.
Guitar Strumming
----------------
Any part with a fretboard can strum chords using real fingering
positions. The ``strum()`` method looks up the chord on the fretboard,
gets the correct voicing, and plays all strings as a chord.
.. code-block:: python
from pytheory import Fretboard
guitar = score.part("guitar", instrument="acoustic_guitar",
fretboard=Fretboard.guitar())
guitar.strum("Am", Duration.HALF, direction="down")
guitar.strum("G", Duration.HALF, direction="up")
guitar.strum("F", Duration.WHOLE)
Works with any fretboard instrument — guitar, ukulele, banjo, mandolin.
Works with any guitar preset — clean, crunch, distorted, orange, metal.
Pitch Bends
-----------
Bend a note's pitch up or down over its duration. Essential for guitar
bends, sitar meends, trombone slides, and vocal-style expression.
.. code-block:: python
# Guitar bend: D up to E (2 semitones)
guitar.add("D4", Duration.HALF, bend=2, bend_type="smooth")
# Release bend: E back down to D
guitar.add("E4", Duration.HALF, bend=-2)
# Blues curl: hold then bend at the end
guitar.add("C4", Duration.HALF, bend=1, bend_type="late")
Three bend types:
- ``"smooth"`` — logarithmic (default). Perceptually even pitch change.
- ``"linear"`` — linear frequency interpolation. Mechanical/synth feel.
- ``"late"`` — holds the starting pitch for 60%, bends in the last 40%.
The classic blues "curl."
Rolls
-----
Rapid repeated notes with a velocity ramp — perfect for timpani
rolls, snare rolls, tremolo on any instrument. The velocity ramps
from ``velocity_start`` to ``velocity_end`` for crescendo or
decrescendo effects.
.. code-block:: python
# Timpani crescendo roll
timp = score.part("timp", instrument="timpani")
timp.roll("C3", Duration.WHOLE, velocity_start=20, velocity_end=110)
timp.add("C3", Duration.HALF, velocity=127) # big accent
# Snare roll with 32nd notes
snare = score.part("snare", synth="noise", envelope="pluck")
snare.roll("C4", Duration.HALF, speed=0.125,
velocity_start=40, velocity_end=100)
# Decrescendo (loud to quiet)
timp.roll("G2", Duration.WHOLE, velocity_start=100, velocity_end=30)
Parameters:
- ``velocity_start``: Starting velocity (default 40).
- ``velocity_end``: Ending velocity (default 100).
- ``speed``: Note subdivision (default ``Duration.SIXTEENTH``).
Use ``0.125`` for 32nd notes, ``Duration.EIGHTH`` for 8th notes.
Tuning Systems
--------------
A Score can use any tuning system and temperament:
.. code-block:: python
# Baroque harpsichord — meantone tuning, A=415
score = Score("4/4", bpm=80, temperament="meantone",
reference_pitch=415.0)
# Indian classical — 22-shruti system
score = Score("4/4", bpm=75, system="shruti")
# Just intonation — pure intervals
score = Score("4/4", bpm=90, temperament="just")
The Score constructor accepts these tuning parameters:
- ``system``: Musical system name (default ``"western"``). Any system
from :doc:`systems` works — ``"indian"``, ``"shruti"``, ``"maqam"``,
``"carnatic"``, etc. Note strings in ``Part.add()`` are parsed against
this system.
- ``temperament``: Tuning temperament — ``"equal"`` (default),
``"pythagorean"``, ``"meantone"``, ``"just"``.
- ``reference_pitch``: Concert pitch in Hz (default 440.0). Use 415.0
for Baroque tuning, 432.0 for "Verdi tuning", etc.
Custom equal temperaments via the ``TET()`` factory:
.. code-block:: python
from pytheory import TET
edo19 = TET(19) # 19-tone equal temperament
score = Score("4/4", bpm=100, system=edo19)