mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
80698ccc3a
Break rhythm.rst and playback.rst into: - sequencing.rst: Score, Parts, Duration, arpeggiator, legato - synths.rst: 10 waveforms, 8 envelopes, combo recommendations - effects.rst: signal chain, 5 effects, automation, LFOs - drums.rst: DrumSound, 58 patterns, 21 fills, synthesis - playback.rst: simplified output functions only Rewrite index.rst with "Why compose in Python?" section explaining the sketch→hear→export→DAW workflow, plus comprehensive highlights. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
279 lines
7.6 KiB
ReStructuredText
279 lines
7.6 KiB
ReStructuredText
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.
|
|
|
|
Duration
|
|
--------
|
|
|
|
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
|
|
|
|
Time Signatures
|
|
---------------
|
|
|
|
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:: pycon
|
|
|
|
>>> from pytheory import Score, Duration, Tone
|
|
|
|
>>> score = Score("4/4", bpm=120)
|
|
>>> score.add(Tone.from_string("C4", system="western"), Duration.QUARTER)
|
|
<Score 4/4 120bpm ...>
|
|
>>> score.add(Tone.from_string("E4", system="western"), Duration.QUARTER)
|
|
<Score 4/4 120bpm ...>
|
|
>>> score.add(Tone.from_string("G4", system="western"), Duration.HALF)
|
|
<Score 4/4 120bpm ...>
|
|
|
|
>>> score.total_beats
|
|
4.0
|
|
>>> score.measures
|
|
1.0
|
|
>>> score.duration_ms
|
|
2000.0
|
|
|
|
Rests
|
|
~~~~~
|
|
|
|
Add silence with ``.rest()``:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> score = Score("4/4", bpm=120)
|
|
>>> score.add(Tone.from_string("C4", system="western"), Duration.HALF)
|
|
<Score 4/4 120bpm ...>
|
|
>>> score.rest(Duration.HALF)
|
|
<Score 4/4 120bpm ...>
|
|
>>> score.measures
|
|
1.0
|
|
|
|
Chords
|
|
~~~~~~
|
|
|
|
Chords work just like tones — pass any ``Chord`` object:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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)
|
|
|
|
>>> 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:: pycon
|
|
|
|
>>> 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)
|
|
|
|
>>> score.measures
|
|
4.0
|
|
|
|
Parts
|
|
-----
|
|
|
|
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:: pycon
|
|
|
|
>>> 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:: pycon
|
|
|
|
>>> lead.add("E5", Duration.QUARTER).add("D5", Duration.EIGHTH).rest(Duration.EIGHTH)
|
|
<Part 'lead' ...>
|
|
|
|
Raw float beats work too — useful for swing and tuplets:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> lead.add("C5", 0.67).add("B4", 0.33).add("A4", 1.0)
|
|
<Part 'lead' ...>
|
|
|
|
Chords and Tone objects work the same way:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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)
|
|
|
|
Arpeggiator
|
|
------------
|
|
|
|
``Part.arpeggio()`` takes a chord and sequences through its notes
|
|
automatically — like a hardware arpeggiator on a synth:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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:: pycon
|
|
|
|
>>> for sym in ["Cm", "Fm", "Abm", "Gm"]:
|
|
... lead.arpeggio(sym, bars=2, pattern="updown", octaves=2)
|
|
|
|
Combined with legato, glide, distortion, and a resonant lowpass, this
|
|
produces the classic acid/trance arpeggiator sound.
|
|
|
|
Legato and Glide
|
|
----------------
|
|
|
|
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:: pycon
|
|
|
|
>>> acid = score.part("acid", synth="saw", envelope="pad",
|
|
... legato=True, glide=0.04)
|
|
>>> acid.add("C2", 0.25).add("C3", 0.25).add("G2", 0.25).add("C2", 0.25)
|
|
|
|
- ``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 built from scratch — bossa nova with FM
|
|
rhodes, triangle lead, and filtered bass:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> from pytheory import Score, Pattern, Key, Duration, Chord
|
|
>>> from pytheory.play import play_score
|
|
|
|
>>> score = Score("4/4", bpm=140)
|
|
>>> score.drums("bossa nova", repeats=4)
|
|
|
|
>>> # FM rhodes with reverb
|
|
>>> rhodes = score.part("rhodes", synth="fm", envelope="piano",
|
|
... volume=0.3, reverb=0.4, reverb_decay=1.8)
|
|
|
|
>>> # Triangle lead with delay
|
|
>>> lead = score.part("lead", synth="triangle", envelope="pluck",
|
|
... volume=0.45, delay=0.25, delay_time=0.32,
|
|
... delay_feedback=0.35, reverb=0.2)
|
|
|
|
>>> # Filtered bass
|
|
>>> bass = score.part("bass", synth="sine", envelope="pluck",
|
|
... volume=0.45, lowpass=600)
|
|
|
|
>>> for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
|
|
... rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
|
|
|
|
>>> for n, d in [("E5",.67),("D5",.33),("C5",.67),("B4",.33),
|
|
... ("A4",1),("C5",.67),("E5",.33),("D5",.67),("C5",.33),
|
|
... ("A4",1)]:
|
|
... lead.add(n, d)
|
|
|
|
>>> for n in ["A2","E2","A2","C3","D2","A2","D2","F2"]:
|
|
... bass.add(n, Duration.QUARTER)
|
|
|
|
>>> play_score(score)
|