Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# Dump a Flanger render, one sample a line, for comparing two builds.
# micropython flanger_dump.py rails > rails.txt
import sys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# The Flanger's wet tap interpolates between two int16 taps:
#
# int32_t wet = s0 + (((s1 - s0) * (int32_t)delay_frac) >> 16);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# Guards for the carry: the stream must still end, a carry larger than a whole
# source buffer must not read out of one, and a Resampler's bound rate must
# still be the ratio it was asked for.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# What a SpeedChanger renders should not depend on how the node above it
# chunks its output. The phase accumulator counts source frames since the
# stream began, so a source buffer boundary is not frame zero.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# The consequence of the lost LSB: a SpeedChanger pair asked to cancel --
# decimate at N, restore at 1/N -- is not an identity when 1/N truncates.
# N = 1.0000000000000004 is what fs / rate_hz returns for a log-mapped rate
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

# A rate is stored in 16.16, so `rate` should read back within half a step of
# what was asked for. Truncating instead of rounding costs a whole step.
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

import synthio, audiocore
try:
from ulab import numpy as np
Expand Down
10 changes: 7 additions & 3 deletions lib/audiodsp_util/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""One number, the same number, on every target audiodsp runs on.

``import audiodsp_util`` -- a pure-Python tier with no dependencies beyond
Expand All @@ -22,18 +26,18 @@

`docs/how-it-works/correctness-standard.md` holds our nodes to every target rendering
them identically, so a setting derived in Python arithmetic passes through
:func:`float32` before it reaches a node. On a single-precision target that
`float32` before it reaches a node. On a single-precision target that
call is the identity. On a double one it rounds to the value the board would
have held. Both then agree, which is the whole point.

The same rule is what audiocomponents#75 needs on the class side: a class
computing ``360 * 4 ** (macro / 127)`` for a filter frequency is deriving a
setting in Python, and its board and its desktop land a ULP apart until that
derivation ends in :func:`float32`.
derivation ends in `float32`.

## And why printing needs its own function

:func:`float32_bits` is for output rather than settings. ``"%.6f" % value``
`float32_bits` is for output rather than settings. ``"%.6f" % value``
is not one string across interpreters even when ``value`` is bit-for-bit the
same float32: six decimals of a single-precision number is seven significant
digits, and MicroPython's single-precision formatter is not correctly
Expand Down
12 changes: 8 additions & 4 deletions lib/audiorender/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Render a whole composition offline, through audiodsp's own DSP.

This is the tier above :mod:`audioinstruments` and :mod:`audioeffects`: it
This is the tier above `audioinstruments` and `audioeffects`: it
knows what a *piece* is - tracks, a tempo map, notes, automation, sections
- and turns one into a mixed stereo master plus the level report that
master is judged by. It is deliberately offline and CPython-shaped (numpy
Expand Down Expand Up @@ -33,9 +37,9 @@

Loading a track's sound is the caller's job, not this package's - a
plug-in host has its own script loader and its own reasons to keep it. Pass
:func:`render` a ``voice_for`` that returns a :class:`~.voices.Voice`
around an :mod:`audioinstruments` instrument, or anything else that speaks
the small protocol in :mod:`.voices`.
`render` a ``voice_for`` that returns a `Voice <.voices.Voice>`
around an `audioinstruments` instrument, or anything else that speaks
the small protocol in `.voices`.

from audiorender import Clock, Voice, render, report, write_wav

Expand Down
4 changes: 4 additions & 0 deletions lib/audiorender/events.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Turn a track's notes and automation into a time-ordered event list.

Events are ``(sample_position, kind, data, value)`` where `kind` is one of
Expand Down
4 changes: 4 additions & 0 deletions lib/audiorender/metrics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""What a finished render is worth looking at.

Levels rather than waveforms: which track carries which section, whether
Expand Down
10 changes: 7 additions & 3 deletions lib/audiorender/render.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""The block render loop: a composition in, a mixed master out.

Rendering happens a block at a time, and every block does the same three
Expand Down Expand Up @@ -44,10 +48,10 @@ def render_track(track, composition, tempo, voice, total_frames,
patch=None, effects=(), clock=None, block=BLOCK):
"""One track's audio as float32 ``(total_frames, 2)`` in -1.0..1.0.

`voice` is anything implementing the protocol in :mod:`.voices`, and
`voice` is anything implementing the protocol in `.voices`, and
`effects` are callables taking the rendered track as int16 bytes and
returning something to pull the processed track from - a rack built
from :mod:`audioeffects`, or a host's own effect runner.
from `audioeffects`, or a host's own effect runner.
"""
events = build_events(track, composition, tempo, patch=patch, block=block)
data = np.zeros((total_frames, 2), dtype=np.float32)
Expand Down Expand Up @@ -112,7 +116,7 @@ def render(composition, voice_for, patch_for=None, effects_for=None,
a sound is its own business:

``voice_for(track, clock)``
the voice to render this track with (see :mod:`.voices`).
the voice to render this track with (see `.voices`).
``patch_for(track)``
``{macro index: normalized value}`` for the macros the composition
does not set - the instrument's own patch, not the middle of every
Expand Down
6 changes: 5 additions & 1 deletion lib/audiorender/tempo.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Beats to seconds and back, through a piecewise-constant tempo map.

A tempo map is a list of rows, each ``(beat, bpm)`` or
Expand Down Expand Up @@ -37,7 +41,7 @@ def beats_to_seconds(self, beat):
return seconds

def beat_at_sample(self, sample):
"""The inverse of :meth:`beats_to_seconds`, piecewise exact."""
"""The inverse of `beats_to_seconds`, piecewise exact."""
seconds = sample / self.sample_rate
elapsed = 0.0
for index, (start, bpm) in enumerate(self._spans):
Expand Down
10 changes: 7 additions & 3 deletions lib/audiorender/voices.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""What a render pulls audio from, and how it tells them the time.

A *voice* is anything a render can drive: it takes the events
:mod:`audiorender.events` builds and hands back frames.
`audiorender.events` builds and hands back frames.

deliver(event, sample_position)
pull_frames(frames) -> interleaved little-endian stereo int16

:class:`Voice` is that protocol over an :mod:`audioinstruments` instrument,
`Voice` is that protocol over an `audioinstruments` instrument,
which is what a caller wants unless it has its own way of loading a sound -
a plug-in host driving its own script runner, say. Keeping the protocol
this small is what lets such a host reuse the render loop without reusing
Expand Down Expand Up @@ -45,7 +49,7 @@ def pull_frames(self, frames):


class Voice(Puller):
"""One :mod:`audioinstruments` instrument, ready to be rendered."""
"""One `audioinstruments` instrument, ready to be rendered."""

def __init__(self, instrument):
Puller.__init__(self, instrument.output)
Expand Down
4 changes: 4 additions & 0 deletions lib/audiorender/wav.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Writing a render out as a 16-bit stereo WAV."""

import wave
Expand Down
4 changes: 4 additions & 0 deletions manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""audiodsp's manifest: it names the C module and freezes no Python.

A build includes this file from its own manifest (see the README), so
Expand Down
4 changes: 4 additions & 0 deletions scripts/test_testpypi_install.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Smoke a fresh omnipython-audiodsp install from TestPyPI.

scripts/test_testpypi_install.py [VERSION] [--skip-render]
Expand Down
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

import subprocess
import sys
from pathlib import Path
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiobiquad.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A biquad and a first-order all-pass cascade that reach exact zero.

Not a CircuitPython module, and not from micropython-vst3's engine either --
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audioconvolve.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Convolution: an impulse response applied properly, rather than imitated.

Not a CircuitPython module, and not from micropython-vst3's engine either --
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiocore.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""CircuitPython-compatible audio sample sources for CPython."""

import wave
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiodelays.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""CircuitPython-compatible streaming delay effects."""

from array import array
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiodynamics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Envelope-follower dynamics processing: compression, limiting, downward
expansion, gating and transient shaping.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audioecho.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A delay with a filter, a soft-clip and a cross-feed inside its feedback
loop.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiofeatures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Speech features for a wake word model, from PCM at any rate.

audiodsp's own, not a CircuitPython module. ``Frontend`` is TensorFlow Lite
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiofilters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""CircuitPython-compatible streaming audio filters."""

from array import array
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiofreeverb.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""CircuitPython-compatible Freeverb streaming effect."""

from array import array
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiograph.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""The graph runner: a whole patch rendered by one C call per block.

audiodsp's own, not a CircuitPython module. The runner is
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audioladder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A transistor ladder filter: four one-pole stages round a feedback loop
with an odd saturator inside the loop.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiomath.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Arithmetic on audio streams: one multiplied by another, and one divided
down in frequency.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiometer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Band levels for a spectrum meter: N log-spaced bands between two
frequencies, plus peak and RMS, one byte each in half-dB steps.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiomixer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""CircuitPython-compatible PCM mixer."""

from audiocore import (
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiomodal.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A bank of resonators, which is what a struck object is.

Hit a drum head, a marimba bar, a bell or a wine glass and it rings as a sum
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiooscillator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A band-limited oscillator whose pitch, FM, pulse width and sync are audio
streams, read sample by sample.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiopump.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""The audio pump, for CPython.

The same module the MicroPython and CircuitPython builds carry, with the same
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audioroute.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Route audio: fan one stream out to parallel branches, or move it between
the channels.

Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audiosampler.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Play recorded notes: N voices, each reading a buffer at its own pitch.

`audiomixer.Mixer` plays sources at their own rate and `synthio` plays
Expand Down
4 changes: 4 additions & 0 deletions src/cpython/audioshaper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""A table-driven waveshaper that does its shaping above the sample rate.

Not a CircuitPython module, and not from micropython-vst3's engine either --
Expand Down
Loading
Loading