v0.36.0: Banjo, mandolin, ukulele, cajón, vocal synth, granular

34 synth waveforms, 26 songs, vocal/formant synthesis with choir
preset, granular engine, banjo/mandolin/ukulele physical models,
cajón drum with 3 patterns, strum sweep on fretboard instruments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 19:54:08 -04:00
parent bf6deaab64
commit 70efb0ad40
7 changed files with 72 additions and 5 deletions
+19
View File
@@ -2,6 +2,25 @@
All notable changes to PyTheory are documented here.
## 0.36.0
- **Banjo synth** — steel strings on drum-head body, nasal twang,
fast decay with membrane resonance
- **Mandolin synth** — paired steel strings (natural chorus from
doubled courses), bright body resonance
- **Ukulele synth** — nylon strings, small mid-heavy body, shorter
sustain than guitar
- **Cajón drums** — bass (woody box thump), slap (snare wire buzz),
tap (ghost note). 3 patterns: cajon, cajon rumba, cajon folk
- **Vocal/formant synth** — LF glottal model, 5 Peterson & Barney
formant peaks, jitter/shimmer, consonant onsets, per-note lyrics.
Presets: vocal, choir
- **Granular synthesis** — grain cloud engine with scatter, pitch
variation, Hanning windows. Presets: granular_pad, granular_texture
- **Strum sweep** — subtle grace notes before chord hit for natural
strum feel on all fretboard instruments
- Mandola preset, 34 synth waveforms, 26 songs
## 0.35.0
- **8.5x faster import** — dropped pytuning/sympy, lazy-load scipy.
+1 -1
View File
@@ -77,7 +77,7 @@ 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** — 30 waveforms (including Karplus-Strong pluck, Hammond organ,
- **Synthesis** — 34 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
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "pytheory"
version = "0.35.1"
version = "0.36.0"
description = "Music Theory for Humans"
readme = "README.md"
license = "MIT"
+1 -1
View File
@@ -1,6 +1,6 @@
"""PyTheory: Music Theory for Humans."""
__version__ = "0.35.1"
__version__ = "0.36.0"
from .tones import Tone, Interval
from .systems import System, SYSTEMS, TET
+45 -1
View File
@@ -1130,6 +1130,48 @@ def granular_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE,
return (peak * out).astype(numpy.int16)
def banjo_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
"""Banjo — steel strings on a drum-head body.
The banjo's distinctive twang comes from the membrane head
(like a drum skin) instead of a wooden soundboard. This gives
a sharp attack, bright tone, and fast decay with a nasal,
metallic quality. The 5th string drone adds shimmer.
"""
period = int(SAMPLE_RATE / hz)
if period < 2:
period = 2
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
# Steel string — bright, sharp attack
buf = rng.uniform(-0.9, 0.9, period).astype(numpy.float64)
# Minimal filtering — banjo keeps the brightness
for k in range(period - 1):
buf[k] = 0.7 * buf[k] + 0.3 * buf[k + 1]
out = numpy.zeros(n_samples, dtype=numpy.float64)
for i in range(n_samples):
out[i] = buf[i % period]
next_idx = (i + 1) % period
# Moderate decay — drum head rings but shorter than guitar
buf[i % period] = 0.5 * (buf[i % period] + buf[next_idx]) * 0.9988
# Drum-head resonance — nasal, ringy, mid-frequency peaks
# The membrane head rings more than wood — that's the twang
import scipy.signal as _sig
for center, bw, gain in [(600, 200, 0.5), (1500, 300, 0.4), (3000, 500, 0.25)]:
lo = max(20, center - bw)
hi = min(SAMPLE_RATE // 2 - 1, center + bw)
if lo < hi:
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
out += _sig.lfilter(bp, ap, out) * gain
mx = numpy.abs(out).max()
if mx > 0:
out /= mx
return (peak * out).astype(numpy.int16)
def mandolin_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
"""Mandolin — paired steel strings, bright and ringing.
@@ -1523,6 +1565,7 @@ class Synth(Enum):
SAXOPHONE = "saxophone_synth"
GRANULAR = "granular_synth"
VOCAL = "vocal_synth"
BANJO = "banjo_synth"
MANDOLIN = "mandolin_synth"
UKULELE = "ukulele_synth"
ACOUSTIC_GUITAR = "acoustic_guitar_synth"
@@ -1548,7 +1591,8 @@ _SYNTH_FUNCTIONS = {
"harp_synth": harp_wave, "upright_bass_synth": upright_bass_wave,
"timpani_synth": timpani_wave, "saxophone_synth": saxophone_wave,
"granular_synth": granular_wave, "vocal_synth": vocal_wave,
"mandolin_synth": mandolin_wave, "ukulele_synth": ukulele_wave,
"banjo_synth": banjo_wave, "mandolin_synth": mandolin_wave,
"ukulele_synth": ukulele_wave,
"acoustic_guitar_synth": acoustic_guitar_wave,
"sitar_synth": sitar_wave, "electric_guitar_synth": electric_guitar_wave,
}
+4
View File
@@ -195,6 +195,10 @@ INSTRUMENTS = {
"detune": 12, "lowpass": 3000, "lowpass_q": 1.5,
"humanize": 0.2,
},
"banjo": {
"synth": "banjo_synth", "envelope": "none",
"humanize": 0.2,
},
"mandolin": {
"synth": "mandolin_synth", "envelope": "none",
"humanize": 0.2,
Generated
+1 -1
View File
@@ -698,7 +698,7 @@ wheels = [
[[package]]
name = "pytheory"
version = "0.35.1"
version = "0.36.0"
source = { editable = "." }
dependencies = [
{ name = "numeral" },