Skip to content
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 11 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Contributing to audioinstruments

Every instrument here is a patch on `vrack`, a virtual Eurorack modular
synthesizer in Python, built on [audiodsp](https://github.com/PyDevices/audiodsp). Twenty modules inspired by
classic instruments live in `lib/vrack/modules/`, their DSP in `lib/vrack/dsp/`, the
rack and its kernels in `lib/vrack/core/`. These are the rules of the repo.
Every instrument here is a patch on the rack in `lib/audioinstruments/_rack/`,
a virtual Eurorack modular synthesizer in Python, built on
[audiodsp](https://github.com/PyDevices/audiodsp). Twenty modules inspired by
classic instruments live in `_rack/modules/`, their DSP in `_rack/dsp/`, the
rack and its kernels in `_rack/core/`. These are the rules of the repo.

## Run the tests

Expand Down Expand Up @@ -72,9 +73,10 @@ control (sync, linear FM, PWM, cutoff under a fast envelope), a model
audiodsp lacks, or a feedback loop inside the module.
[docs/audiodsp.md](docs/audiodsp.md) lists what is on which side and why.
Do not write a pure-Python copy of a node audiodsp has. Nothing under
`lib/vrack` imports `audioinstruments` or `audioeffects`: vrack depends on
audiodsp alone. The `audioinstruments` package in this repo is the public
face over vrack, so it imports vrack and never the other way round.
`lib/audioinstruments/_rack` imports the faces or `audioeffects`: the rack
depends on audiodsp alone. The faces, `lib/audioinstruments/<name>.py`, are
the public side over the rack, so they import the rack and never the other way
round.

The face is generated. Each `lib/audioinstruments/<name>.py` is written by
`tools/make_faces.py` from the instrument's class, so change `MACRO_LABELS`,
Expand Down Expand Up @@ -109,7 +111,7 @@ the bend, for a glide. It is all in

An instrument in `adapter.ALL` follows the Poly106
([docs/instruments.md](docs/instruments.md)): a family of modules, a voice
patch with its presets in `lib/vrack/instruments/`, a built-in effect as a
patch with its presets in `lib/audioinstruments/_rack/instruments/`, a built-in effect as a
core in `dsp/`, a spec in `tools/traits.py` and a trait file, and tests on
both interpreters. Its sound is built from the original's controls; there is
nothing to match.
Expand Down Expand Up @@ -141,4 +143,4 @@ format: bump `PATCH_FORMAT_VERSION` once a release has carried the old name.

Ideas and planned work: `ROADMAP.md`, three lines at most each. Bugs and
things a user should be able to do but cannot: issues. Docs are for people
using vrack: second person, short paragraphs, say where a number came from.
using the rack: second person, short paragraphs, say where a number came from.
6 changes: 1 addition & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ audioinstruments is a set of playable instruments, each a patch of modules
on a virtual rack, rendered a block at a time on audiodsp.
Planned work, in rough order:

- Every instrument by name: `audioinstruments.create("poly106", 48000)`, with
each instrument's macros and patches readable from its module.
- The rack moves to `audioinstruments._rack` before 0.5.0. Instruments are the
public surface; the rack's modules and jacks stay free to change.
- The MicroPython tests in CI, on a unix build with audiodsp and ulab; today
they are run by hand before a merge.
- The docs in the shared five sections: Get started, Guides, Reference, How it
Expand Down Expand Up @@ -45,7 +41,7 @@ Planned work, in rough order:
- An arpeggiator. A note source in front of a `VoicePool` that holds the keys you play
and steps through them as a pattern (up, down, random, over several octaves), so any
polyphonic instrument can arpeggiate; a voice patch has no hook on incoming notes today.
- A tempo that follows the host's transport in `vrack.adapter`.
- A tempo that follows the host's transport in `audioinstruments._rack.adapter`.
- The trait tool for fixed-sound instruments: render a SoundFont preset
through the sampler with the same phrase and measure it the same way, so an
electric piano or a drum kit can be set beside a reference.
Expand Down
2 changes: 1 addition & 1 deletion docs/allocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ frames, and `tests/mpy/test_bridge.py` pulls an audiodsp node under

These cores loop per sample in Python, because no vector form exists for a
recursion with a nonlinearity inside it and audiodsp has no node that does
the job at vrack's block size (see [audiodsp.md](audiodsp.md)):
the job at the rack's block size (see [audiodsp.md](audiodsp.md)):

- `dsp/oscillator.py`: the wave blender, and the PolyBLEP VCO when audiodsp
has no `audiooscillator` (with it, the VCO is a node);
Expand Down
10 changes: 5 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# How vrack is put together
# How the rack is put together

## Layout

lib/vrack/
lib/audioinstruments/_rack/
core/ the rack, jacks and cables, the signal kernels, the audiodsp bridge,
and poly.py: the voice allocator and the voice pool
adapter.py create() and ALL: a polyphonic rack, or a SoundFont, as an audio component
Expand Down Expand Up @@ -66,7 +66,7 @@ may write into; the rack resizes jacks and scratch buffers together when
its block size changes. `set_macro`/`get_macro` address parameters by index
through `MACRO_LABELS`. `get_state`/`set_state` carry the parameters, and
`Rack.save_patch`/`load_patch` the whole patch as a dict.
`Rack.save_patch_file(path)` writes that dict to a JSON file, and `Rack.load_patch_file(path)` or `vrack.load_rack(path)` reads it back and installs the modules it names.
`Rack.save_patch_file(path)` writes that dict to a JSON file, and `Rack.load_patch_file(path)` or `audioinstruments._rack.load_rack(path)` reads it back and installs the modules it names.
A patch stores its format version (`PATCH_FORMAT_VERSION`, now 2). Version 2
renamed every module, so a version 1 patch is refused with a message that
says so; rebuild it with the current names.
Expand All @@ -81,8 +81,8 @@ large panels use named parameters throughout.
On a build with `audiograph` (CPython from audiodsp 0.6.9.dev4, and the
MicroPython and CircuitPython builds), a rack can be compiled into one C
graph and rendered by one call a block, which is what holds real time on an
ESP32-P4. `vrack.core.graph.compile_rack(rack)` does it, and
`vrack.adapter.create()` does it for an instrument whenever every module in
ESP32-P4. `audioinstruments._rack.core.graph.compile_rack(rack)` does it, and
`audioinstruments._rack.adapter.create()` does it for an instrument whenever every module in
the patch can take part. A module takes part by declaring its **fragment**:
the nodes it is made of, how they hang on its jacks, and how its parameters
reach them.
Expand Down
20 changes: 10 additions & 10 deletions docs/audiodsp.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# vrack and audiodsp
# The rack and audiodsp

vrack depends on [audiodsp](https://github.com/PyDevices/audiodsp), the
the rack depends on [audiodsp](https://github.com/PyDevices/audiodsp), the
PyDevices port of CircuitPython's audio nodes plus its own, and on nothing
else beyond numpy or ulab. This page says what vrack runs on audiodsp, what
else beyond numpy or ulab. This page says what the rack runs on audiodsp, what
it keeps in Python and why, and what it would still like audiodsp to have.
Everything here was checked against audiodsp 0.6.7 on CPython 3.12 and on
the unix build of MicroPython 1.29 with the same modules compiled in.

## How the two fit together

audiodsp nodes pull signed 16-bit PCM from a source and hand out a block of
their own. vrack's jacks carry blocks of floats, because control voltage,
their own. The rack's jacks carry blocks of floats, because control voltage,
gates and audio share one scale and get added and multiplied freely. The
`NodeBridge` in `core/bridge.py` joins the two: a looping
`audiocore.RawSample` over an int16 array the size of one vrack block, a
`audiocore.RawSample` over an int16 array the size of one block, a
passthrough `audiofilters.Filter` so no node sees the RawSample's
end-of-sample flag, then the nodes. Per block it converts the float input
in, pulls one block from the last node and converts back. With every node's
Expand All @@ -24,7 +24,7 @@ pull goes through `audiopump.pull` and `drain` and allocates nothing.
Two things decide what can live on audiodsp:

- **Block size.** The CircuitPython-ported effects take a `buffer_size`, so
they run at vrack's 64 frames, and so do `audioladder.Ladder` and
they run at the rack's 64 frames, and so do `audioladder.Ladder` and
`audiooscillator.Oscillator` from audiodsp 0.6.8. synthio and the rest of
audiodsp's own nodes (`audioshaper`, `audioverb`, `audiomodal`,
`audiomath`, `audioecho`) always hand out 256 frames. `ChunkedBridge` runs those by
Expand All @@ -37,7 +37,7 @@ Two things decide what can live on audiodsp:

## What runs on audiodsp

| vrack core | audiodsp | Used by |
| Rack core | audiodsp | Used by |
| --- | --- | --- |
| `OscillatorCore`, the PolyBLEP VCO (saw, pulse, triangle, sine) | `audiooscillator.Oscillator` through `OscillatorBridge`: pitch, linear FM, PWM and sync go in as audio and are read per sample; one stereo node gives two waveforms from one phase | Patch2600, Modular100 VCO, Modular55 VCO, StateVariableMono, Duo20, WestCoastSynth, PinMatrix3, MonoOne, HybridMono, Semi32, DrumSynth, PolyrhythmSynth, Acid303 |
| `LadderCore` (transistor, clean, Curtis, diode) | `audioladder.Ladder` at the rack's block size; the cutoff CV goes into the node's cutoff input and is read per sample (a node without that input, audiodsp 0.6.8 and earlier, moves the cutoff once a block); the clean preset's high-pass is a biquad in front; `render(poles=2)` takes the output two stages in, a 12 dB slope that still self-oscillates | Semi32, DrumSynth, PolyrhythmSynth, Modular55 low-pass, Patch2600, Modular100, PinMatrix3, MonoOne |
Expand All @@ -53,7 +53,7 @@ Two things decide what can live on audiodsp:

## What stays in Python, and why

| vrack core | Why not a node |
| Rack core | Why not a node |
| --- | --- |
| The wave blender (`dsp/oscillator.py`) | Its morph between shapes has no node. |
| The acid filter (`dsp/ladder.py`) | Its feedback is high-passed inside the loop, which `audioladder.Ladder` has no place for. |
Expand All @@ -66,7 +66,7 @@ Two things decide what can live on audiodsp:
| Granular engine | `audiodelays.GranularPitchShift` shifts live input; it has no buffer position, size or freeze. |
| MacroOscillator's other six models, the WestCoast0 triangle core | Per-sample phase arithmetic (FM, folding, formants, a kick's pitch drop). |

## What vrack still lacks in audiodsp
## What the rack still lacks in audiodsp

The ladder at the caller's buffer size and the band-limited oscillator were
the first two asks; both are in audiodsp 0.6.8. With an older audiodsp, or a
Expand Down Expand Up @@ -101,4 +101,4 @@ imported: its West Coast instrument drives a vactrol-style gate from a synthio
envelope and a one-shot LFO; its chorus and vibrato model a clocked
bucket-brigade line, which is where `BBDDelayCore`'s use of `Echo`'s
`freq_shift` comes from; `acoustickit` is a modal synthesizer like ModalResonator's
model 0. vrack depends on audiodsp alone.
model 0. The rack depends on audiodsp alone.
Loading
Loading