From 40901d603d4b69528d03ac66b87136dc7254b48b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 31 Mar 2026 07:08:10 -0400 Subject: [PATCH] =?UTF-8?q?Multi-stage=20distortion:=20preamp,=20power=20a?= =?UTF-8?q?mp,=20asymmetric=20clipping=20=E2=80=94=20v0.40.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single tanh was too mild. Now chains preamp gain → power amp clip → asymmetric rectifier sag for proper overdrive/fuzz character. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- pytheory/__init__.py | 2 +- pytheory/play.py | 14 +++++++++++++- uv.lock | 2 +- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc716ae..387cb88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to PyTheory are documented here. +## 0.40.4 + +- **Distortion overhaul** — multi-stage clipping (preamp → power amp → + asymmetric rectifier) replaces single-stage tanh. Crunch, distorted, + orange crunch, and metal guitar presets now sound properly driven. + ## 0.40.3 - **Crotales synth** — tuned bronze discs with long ring and bright harmonics diff --git a/pyproject.toml b/pyproject.toml index a609b81..eed1d60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pytheory" -version = "0.40.3" +version = "0.40.4" description = "Music Theory for Humans" readme = "README.md" license = "MIT" diff --git a/pytheory/__init__.py b/pytheory/__init__.py index edca49c..6e7486c 100644 --- a/pytheory/__init__.py +++ b/pytheory/__init__.py @@ -1,6 +1,6 @@ """PyTheory: Music Theory for Humans.""" -__version__ = "0.40.3" +__version__ = "0.40.4" from .tones import Tone, Interval from .systems import System, SYSTEMS, TET diff --git a/pytheory/play.py b/pytheory/play.py index 4a29f5c..1e07853 100644 --- a/pytheory/play.py +++ b/pytheory/play.py @@ -4902,7 +4902,19 @@ def _apply_distortion(samples, drive=1.0, mix=1.0): """ if mix <= 0 or drive <= 0: return samples - driven = numpy.tanh(samples * drive) + # Multi-stage gain + clipping like a real amp: + # Stage 1: preamp gain — push the signal hard + stage1 = numpy.tanh(samples * drive) + # Stage 2: power amp — clip again with more gain for sustain and grit + stage2 = numpy.tanh(stage1 * drive * 0.5) + # Stage 3: at high drive, add asymmetric clipping (tube rectifier sag) + if drive > 3.0: + # Positive peaks clip harder than negative — asymmetric harmonics + driven = numpy.where(stage2 > 0, + numpy.tanh(stage2 * 1.5), + numpy.tanh(stage2 * 1.2)) + else: + driven = stage2 return samples * (1 - mix) + driven * mix diff --git a/uv.lock b/uv.lock index b493bb2..c3ce338 100644 --- a/uv.lock +++ b/uv.lock @@ -690,7 +690,7 @@ wheels = [ [[package]] name = "pytheory" -version = "0.40.3" +version = "0.40.4" source = { editable = "." } dependencies = [ { name = "rich" },