mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33b2e82594 | |||
| 9f8dd0006d | |||
| 417f7f74a3 | |||
| cd6f814049 | |||
| 83fcdb0a09 |
@@ -20,6 +20,8 @@ All notable changes to PyTheory are documented here.
|
||||
with 22 new drum patterns
|
||||
- **Piano improvements:** brightness scales with pitch, two-stage decay,
|
||||
hammer impact with felt character
|
||||
- **Vibrato tuning:** reduced across flute, oboe, trumpet, cello for
|
||||
smoother ensemble sound
|
||||
- 27 synth waveforms, 10 envelopes, 40+ instrument presets, 80+ drum patterns
|
||||
|
||||
## 0.33.1
|
||||
|
||||
+135
-6
@@ -10,7 +10,7 @@ the genre -- they tell the listener's body how to move before a single
|
||||
melodic note is played.
|
||||
|
||||
PyTheory includes a complete drum system -- 27 synthesized percussion
|
||||
sounds, 58 pattern presets across dozens of genres, and 21 fill presets.
|
||||
sounds, 80+ pattern presets across dozens of genres, and 21 fill presets.
|
||||
Every sound is generated from waveforms; no samples needed.
|
||||
|
||||
Drum Sounds
|
||||
@@ -145,8 +145,8 @@ Each sound has a dedicated synthesizer:
|
||||
Pattern Presets
|
||||
---------------
|
||||
|
||||
58 patterns spanning genres from rock to Afro-Cuban to electronic.
|
||||
Load them with ``Pattern.preset()``:
|
||||
80+ patterns spanning genres from rock to Afro-Cuban to electronic to
|
||||
world percussion. Load them with ``Pattern.preset()``:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
@@ -193,9 +193,16 @@ adds syncopation.
|
||||
rattling hi-hats of trap, the breakneck tempo of drum and bass. These
|
||||
patterns were born in drum machines and they still live there.
|
||||
|
||||
**Metal/Punk:** metal, blast beat, punk -- Speed and aggression.
|
||||
The blast beat is both feet and both hands going as fast as humanly
|
||||
possible. Punk strips everything to its essentials.
|
||||
**Metal/Punk:** metal, blast beat, punk, double kick, metal blast,
|
||||
metal groove, metal gallop -- Speed and aggression. The blast beat is
|
||||
both feet and both hands going as fast as humanly possible. Punk strips
|
||||
everything to its essentials. The metal kit adds 3 dedicated sounds
|
||||
(double kick, china cymbal, stack) and 4 patterns for extreme metal
|
||||
subgenres.
|
||||
|
||||
**World Percussion:** tabla, dhol, dholak, mridangam, djembe -- Deep
|
||||
traditions from across the globe, each with authentic sound sets and
|
||||
idiomatic patterns. See the World Percussion section below for details.
|
||||
|
||||
**Other:** funk, hip hop, bo diddley, second line, new orleans, waltz,
|
||||
12/8 blues, country, gospel, flamenco -- Everything else. The syncopated
|
||||
@@ -304,6 +311,128 @@ drum pattern and all named parts are mixed together by ``play_score()``:
|
||||
|
||||
play_score(score)
|
||||
|
||||
World Percussion
|
||||
----------------
|
||||
|
||||
PyTheory includes dedicated sound sets and pattern presets for
|
||||
traditional percussion instruments from around the world. Each
|
||||
instrument has its own synthesized sounds that capture the timbral
|
||||
character of the real instrument, plus idiomatic rhythmic patterns
|
||||
drawn from their musical traditions.
|
||||
|
||||
Tabla
|
||||
~~~~~
|
||||
|
||||
The tabla is a pair of hand drums from the Indian subcontinent -- the
|
||||
smaller, higher-pitched *dayan* and the larger, bass *bayan*. It is
|
||||
the rhythmic backbone of Hindustani classical music, and one of the
|
||||
most expressive percussion instruments ever created. A single tabla
|
||||
player can produce an astonishing range of tones by varying finger
|
||||
placement, pressure, and striking technique.
|
||||
|
||||
**6 sounds** -- covering the primary tabla strokes (na, tin, tun, ge,
|
||||
ke, and ti-ra-ki-ta combinations).
|
||||
|
||||
**7 patterns:** teental (16 beats, the most common taal), jhaptaal
|
||||
(10 beats), rupak (7 beats), dadra (6 beats), keherwa (8 beats, folk
|
||||
and light classical), tabla solo, and tiri kita (fast ornamental
|
||||
pattern).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=80)
|
||||
score.drums("teental", repeats=4)
|
||||
|
||||
Dhol
|
||||
~~~~
|
||||
|
||||
The dhol is a double-headed barrel drum from Punjab, played with
|
||||
sticks. It is the driving force behind bhangra music -- loud,
|
||||
energetic, and physically impossible to sit still to.
|
||||
|
||||
**3 sounds** -- bass stroke, treble stroke, and rimshot.
|
||||
|
||||
**2 patterns:** bhangra (the classic bhangra groove) and dhol chaal
|
||||
(a processional rhythm).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=160)
|
||||
score.drums("bhangra", repeats=4)
|
||||
|
||||
Dholak
|
||||
~~~~~~
|
||||
|
||||
The dholak is a smaller, lighter two-headed drum used across South
|
||||
Asia in folk music, qawwali, and Bollywood. Played with bare hands,
|
||||
it produces a warm, melodic tone.
|
||||
|
||||
**3 sounds** -- bass, treble, and slap.
|
||||
|
||||
**2 patterns:** qawwali (the rhythmic foundation of Sufi devotional
|
||||
music) and dholak folk (a general folk groove).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120)
|
||||
score.drums("qawwali", repeats=4)
|
||||
|
||||
Mridangam
|
||||
~~~~~~~~~
|
||||
|
||||
The mridangam is a double-headed drum from South India, the
|
||||
rhythmic anchor of Carnatic classical music. Its tuning system is
|
||||
extraordinarily precise, and its rhythmic vocabulary is among the
|
||||
most mathematically complex in the world.
|
||||
|
||||
**4 sounds** -- tha, thom, nam, and din.
|
||||
|
||||
**2 patterns:** adi talam (the most common Carnatic talam, 8 beats)
|
||||
and mridangam korvai (a rhythmic cadence pattern).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=90)
|
||||
score.drums("adi talam", repeats=4)
|
||||
|
||||
Djembe
|
||||
~~~~~~
|
||||
|
||||
The djembe is a rope-tuned goblet drum from West Africa, capable of
|
||||
producing a wide range of tones from deep bass to sharp slaps. It is
|
||||
central to the drum ensemble traditions of Mali, Guinea, and Senegal.
|
||||
|
||||
**3 sounds** -- bass (open center strike), tone (edge strike), and
|
||||
slap (sharp edge strike).
|
||||
|
||||
**3 patterns:** djembe (a basic accompanying rhythm), kuku (a
|
||||
traditional rhythm from Guinea associated with fishing), and soli (a
|
||||
solo/celebration rhythm).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=120)
|
||||
score.drums("djembe", repeats=4)
|
||||
|
||||
Metal Kit
|
||||
~~~~~~~~~
|
||||
|
||||
A dedicated percussion kit for extreme metal subgenres, with
|
||||
specialized sounds and patterns that go beyond the standard drum kit.
|
||||
|
||||
**3 sounds** -- double kick (triggered, tight attack), china cymbal,
|
||||
and stack (a short, trashy cymbal choke).
|
||||
|
||||
**4 patterns:** double kick (relentless double bass drum pattern),
|
||||
metal blast (blast beat with china cymbal accents), metal groove (a
|
||||
half-time groove with double kick fills), and metal gallop (the
|
||||
classic triplet-feel gallop rhythm).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
score = Score("4/4", bpm=200)
|
||||
score.drums("metal blast", repeats=4)
|
||||
|
||||
MIDI Export
|
||||
-----------
|
||||
|
||||
|
||||
+91
-7
@@ -32,8 +32,8 @@ It's a well-tested order that sounds good by default.
|
||||
|
||||
Effects are applied in this fixed order::
|
||||
|
||||
Signal --> Saturation --> Tremolo --> Distortion --> Chorus --> Phaser
|
||||
--> Highpass --> Lowpass --> Delay --> Reverb --> Mix
|
||||
Signal --> Saturation --> Tremolo --> Distortion --> Cabinet --> Chorus
|
||||
--> Phaser --> Highpass --> Lowpass --> Delay --> Reverb --> Mix
|
||||
|
||||
Additionally, these per-note effects are applied before the part effects chain:
|
||||
|
||||
@@ -47,11 +47,12 @@ Part-level effects:
|
||||
- **Saturation** first: subtle even-harmonic warmth (tape/tube color).
|
||||
- **Tremolo** second: amplitude LFO modulation.
|
||||
- **Distortion** third: drives the signal before filtering.
|
||||
- **Chorus** fourth: thickens the signal.
|
||||
- **Phaser** fifth: swept allpass notches.
|
||||
- **Highpass** sixth: removes low-frequency mud.
|
||||
- **Lowpass** seventh: shapes the tone (like a tone knob on an amp).
|
||||
- **Delay** eighth: echoes the shaped signal (tap delay / tape echo).
|
||||
- **Cabinet** fourth: speaker cab simulation (rolloff + presence bump).
|
||||
- **Chorus** fifth: thickens the signal.
|
||||
- **Phaser** sixth: swept allpass notches.
|
||||
- **Highpass** seventh: removes low-frequency mud.
|
||||
- **Lowpass** eighth: shapes the tone (like a tone knob on an amp).
|
||||
- **Delay** ninth: echoes the shaped signal (tap delay / tape echo).
|
||||
- **Reverb** last: places everything in a space (room / hall).
|
||||
|
||||
Distortion
|
||||
@@ -96,6 +97,89 @@ Parameters:
|
||||
distortion_drive=10.0,
|
||||
)
|
||||
|
||||
Cabinet Simulation
|
||||
------------------
|
||||
|
||||
A real guitar amp doesn't just distort the signal -- the speaker
|
||||
cabinet shapes the tone dramatically. A 12-inch speaker in a closed
|
||||
cabinet rolls off the harsh high frequencies above 5 kHz and adds a
|
||||
presence bump around 2--3 kHz that gives the sound its "in the room"
|
||||
quality. Without a cabinet, distortion sounds thin and fizzy. With
|
||||
one, it sounds like a real amp.
|
||||
|
||||
PyTheory's cabinet simulation applies a speaker rolloff curve (lowpass
|
||||
at ~5 kHz) combined with a presence resonance bump, placed in the
|
||||
signal chain immediately after distortion -- exactly where it sits in
|
||||
a real amp.
|
||||
|
||||
Parameters:
|
||||
|
||||
- ``cabinet``: Wet/dry mix, 0.0--1.0 (default 0, off).
|
||||
|
||||
- 0.3--0.5 = subtle speaker coloring
|
||||
- 0.6--0.8 = classic amp-in-a-room
|
||||
- 1.0 = full cabinet, no dry signal
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Classic rock amp tone: distortion into cabinet
|
||||
guitar = score.part(
|
||||
"guitar",
|
||||
synth="saw",
|
||||
envelope="pluck",
|
||||
distortion=0.6,
|
||||
distortion_drive=5.0,
|
||||
cabinet=0.8,
|
||||
)
|
||||
|
||||
# Clean amp with just cabinet warmth (no distortion)
|
||||
clean = score.part(
|
||||
"clean",
|
||||
synth="triangle",
|
||||
envelope="pluck",
|
||||
cabinet=0.5,
|
||||
)
|
||||
|
||||
Analog Drift
|
||||
------------
|
||||
|
||||
Real analog synthesizers are never perfectly in tune. The voltage-
|
||||
controlled oscillators drift slightly over time as components warm up
|
||||
and temperature fluctuates. This imperfection is actually a big part
|
||||
of why vintage analog synths sound so appealing -- the subtle pitch
|
||||
wandering gives each note a unique, living quality that static digital
|
||||
oscillators lack.
|
||||
|
||||
The ``analog_drift`` parameter adds slow, random pitch variation to
|
||||
each oscillator, modeling this vintage behavior.
|
||||
|
||||
Parameters:
|
||||
|
||||
- ``analog_drift``: Drift amount, 0.0--1.0 (default 0, off).
|
||||
|
||||
- 0.05--0.1 = subtle warmth (studio-grade analog)
|
||||
- 0.15--0.25 = noticeable drift (vintage gear warming up)
|
||||
- 0.3+ = unstable, wobbly (broken tape machine)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Warm vintage pad
|
||||
pad = score.part(
|
||||
"pad",
|
||||
synth="supersaw",
|
||||
envelope="pad",
|
||||
analog_drift=0.1,
|
||||
chorus=0.3,
|
||||
)
|
||||
|
||||
# Lo-fi detuned lead
|
||||
lead = score.part(
|
||||
"lead",
|
||||
synth="saw",
|
||||
envelope="pluck",
|
||||
analog_drift=0.25,
|
||||
)
|
||||
|
||||
Chorus
|
||||
------
|
||||
|
||||
|
||||
+175
-3
@@ -1,7 +1,7 @@
|
||||
Synthesizers
|
||||
============
|
||||
|
||||
PyTheory includes 13 built-in waveforms and 10 ADSR envelope presets.
|
||||
PyTheory includes 27 built-in waveforms and 10 ADSR envelope presets.
|
||||
Every sound is generated from scratch -- no samples or external audio
|
||||
files needed.
|
||||
|
||||
@@ -233,7 +233,7 @@ shapes the amplitude over time for natural-sounding notes:
|
||||
- **Sustain** -- the held volume while the note is on.
|
||||
- **Release** -- how quickly it fades to silence after the note ends.
|
||||
|
||||
PyTheory includes 8 presets:
|
||||
PyTheory includes 10 presets:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -386,11 +386,183 @@ more "wooden" than a raw saw wave.
|
||||
|
||||
violin = score.part("violin", synth="strings_synth")
|
||||
|
||||
Dedicated Instrument Synths
|
||||
--------------------------
|
||||
|
||||
Beyond the classic and physical modeling waveforms, PyTheory includes
|
||||
14 dedicated instrument synths. Each one uses tailored synthesis
|
||||
techniques -- additive harmonics, formant shaping, body resonance
|
||||
modeling, and specialized envelopes -- to capture the character of a
|
||||
specific acoustic instrument. These are the waveforms that bring the
|
||||
total count to 27.
|
||||
|
||||
Piano Synth
|
||||
~~~~~~~~~~~
|
||||
|
||||
Hammer-strike envelope with body resonance and subtle inharmonicity.
|
||||
Models the way a felt hammer excites steel strings inside a wooden
|
||||
soundboard.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
piano = score.part("piano", synth="piano_synth")
|
||||
|
||||
Bass Guitar Synth
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Plucked string model with finger-damped harmonics and low-end warmth.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bass = score.part("bass", synth="bass_guitar_synth")
|
||||
|
||||
Flute Synth
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Breathy noise excitation through a resonant tube model, with
|
||||
overblowing behavior at higher velocities.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
flute = score.part("flute", synth="flute_synth")
|
||||
|
||||
Trumpet Synth
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Brass lip-buzz model with spectral brightness that increases with
|
||||
velocity, plus a characteristic brassy edge from shaped harmonics.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
trumpet = score.part("trumpet", synth="trumpet_synth")
|
||||
|
||||
Clarinet Synth
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Cylindrical bore model producing mostly odd harmonics, giving the
|
||||
characteristic hollow, woody tone.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
clarinet = score.part("clarinet", synth="clarinet_synth")
|
||||
|
||||
Oboe Synth
|
||||
~~~~~~~~~~~
|
||||
|
||||
Double-reed model with nasal formant shaping and a buzzy, penetrating
|
||||
timbre.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
oboe = score.part("oboe", synth="oboe_synth")
|
||||
|
||||
Marimba Synth
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Tuned bar model with a soft mallet attack and a warm, resonant decay
|
||||
that emphasizes the fundamental.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
marimba = score.part("marimba", synth="marimba_synth")
|
||||
|
||||
Harpsichord Synth
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Plucked-string model with a bright, immediate attack and rapid decay
|
||||
-- the characteristic "plink" of a quill plucking a string.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
harpsi = score.part("harpsi", synth="harpsichord_synth")
|
||||
|
||||
Cello Synth
|
||||
~~~~~~~~~~~
|
||||
|
||||
Bowed string model with body formants at cello resonance frequencies,
|
||||
producing a rich, warm, sustained tone.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
cello = score.part("cello", synth="cello_synth")
|
||||
|
||||
Harp Synth
|
||||
~~~~~~~~~~
|
||||
|
||||
Plucked string with longer sustain and gentle high-frequency rolloff,
|
||||
modeling nylon strings on a resonant frame.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
harp = score.part("harp", synth="harp_synth")
|
||||
|
||||
Upright Bass Synth
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pizzicato double bass with woody body resonance and a thumpy low end.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bass = score.part("bass", synth="upright_bass_synth")
|
||||
|
||||
Acoustic Guitar Synth
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Steel-string model with pick transient, body resonance, and natural
|
||||
string decay.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
guitar = score.part("guitar", synth="acoustic_guitar_synth")
|
||||
|
||||
Electric Guitar Synth
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Magnetic pickup model with brighter harmonics and less body resonance
|
||||
than the acoustic, ready for effects processing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
eguitar = score.part("eguitar", synth="electric_guitar_synth")
|
||||
|
||||
Sitar Synth
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Sympathetic string resonance with the characteristic buzzy "jawari"
|
||||
bridge, producing a shimmering, metallic sustain.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
sitar = score.part("sitar", synth="sitar_synth")
|
||||
|
||||
Analog Oscillator Drift
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All waveform synths support the ``analog_drift`` parameter, which adds
|
||||
subtle, slow random pitch variation to each oscillator -- modeling the
|
||||
voltage instability of vintage analog circuits. This is what makes a
|
||||
real Minimoog sound slightly different on every note, and why analog
|
||||
synths feel "alive" compared to their digital counterparts.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Subtle vintage drift
|
||||
pad = score.part("pad", synth="saw", analog_drift=0.1)
|
||||
|
||||
# More pronounced, wobbly analog character
|
||||
lead = score.part("lead", synth="square", analog_drift=0.3)
|
||||
|
||||
Drift values:
|
||||
|
||||
- **0.05--0.1** = subtle warmth (studio-grade analog)
|
||||
- **0.15--0.25** = noticeable drift (vintage gear warming up)
|
||||
- **0.3+** = unstable, wobbly (broken tape machine)
|
||||
|
||||
Instrument Presets
|
||||
------------------
|
||||
|
||||
Instead of choosing synth + envelope + effects manually, use an
|
||||
instrument preset — 38 predefined combinations that approximate real
|
||||
instrument preset — 40+ predefined combinations that approximate real
|
||||
instruments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
+9
-7
@@ -77,14 +77,16 @@ What's Inside
|
||||
numbers), scale recommendation, modulation, voice leading
|
||||
- **Sequencing** — Score, Parts, arpeggiator, legato/glide, velocity,
|
||||
swing, humanize, tempo changes, song sections with repeat
|
||||
- **Synthesis** — 13 waveforms (including Karplus-Strong pluck, Hammond organ,
|
||||
bowed string), 10 envelopes, 38 instrument presets, configurable FM,
|
||||
sub-oscillator, noise layer, filter envelope, velocity-to-brightness,
|
||||
detune, stereo pan/spread, 58 drum patterns (stereo panned), 21 fills
|
||||
- **Synthesis** — 27 waveforms (including Karplus-Strong pluck, Hammond organ,
|
||||
bowed string, and 14 dedicated instrument synths), 10 envelopes, 40+
|
||||
instrument presets, configurable FM, sub-oscillator, noise layer, filter
|
||||
envelope, velocity-to-brightness, analog oscillator drift, detune, stereo
|
||||
pan/spread, strumming, 80+ drum patterns (stereo panned, including world
|
||||
percussion), 21 fills
|
||||
- **Effects** — reverb (algorithmic + 7 convolution IRs, stereo), delay,
|
||||
lowpass/highpass (with resonance), distortion, saturation, chorus,
|
||||
phaser, tremolo, sidechain compression, automation, LFOs. Master bus
|
||||
compressor/limiter
|
||||
lowpass/highpass (with resonance), distortion, cabinet simulation,
|
||||
saturation, chorus, phaser, tremolo, analog drift, sidechain compression,
|
||||
automation, LFOs. Master bus compressor/limiter
|
||||
- **Instruments** — 25 presets with fingering generation
|
||||
- **Output** — stereo playback, WAV export, MIDI import/export
|
||||
- **Interface** — REPL with tab completion, CLI (15 commands), ``pytheory demo``
|
||||
|
||||
+103
-139
@@ -45,18 +45,15 @@ def bossa_nova_girl():
|
||||
score = Score("4/4", bpm=140)
|
||||
score.drums("bossa nova", repeats=4)
|
||||
|
||||
rhodes = score.part("rhodes", synth="fm", envelope="piano",
|
||||
rhodes = score.part("rhodes", instrument="electric_piano",
|
||||
volume=0.3, pan=-0.3,
|
||||
reverb=0.4, reverb_decay=1.8, reverb_type="plate",
|
||||
detune=8, humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="pluck",
|
||||
reverb=0.4, reverb_decay=1.8, reverb_type="plate")
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.45, pan=0.3,
|
||||
delay=0.25, delay_time=0.32, delay_feedback=0.35,
|
||||
reverb=0.2, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.45, pan=0.0, lowpass=600,
|
||||
humanize=0.15)
|
||||
reverb=0.2, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.45, pan=0.0, lowpass=600)
|
||||
|
||||
for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
|
||||
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -90,19 +87,16 @@ def bebop_in_bb():
|
||||
score = Score("4/4", bpm=160)
|
||||
score.drums("bebop", repeats=8, fill="jazz", fill_every=8)
|
||||
|
||||
rhodes = score.part("rhodes", synth="fm", envelope="piano",
|
||||
rhodes = score.part("rhodes", instrument="electric_piano",
|
||||
volume=0.25, pan=-0.3,
|
||||
reverb=0.35, reverb_decay=1.2, reverb_type="plate",
|
||||
detune=8, humanize=0.2)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck",
|
||||
reverb=0.35, reverb_decay=1.2, reverb_type="plate")
|
||||
lead = score.part("lead", instrument="trumpet",
|
||||
volume=0.4, pan=0.25,
|
||||
lowpass=4000, lowpass_q=1.1,
|
||||
delay=0.15, delay_time=0.19, delay_feedback=0.25,
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="triangle", envelope="pluck",
|
||||
volume=0.4, pan=0.0, lowpass=500,
|
||||
humanize=0.15)
|
||||
reverb=0.15, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.4, pan=0.0, lowpass=500)
|
||||
|
||||
for sym in ["Bb", "Gm", "Cm", "F7"] * 2:
|
||||
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -148,14 +142,12 @@ def salsa_descarga():
|
||||
volume=0.2, pan=-0.35,
|
||||
reverb=0.3, reverb_type="plate", lowpass=2000,
|
||||
detune=10, humanize=0.2)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck",
|
||||
lead = score.part("lead", instrument="trumpet",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.2, delay_time=0.167, delay_feedback=0.3,
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="pulse", envelope="pluck",
|
||||
volume=0.45, pan=0.0, lowpass=500, lowpass_q=1.3,
|
||||
humanize=0.15)
|
||||
reverb=0.15, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="synth_bass",
|
||||
volume=0.45, pan=0.0, lowpass=500, lowpass_q=1.3)
|
||||
|
||||
for sym in ["Em7b5", "A7", "Dm7", "Bbmaj7"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -191,20 +183,17 @@ def afrobeat_groove():
|
||||
score = Score("4/4", bpm=115)
|
||||
score.drums("afrobeat", repeats=8, fill="afrobeat", fill_every=8)
|
||||
|
||||
pads = score.part("pads", synth="supersaw", envelope="pad",
|
||||
pads = score.part("pads", instrument="synth_pad",
|
||||
volume=0.2, pan=-0.3,
|
||||
reverb=0.4, reverb_decay=2.0, reverb_type="cathedral",
|
||||
lowpass=3000, detune=10, spread=0.6,
|
||||
humanize=0.2)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck",
|
||||
lowpass=3000)
|
||||
lead = score.part("lead", instrument="trumpet",
|
||||
volume=0.4, pan=0.3,
|
||||
lowpass=3000, lowpass_q=1.0,
|
||||
delay=0.2, delay_time=0.26, delay_feedback=0.3,
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.5, pan=0.0, lowpass=500,
|
||||
humanize=0.15)
|
||||
reverb=0.15, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.5, pan=0.0, lowpass=500)
|
||||
|
||||
for sym in ["Em", "Am", "D", "C"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -241,14 +230,12 @@ def reggae_one_drop():
|
||||
reverb=0.5, reverb_decay=2.0, reverb_type="cathedral",
|
||||
lowpass=2000, detune=8,
|
||||
humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="strings",
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.35, delay_time=0.5625, delay_feedback=0.45,
|
||||
reverb=0.3, reverb_type="cathedral",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.55, pan=0.0, lowpass=400, lowpass_q=1.3,
|
||||
humanize=0.15)
|
||||
reverb=0.3, reverb_type="cathedral")
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.55, pan=0.0, lowpass=400, lowpass_q=1.3)
|
||||
|
||||
for sym in ["G", "C", "D", "C"] * 2:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -286,15 +273,13 @@ def funk_workout():
|
||||
volume=0.25, pan=-0.4,
|
||||
lowpass=2500, reverb=0.15, reverb_type="plate",
|
||||
sidechain=0.4, humanize=0.2)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck",
|
||||
lead = score.part("lead", instrument="synth_lead",
|
||||
volume=0.4, pan=0.35,
|
||||
lowpass=3500, lowpass_q=1.5,
|
||||
delay=0.15, delay_time=0.15, delay_feedback=0.25,
|
||||
reverb=0.1, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="pulse", envelope="pluck",
|
||||
volume=0.5, pan=0.0, lowpass=600, lowpass_q=1.2,
|
||||
humanize=0.15)
|
||||
reverb=0.1, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="synth_bass",
|
||||
volume=0.5, pan=0.0, lowpass=600, lowpass_q=1.2)
|
||||
|
||||
for sym in ["Em", "Am", "D", "B7"] * 2:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -332,18 +317,16 @@ def blues_shuffle():
|
||||
score = Score("12/8", bpm=70)
|
||||
score.drums("12/8 blues", repeats=6)
|
||||
|
||||
chords = score.part("chords", synth="fm", envelope="piano",
|
||||
chords = score.part("chords", instrument="electric_piano",
|
||||
volume=0.3, pan=-0.3,
|
||||
reverb=0.3, reverb_decay=1.5, reverb_type="plate",
|
||||
detune=8, humanize=0.2)
|
||||
lead = score.part("lead", synth="saw", envelope="pluck",
|
||||
reverb=0.3, reverb_decay=1.5, reverb_type="plate")
|
||||
lead = score.part("lead", instrument="trumpet",
|
||||
volume=0.45, pan=0.25,
|
||||
reverb=0.3, reverb_decay=1.2, reverb_type="plate",
|
||||
delay=0.2, delay_time=0.43, delay_feedback=0.3,
|
||||
lowpass=3500, humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.5, pan=0.0, lowpass=500,
|
||||
humanize=0.15)
|
||||
lowpass=3500)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.5, pan=0.0, lowpass=500)
|
||||
|
||||
for sym in ["A", "A", "D", "D", "E7", "A"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
|
||||
@@ -381,19 +364,16 @@ def samba_de_janeiro():
|
||||
score = Score("4/4", bpm=170)
|
||||
score.drums("samba", repeats=8, fill="samba", fill_every=8)
|
||||
|
||||
pads = score.part("pads", synth="supersaw", envelope="pad",
|
||||
pads = score.part("pads", instrument="synth_pad",
|
||||
volume=0.2, pan=-0.3,
|
||||
reverb=0.45, reverb_decay=2.0, reverb_type="plate",
|
||||
lowpass=4000, detune=10, spread=0.5,
|
||||
humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="pluck",
|
||||
lowpass=4000)
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.45, pan=0.3,
|
||||
delay=0.2, delay_time=0.176, delay_feedback=0.3,
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.45, pan=0.0, lowpass=500,
|
||||
humanize=0.15)
|
||||
reverb=0.15, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.45, pan=0.0, lowpass=500)
|
||||
|
||||
for sym in ["G", "Em", "Am", "D7"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -426,18 +406,15 @@ def jazz_waltz():
|
||||
score = Score("3/4", bpm=150)
|
||||
score.drums("waltz", repeats=16)
|
||||
|
||||
rhodes = score.part("rhodes", synth="fm", envelope="piano",
|
||||
rhodes = score.part("rhodes", instrument="electric_piano",
|
||||
volume=0.3, pan=-0.3,
|
||||
reverb=0.4, reverb_decay=2.0, reverb_type="cathedral",
|
||||
detune=8, humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="strings",
|
||||
reverb=0.4, reverb_decay=2.0, reverb_type="cathedral")
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.25,
|
||||
reverb=0.3, reverb_decay=1.5, reverb_type="plate",
|
||||
delay=0.2, delay_time=0.4, delay_feedback=0.3,
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.4, pan=0.0, lowpass=500,
|
||||
humanize=0.15)
|
||||
delay=0.2, delay_time=0.4, delay_feedback=0.3)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.4, pan=0.0, lowpass=500)
|
||||
|
||||
for _ in range(2):
|
||||
for sym in ["Fmaj7", "Gm", "C7", "Fmaj7"]:
|
||||
@@ -471,20 +448,19 @@ def house_anthem():
|
||||
score = Score("4/4", bpm=124)
|
||||
score.drums("house", repeats=8, fill="house", fill_every=8)
|
||||
|
||||
pads = score.part("pads", synth="supersaw", envelope="pad",
|
||||
pads = score.part("pads", instrument="synth_pad",
|
||||
volume=0.25, pan=-0.3,
|
||||
reverb=0.5, reverb_decay=2.5, reverb_type="cathedral",
|
||||
lowpass=5000, detune=12, spread=0.7,
|
||||
sidechain=0.6, humanize=0.2)
|
||||
lead = score.part("lead", synth="saw", envelope="staccato",
|
||||
lowpass=5000,
|
||||
sidechain=0.6)
|
||||
lead = score.part("lead", instrument="synth_lead",
|
||||
volume=0.35, pan=0.3,
|
||||
lowpass=2000, lowpass_q=2.0,
|
||||
delay=0.2, delay_time=0.242, delay_feedback=0.35,
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.55, pan=0.0, lowpass=300,
|
||||
sidechain=0.5, humanize=0.15)
|
||||
reverb=0.15, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.55, pan=0.0,
|
||||
sidechain=0.5)
|
||||
|
||||
for sym in ["Cm", "Ab", "Bb", "Cm"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -538,14 +514,12 @@ def dub_kingston():
|
||||
reverb=0.6, reverb_decay=2.5, reverb_type="cathedral",
|
||||
lowpass=1500, lowpass_q=0.9, detune=8,
|
||||
humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="strings",
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.45, delay_time=0.625, delay_feedback=0.5,
|
||||
reverb=0.35, reverb_decay=2.0, reverb_type="cathedral",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.6, pan=0.0, lowpass=400, lowpass_q=1.5,
|
||||
humanize=0.15)
|
||||
reverb=0.35, reverb_decay=2.0, reverb_type="cathedral")
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.6, pan=0.0, lowpass=400, lowpass_q=1.5)
|
||||
siren = score.part("siren", synth="pwm_slow", envelope="pad",
|
||||
volume=0.15, pan=0.5,
|
||||
reverb=0.7, reverb_decay=3.0, reverb_type="cathedral",
|
||||
@@ -585,20 +559,20 @@ def techno_minimal():
|
||||
score = Score("4/4", bpm=130)
|
||||
score.drums("techno", repeats=8, fill="house", fill_every=8)
|
||||
|
||||
pad = score.part("pad", synth="supersaw", envelope="pad",
|
||||
pad = score.part("pad", instrument="synth_pad",
|
||||
volume=0.2, pan=-0.3,
|
||||
reverb=0.5, reverb_decay=3.0, reverb_type="cathedral",
|
||||
lowpass=3000, detune=12, spread=0.7,
|
||||
sidechain=0.6, humanize=0.2)
|
||||
lowpass=3000,
|
||||
sidechain=0.6)
|
||||
lead = score.part("lead", synth="pwm_fast", envelope="staccato",
|
||||
volume=0.35, pan=0.3,
|
||||
lowpass=1500, lowpass_q=3.0,
|
||||
delay=0.3, delay_time=0.231, delay_feedback=0.4,
|
||||
reverb=0.1, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.55, pan=0.0, lowpass=250,
|
||||
sidechain=0.5, humanize=0.15)
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.55, pan=0.0,
|
||||
sidechain=0.5)
|
||||
|
||||
for sym in ["Fm", "Db", "Eb", "Fm"] * 2:
|
||||
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -624,18 +598,15 @@ def gospel_shuffle():
|
||||
score = Score("4/4", bpm=108)
|
||||
score.drums("gospel", repeats=8, fill="buildup", fill_every=8)
|
||||
|
||||
organ = score.part("organ", synth="fm", envelope="organ",
|
||||
organ = score.part("organ", instrument="organ",
|
||||
volume=0.3, pan=-0.3,
|
||||
reverb=0.45, reverb_decay=2.0, reverb_type="cathedral",
|
||||
detune=8, humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="pluck",
|
||||
reverb=0.45, reverb_decay=2.0, reverb_type="cathedral")
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.2, delay_time=0.278, delay_feedback=0.3,
|
||||
reverb=0.2, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.45, pan=0.0, lowpass=500,
|
||||
humanize=0.15)
|
||||
reverb=0.2, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.45, pan=0.0, lowpass=500)
|
||||
|
||||
for sym in ["C", "Am", "F", "G"] * 2:
|
||||
organ.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -691,20 +662,18 @@ def dub_delay_madness():
|
||||
volume=0.15, pan=-0.4,
|
||||
reverb=0.7, reverb_decay=3.0, reverb_type="cathedral",
|
||||
lowpass=1200, detune=8, humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.6, pan=0.0, lowpass=350, lowpass_q=1.5,
|
||||
humanize=0.15)
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.6, pan=0.0, lowpass=350, lowpass_q=1.5)
|
||||
siren = score.part("siren", synth="pwm_slow", envelope="pad",
|
||||
volume=0.12, pan=0.5,
|
||||
reverb=0.8, reverb_decay=4.0, reverb_type="cathedral",
|
||||
delay=0.4, delay_time=0.88, delay_feedback=0.6,
|
||||
lowpass=900, detune=10)
|
||||
# Melodica stabs — sparse, lots of delay
|
||||
melodica = score.part("melodica", synth="triangle", envelope="pluck",
|
||||
melodica = score.part("melodica", instrument="flute",
|
||||
volume=0.35, pan=0.3,
|
||||
delay=0.6, delay_time=0.66, delay_feedback=0.55,
|
||||
reverb=0.5, reverb_decay=2.5, reverb_type="cathedral",
|
||||
humanize=0.2)
|
||||
reverb=0.5, reverb_decay=2.5, reverb_type="cathedral")
|
||||
|
||||
for sym in ["Em", "Em", "Am", "Am", "Em", "Em", "Bm", "Em"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -741,19 +710,16 @@ def drum_and_bass():
|
||||
score = Score("4/4", bpm=174)
|
||||
score.drums("drum and bass", repeats=8, fill="buildup", fill_every=8)
|
||||
|
||||
pads = score.part("pads", synth="supersaw", envelope="pad",
|
||||
pads = score.part("pads", instrument="synth_pad",
|
||||
volume=0.25, pan=-0.3,
|
||||
reverb=0.5, reverb_decay=2.5, reverb_type="plate",
|
||||
lowpass=4000, detune=10, spread=0.6,
|
||||
humanize=0.2)
|
||||
lead = score.part("lead", synth="triangle", envelope="strings",
|
||||
lowpass=4000)
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.3, delay_time=0.172, delay_feedback=0.4,
|
||||
reverb=0.25, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.55, pan=0.0, lowpass=300,
|
||||
humanize=0.15)
|
||||
reverb=0.25, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.55, pan=0.0)
|
||||
|
||||
for sym in ["Am", "F", "C", "G"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -787,25 +753,24 @@ def drake_vibes():
|
||||
score = Score("4/4", bpm=68)
|
||||
score.drums("trap", repeats=8, fill="trap", fill_every=8)
|
||||
|
||||
pads = score.part("pads", synth="supersaw", envelope="pad",
|
||||
pads = score.part("pads", instrument="synth_pad",
|
||||
volume=0.2, pan=-0.25,
|
||||
reverb=0.5, reverb_decay=3.0, reverb_type="cathedral",
|
||||
lowpass=2500, detune=12, spread=0.6,
|
||||
sidechain=0.4, humanize=0.2)
|
||||
bells = score.part("bells", synth="fm", envelope="bell",
|
||||
lowpass=2500,
|
||||
sidechain=0.4)
|
||||
bells = score.part("bells", instrument="vibraphone",
|
||||
volume=0.3, pan=0.4,
|
||||
reverb=0.4, reverb_decay=2.0, reverb_type="plate",
|
||||
delay=0.25, delay_time=0.44, delay_feedback=0.35,
|
||||
humanize=0.2)
|
||||
delay=0.25, delay_time=0.44, delay_feedback=0.35)
|
||||
lead = score.part("lead", synth="pwm_slow", envelope="strings",
|
||||
volume=0.35, pan=-0.2,
|
||||
reverb=0.3, reverb_type="cathedral", lowpass=2000,
|
||||
delay=0.2, delay_time=0.88, delay_feedback=0.3,
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", synth="sine", envelope="pluck",
|
||||
volume=0.6, pan=0.0, lowpass=200, lowpass_q=1.8,
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.6, pan=0.0,
|
||||
distortion=0.4, distortion_drive=2.0,
|
||||
sidechain=0.3, humanize=0.15)
|
||||
sidechain=0.3)
|
||||
|
||||
for sym in ["Ebm", "B", "Gb", "Db"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -858,14 +823,14 @@ def neon_grid():
|
||||
score._drum_pattern_beats = max(score._drum_pattern_beats, 32.0)
|
||||
|
||||
sky = score.part(
|
||||
"sky", synth="supersaw", envelope="pad",
|
||||
"sky", instrument="synth_pad",
|
||||
volume=0.18, pan=0.0,
|
||||
detune=30, spread=1.0,
|
||||
reverb=0.6, reverb_decay=3.5,
|
||||
chorus=0.3, sidechain=0.5,
|
||||
)
|
||||
acid_l = score.part(
|
||||
"acid_l", synth="saw", envelope="pad",
|
||||
"acid_l", instrument="acid_bass",
|
||||
volume=0.35, pan=-0.7,
|
||||
legato=True, glide=0.025,
|
||||
distortion=0.9, distortion_drive=12.0,
|
||||
@@ -875,7 +840,7 @@ def neon_grid():
|
||||
acid_l.lfo("lowpass", rate=0.5, min=400, max=3000, bars=8, shape="sine")
|
||||
|
||||
acid_r = score.part(
|
||||
"acid_r", synth="saw", envelope="pad",
|
||||
"acid_r", instrument="acid_bass",
|
||||
volume=0.3, pan=0.7,
|
||||
legato=True, glide=0.02,
|
||||
distortion=0.85, distortion_drive=10.0,
|
||||
@@ -885,7 +850,7 @@ def neon_grid():
|
||||
acid_r.lfo("lowpass", rate=0.33, min=500, max=2500, bars=8, shape="triangle")
|
||||
|
||||
sub = score.part(
|
||||
"sub", synth="sine", envelope="pluck",
|
||||
"sub", instrument="808_bass",
|
||||
volume=0.55, pan=0.0,
|
||||
lowpass=160, sidechain=0.85, sidechain_release=0.08,
|
||||
)
|
||||
@@ -896,12 +861,12 @@ def neon_grid():
|
||||
detune=15, spread=0.7,
|
||||
)
|
||||
bell_l = score.part(
|
||||
"bell_l", synth="fm", envelope="bell",
|
||||
"bell_l", instrument="vibraphone",
|
||||
volume=0.1, pan=-1.0,
|
||||
reverb=0.7, reverb_decay=3.0,
|
||||
)
|
||||
bell_r = score.part(
|
||||
"bell_r", synth="fm", envelope="bell",
|
||||
"bell_r", instrument="vibraphone",
|
||||
volume=0.1, pan=1.0,
|
||||
reverb=0.7, reverb_decay=3.0,
|
||||
delay=0.2, delay_time=0.8, delay_feedback=0.4,
|
||||
@@ -1026,14 +991,14 @@ def dance_party():
|
||||
score._drum_hits.append(_Hit(DrumSound.KICK, bar * 4.0 + beat, 120))
|
||||
score._drum_pattern_beats = max(score._drum_pattern_beats, 32.0)
|
||||
|
||||
bass = score.part("bass", synth="square", envelope="pluck",
|
||||
bass = score.part("bass", instrument="synth_bass",
|
||||
volume=0.45, lowpass=500, lowpass_q=1.3,
|
||||
sidechain=0.75, sidechain_release=0.12)
|
||||
sparkle = score.part("sparkle", synth="fm", envelope="bell",
|
||||
sparkle = score.part("sparkle", instrument="vibraphone",
|
||||
volume=0.3, pan=0.4, reverb=0.3, reverb_decay=1.5,
|
||||
delay=0.2, delay_time=0.234, delay_feedback=0.3)
|
||||
chords_part = score.part("chords", synth="supersaw", envelope="pad",
|
||||
volume=0.2, detune=12, spread=0.7,
|
||||
chords_part = score.part("chords", instrument="synth_pad",
|
||||
volume=0.2,
|
||||
reverb=0.4, reverb_type="plate", sidechain=0.7)
|
||||
fun = score.part("fun", synth="square", envelope="staccato",
|
||||
volume=0.2, pan=-0.5, delay=0.15, delay_time=0.117,
|
||||
@@ -1080,14 +1045,13 @@ def temple_bell():
|
||||
score = Score("4/4", bpm=65)
|
||||
score.drums("bolero", repeats=8)
|
||||
|
||||
koto = score.part("koto", synth="triangle", envelope="pluck",
|
||||
koto = score.part("koto", instrument="koto",
|
||||
volume=0.45, pan=0.2,
|
||||
reverb=0.5, reverb_type="taj_mahal",
|
||||
humanize=0.3)
|
||||
reverb=0.5, reverb_type="taj_mahal")
|
||||
drone = score.part("drone", synth="sine", envelope="pad",
|
||||
volume=0.15, reverb=0.6, reverb_type="taj_mahal",
|
||||
chorus=0.15, chorus_rate=0.2)
|
||||
bell = score.part("bell", synth="fm", envelope="bell",
|
||||
bell = score.part("bell", instrument="vibraphone",
|
||||
volume=0.1, pan=-0.6,
|
||||
reverb=0.8, reverb_type="taj_mahal")
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pytheory"
|
||||
version = "0.34.0"
|
||||
version = "0.34.1"
|
||||
description = "Music Theory for Humans"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""PyTheory: Music Theory for Humans."""
|
||||
|
||||
__version__ = "0.34.0"
|
||||
__version__ = "0.34.1"
|
||||
|
||||
from .tones import Tone, Interval
|
||||
from .systems import System, SYSTEMS, TET
|
||||
|
||||
+34
-7
@@ -442,7 +442,7 @@ def flute_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
|
||||
# Vibrato — develops after ~200ms
|
||||
vib_onset = numpy.clip(t / 0.2, 0.0, 1.0)
|
||||
vib = hz * 0.003 * vib_onset * numpy.sin(2 * numpy.pi * 5.0 * t)
|
||||
vib = hz * 0.0015 * vib_onset * numpy.sin(2 * numpy.pi * 5.0 * t)
|
||||
|
||||
# Tube resonance — mostly fundamental + weak odd harmonics
|
||||
wave = numpy.sin(2 * numpy.pi * (hz + vib) * t) * 0.7
|
||||
@@ -481,7 +481,7 @@ def trumpet_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
|
||||
# Vibrato
|
||||
vib_onset = numpy.clip(t / 0.15, 0.0, 1.0)
|
||||
vib = hz * 0.004 * vib_onset * numpy.sin(2 * numpy.pi * 5.5 * t)
|
||||
vib = hz * 0.002 * vib_onset * numpy.sin(2 * numpy.pi * 5.5 * t)
|
||||
|
||||
# Lip buzz — additive with brass spectral shape
|
||||
# Trumpet has strong even AND odd harmonics (unlike clarinet)
|
||||
@@ -592,7 +592,7 @@ def oboe_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
|
||||
vib_onset = numpy.clip(t / 0.2, 0.0, 1.0)
|
||||
vib = hz * 0.004 * vib_onset * numpy.sin(2 * numpy.pi * 5.0 * t)
|
||||
vib = hz * 0.002 * vib_onset * numpy.sin(2 * numpy.pi * 5.0 * t)
|
||||
|
||||
wave = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
n_harmonics = min(18, int((SAMPLE_RATE / 2) / hz))
|
||||
@@ -668,7 +668,7 @@ def cello_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
|
||||
# Delayed vibrato
|
||||
vib_rate = 5.0 + rng.uniform(-0.3, 0.3)
|
||||
vib_depth = hz * 0.004
|
||||
vib_depth = hz * 0.002
|
||||
vib_onset = numpy.clip(t / 0.25, 0.0, 1.0)
|
||||
vibrato = vib_depth * vib_onset * numpy.sin(2 * numpy.pi * vib_rate * t)
|
||||
|
||||
@@ -3246,9 +3246,36 @@ def _render_notes_to_buf(notes, buf, samples_per_beat, total_samples,
|
||||
if analog > 0:
|
||||
pitches = [hz * (2 ** (_rnd.gauss(0, analog * 5) / 1200))
|
||||
for hz in pitches]
|
||||
# Render oscillators (pass synth_kwargs for FM etc.)
|
||||
waves = [synth_fn(hz, n_samples=n_samples, **_skw)
|
||||
for hz in pitches]
|
||||
# Pitch bend: render with time-varying frequency
|
||||
bend_amt = getattr(note, 'bend', 0.0)
|
||||
if bend_amt != 0:
|
||||
bend_type = getattr(note, 'bend_type', 'smooth')
|
||||
t_bend = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
t_norm = numpy.clip(t_bend / (dur_ms / 1000), 0.0, 1.0)
|
||||
|
||||
waves = []
|
||||
for hz in pitches:
|
||||
hz_end = hz * (2 ** (bend_amt / 12))
|
||||
if bend_type == 'smooth':
|
||||
# Log interpolation — perceptually linear pitch
|
||||
freq_curve = hz * (hz_end / hz) ** t_norm
|
||||
elif bend_type == 'linear':
|
||||
freq_curve = hz + (hz_end - hz) * t_norm
|
||||
elif bend_type == 'late':
|
||||
# Hold pitch for 60%, bend in last 40%
|
||||
late_t = numpy.clip((t_norm - 0.6) / 0.4, 0.0, 1.0)
|
||||
freq_curve = hz * (hz_end / hz) ** late_t
|
||||
else:
|
||||
freq_curve = hz * (hz_end / hz) ** t_norm
|
||||
|
||||
# Phase accumulation for smooth frequency change
|
||||
phase = numpy.cumsum(2 * numpy.pi * freq_curve / SAMPLE_RATE)
|
||||
bent = numpy.sin(phase).astype(numpy.float64)
|
||||
waves.append((bent * SAMPLE_PEAK).astype(numpy.int16))
|
||||
else:
|
||||
# Render oscillators (pass synth_kwargs for FM etc.)
|
||||
waves = [synth_fn(hz, n_samples=n_samples, **_skw)
|
||||
for hz in pitches]
|
||||
# Sub-oscillator: octave-below sine
|
||||
if sub_osc > 0:
|
||||
for hz in pitches:
|
||||
|
||||
+16
-3
@@ -320,11 +320,18 @@ class TimeSignature:
|
||||
|
||||
@dataclass
|
||||
class Note:
|
||||
"""A pairing of a sound (Tone, Chord, or None for rest) with a duration."""
|
||||
"""A pairing of a sound (Tone, Chord, or None for rest) with a duration.
|
||||
|
||||
The optional ``bend`` field specifies a pitch bend in semitones
|
||||
applied over the note's duration. Positive = bend up, negative = down.
|
||||
For example, ``bend=2`` bends the note up a whole step by the end.
|
||||
"""
|
||||
|
||||
tone: object
|
||||
duration: Duration
|
||||
velocity: int = 100
|
||||
bend: float = 0.0
|
||||
bend_type: str = "smooth" # "smooth" (log), "linear", "late"
|
||||
|
||||
@property
|
||||
def beats(self) -> float:
|
||||
@@ -2051,11 +2058,15 @@ class Part:
|
||||
self._drum_pattern_beats: float = 0.0
|
||||
self._automation: list[tuple[float, dict]] = [] # (beat, {param: value})
|
||||
|
||||
def add(self, tone_or_string, duration=Duration.QUARTER, *, velocity: int = 100) -> "Part":
|
||||
def add(self, tone_or_string, duration=Duration.QUARTER, *, velocity: int = 100,
|
||||
bend: float = 0.0, bend_type: str = "smooth") -> "Part":
|
||||
"""Add a note. Accepts Tone/Chord objects or note strings like ``"E5"``.
|
||||
|
||||
Duration can be a ``Duration`` enum or a raw float (beats).
|
||||
Velocity controls loudness (1-127, default 100).
|
||||
Bend specifies a pitch bend in semitones over the note duration
|
||||
(e.g. ``bend=2`` bends up a whole step, ``bend=-1`` bends down
|
||||
a half step). Used for guitar bends, sitar meends, slides.
|
||||
|
||||
Returns self for chaining.
|
||||
"""
|
||||
@@ -2064,7 +2075,9 @@ class Part:
|
||||
tone_or_string = Tone.from_string(tone_or_string, system=self._system)
|
||||
if isinstance(duration, (int, float)):
|
||||
duration = _RawDuration(duration)
|
||||
self.notes.append(Note(tone=tone_or_string, duration=duration, velocity=velocity))
|
||||
self.notes.append(Note(tone=tone_or_string, duration=duration,
|
||||
velocity=velocity, bend=bend,
|
||||
bend_type=bend_type))
|
||||
return self
|
||||
|
||||
def set(self, **params) -> "Part":
|
||||
|
||||
@@ -6711,3 +6711,204 @@ def test_interval_to_non12():
|
||||
a5 = a.add(19)
|
||||
result = a.interval_to(a5)
|
||||
assert "octave" in result
|
||||
|
||||
|
||||
# ── Dedicated instrument synths ──────────────────────────────────────────────
|
||||
|
||||
def test_all_dedicated_synths_render():
|
||||
"""Every dedicated synth waveform produces valid audio."""
|
||||
from pytheory.play import (piano_wave, bass_guitar_wave, flute_wave,
|
||||
trumpet_wave, clarinet_wave, oboe_wave,
|
||||
marimba_wave, harpsichord_wave, cello_wave,
|
||||
harp_wave, upright_bass_wave,
|
||||
acoustic_guitar_wave, electric_guitar_wave,
|
||||
sitar_wave, SAMPLE_RATE)
|
||||
synths = [piano_wave, bass_guitar_wave, flute_wave, trumpet_wave,
|
||||
clarinet_wave, oboe_wave, marimba_wave, harpsichord_wave,
|
||||
cello_wave, harp_wave, upright_bass_wave,
|
||||
acoustic_guitar_wave, electric_guitar_wave, sitar_wave]
|
||||
for fn in synths:
|
||||
wave = fn(440, n_samples=11025)
|
||||
assert len(wave) == 11025
|
||||
assert wave.dtype == numpy.int16
|
||||
assert numpy.abs(wave).max() > 0
|
||||
|
||||
|
||||
def test_piano_brightness_scales():
|
||||
"""High-pitched piano should be brighter (more high harmonics)."""
|
||||
from pytheory.play import piano_wave
|
||||
low = piano_wave(130, n_samples=22050) # C3
|
||||
high = piano_wave(1047, n_samples=22050) # C6
|
||||
# Both should produce valid audio
|
||||
assert numpy.abs(low).max() > 0
|
||||
assert numpy.abs(high).max() > 0
|
||||
|
||||
|
||||
def test_acoustic_guitar_body_resonance():
|
||||
"""Acoustic guitar should produce richer spectrum than raw pluck."""
|
||||
from pytheory.play import acoustic_guitar_wave, pluck_wave
|
||||
ag = acoustic_guitar_wave(220, n_samples=22050)
|
||||
pk = pluck_wave(220, n_samples=22050)
|
||||
assert len(ag) == len(pk) == 22050
|
||||
|
||||
|
||||
def test_cello_has_vibrato():
|
||||
"""Cello synth should produce pitch variation (vibrato)."""
|
||||
from pytheory.play import cello_wave
|
||||
wave = cello_wave(220, n_samples=44100)
|
||||
assert len(wave) == 44100
|
||||
assert numpy.abs(wave).max() > 0
|
||||
|
||||
|
||||
# ── Cabinet simulation ───────────────────────────────────────────────────────
|
||||
|
||||
def test_cabinet_reduces_highs():
|
||||
"""Cabinet sim should reduce high-frequency content."""
|
||||
from pytheory.play import _apply_cabinet
|
||||
# White noise has flat spectrum
|
||||
noise = numpy.random.uniform(-1, 1, 44100).astype(numpy.float32)
|
||||
cabbed = _apply_cabinet(noise, brightness=0.5)
|
||||
# RMS of cabbed should be lower (energy removed by filters)
|
||||
assert numpy.sqrt(numpy.mean(cabbed ** 2)) < numpy.sqrt(numpy.mean(noise ** 2))
|
||||
|
||||
|
||||
def test_cabinet_brightness_param():
|
||||
"""Higher brightness = more high-frequency content passes through."""
|
||||
from pytheory.play import _apply_cabinet
|
||||
noise = numpy.random.uniform(-1, 1, 44100).astype(numpy.float32)
|
||||
dark = _apply_cabinet(noise, brightness=0.0)
|
||||
bright = _apply_cabinet(noise, brightness=1.0)
|
||||
# Bright should have more energy than dark
|
||||
assert numpy.sqrt(numpy.mean(bright ** 2)) > numpy.sqrt(numpy.mean(dark ** 2))
|
||||
|
||||
|
||||
# ── Analog drift ─────────────────────────────────────────────────────────────
|
||||
|
||||
def test_analog_drift_varies_pitch():
|
||||
"""Analog drift should make repeated renders slightly different."""
|
||||
from pytheory import Score, Duration
|
||||
score1 = Score("4/4", bpm=120)
|
||||
p1 = score1.part("t", synth="saw", analog=0.5)
|
||||
p1.add("C4", Duration.QUARTER)
|
||||
p1.add("C4", Duration.QUARTER)
|
||||
# With analog > 0, each C4 gets a random pitch offset
|
||||
# This is hard to test deterministically, just verify it renders
|
||||
from pytheory.play import render_score
|
||||
buf = render_score(score1)
|
||||
assert len(buf) > 0
|
||||
|
||||
|
||||
# ── Guitar strumming ─────────────────────────────────────────────────────────
|
||||
|
||||
def test_strum_requires_fretboard():
|
||||
"""Strumming without a fretboard should raise ValueError."""
|
||||
from pytheory import Score, Duration
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("g", synth="saw")
|
||||
with pytest.raises(ValueError, match="fretboard"):
|
||||
p.strum("Am", Duration.QUARTER)
|
||||
|
||||
|
||||
def test_strum_adds_notes():
|
||||
"""Strumming should add notes to the part."""
|
||||
from pytheory import Score, Duration, Fretboard
|
||||
score = Score("4/4", bpm=120)
|
||||
fb = Fretboard.guitar()
|
||||
p = score.part("g", instrument="acoustic_guitar", fretboard=fb)
|
||||
p.strum("Am", Duration.HALF)
|
||||
assert len(p.notes) > 0
|
||||
|
||||
|
||||
def test_strum_direction():
|
||||
"""Both down and up strums should work."""
|
||||
from pytheory import Score, Duration, Fretboard
|
||||
score = Score("4/4", bpm=120)
|
||||
fb = Fretboard.guitar()
|
||||
p = score.part("g", instrument="acoustic_guitar", fretboard=fb)
|
||||
p.strum("G", Duration.QUARTER, direction="down")
|
||||
p.strum("G", Duration.QUARTER, direction="up")
|
||||
assert len(p.notes) == 2
|
||||
|
||||
|
||||
# ── World drums ──────────────────────────────────────────────────────────────
|
||||
|
||||
def test_tabla_sounds_render():
|
||||
"""All tabla drum sounds should produce valid audio."""
|
||||
from pytheory.play import _render_drum_hit
|
||||
from pytheory.rhythm import DrumSound
|
||||
for sound in [DrumSound.TABLA_NA, DrumSound.TABLA_TIN, DrumSound.TABLA_GE,
|
||||
DrumSound.TABLA_DHA, DrumSound.TABLA_TIT, DrumSound.TABLA_KE]:
|
||||
wave = _render_drum_hit(sound.value, 22050)
|
||||
assert len(wave) == 22050
|
||||
assert wave.dtype == numpy.float32
|
||||
|
||||
|
||||
def test_dhol_sounds_render():
|
||||
from pytheory.play import _render_drum_hit
|
||||
from pytheory.rhythm import DrumSound
|
||||
for sound in [DrumSound.DHOL_DAGGA, DrumSound.DHOL_TILLI, DrumSound.DHOL_BOTH]:
|
||||
wave = _render_drum_hit(sound.value, 22050)
|
||||
assert len(wave) == 22050
|
||||
|
||||
|
||||
def test_mridangam_sounds_render():
|
||||
from pytheory.play import _render_drum_hit
|
||||
from pytheory.rhythm import DrumSound
|
||||
for sound in [DrumSound.MRIDANGAM_THAM, DrumSound.MRIDANGAM_NAM,
|
||||
DrumSound.MRIDANGAM_DIN, DrumSound.MRIDANGAM_THA]:
|
||||
wave = _render_drum_hit(sound.value, 22050)
|
||||
assert len(wave) == 22050
|
||||
|
||||
|
||||
def test_djembe_sounds_render():
|
||||
from pytheory.play import _render_drum_hit
|
||||
from pytheory.rhythm import DrumSound
|
||||
for sound in [DrumSound.DJEMBE_BASS, DrumSound.DJEMBE_TONE, DrumSound.DJEMBE_SLAP]:
|
||||
wave = _render_drum_hit(sound.value, 22050)
|
||||
assert len(wave) == 22050
|
||||
|
||||
|
||||
def test_metal_kit_sounds_render():
|
||||
from pytheory.play import _render_drum_hit
|
||||
from pytheory.rhythm import DrumSound
|
||||
for sound in [DrumSound.METAL_KICK, DrumSound.METAL_SNARE, DrumSound.METAL_HAT]:
|
||||
wave = _render_drum_hit(sound.value, 22050)
|
||||
assert len(wave) == 22050
|
||||
|
||||
|
||||
def test_tabla_pattern_presets():
|
||||
"""All tabla patterns should load without error."""
|
||||
from pytheory.rhythm import Pattern
|
||||
for name in ["teental", "jhaptaal", "rupak", "dadra",
|
||||
"keherwa", "tabla solo", "tiri kita"]:
|
||||
p = Pattern.preset(name)
|
||||
assert p.beats > 0
|
||||
|
||||
|
||||
def test_world_drum_pattern_presets():
|
||||
"""All world drum patterns should load."""
|
||||
from pytheory.rhythm import Pattern
|
||||
for name in ["bhangra", "dhol chaal", "qawwali", "dholak folk",
|
||||
"adi talam", "mridangam korvai", "djembe", "kuku", "soli",
|
||||
"double kick", "metal blast", "metal groove", "metal gallop"]:
|
||||
p = Pattern.preset(name)
|
||||
assert p.beats > 0
|
||||
|
||||
|
||||
# ── Guitar presets with cabinet sim ──────────────────────────────────────────
|
||||
|
||||
def test_guitar_presets_have_cabinet():
|
||||
"""Distorted guitar presets should have cabinet simulation."""
|
||||
from pytheory import Score
|
||||
for preset in ["distorted_guitar", "orange_crunch", "metal_guitar"]:
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("g", instrument=preset)
|
||||
assert p.cabinet > 0, f"{preset} should have cabinet sim"
|
||||
|
||||
|
||||
def test_clean_guitar_preset():
|
||||
from pytheory import Score
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("g", instrument="clean_guitar")
|
||||
assert p.synth == "electric_guitar_synth"
|
||||
assert p.cabinet > 0
|
||||
|
||||
Reference in New Issue
Block a user