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
47 changes: 19 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,37 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- uses: actions/checkout@v4

- name: Install system audio libraries
run: |
sudo apt-get update
sudo apt-get install -y libportaudio2

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest coverage

- name: Lint with flake8
run: |
pip install flake8
flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 src/ --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Sync dependencies (locked toolchain)
run: uv sync --all-extras --python ${{ matrix.python-version }}

- name: Lint with ruff
run: uv run ruff check src tests

- name: Type check with mypy
run: uv run mypy src/audx

- name: Test with pytest + coverage
run: |
pip install mypy
mypy --ignore-missing-imports

- name: Test with pytest
run: |
coverage run -m pytest -q
coverage report --fail-under=25
coverage xml
uv run coverage run -m pytest -q
uv run coverage report --fail-under=40
uv run coverage xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: false
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

# Publish to PyPI when a version tag is pushed (e.g. v0.3.0).
# Uses PyPI Trusted Publishing (OIDC) — no API token stored in the repo.
# One-time setup: on PyPI, add a trusted publisher for this repo + workflow.
# https://docs.pypi.org/trusted-publishers/

on:
push:
tags:
- "v*"

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Build sdist + wheel
run: uv build
- name: Check metadata
run: uvx twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write # required for trusted publishing
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,12 @@ __pycache__/
.coverage
.coverage.*
htmlcov/
coverage.xml
dist/
build/
*.egg-info/

# marketing / remotion
marketing/remotion/node_modules/
marketing/remotion/out/
marketing/remotion/.remotion/
87 changes: 87 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Changelog

All notable changes to audx are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.3.0] - 2026-06-21

### Added
- **Live MIDI jam** (`audx jam`): play the built-in synth kit in real time from a
MIDI controller or Push 2 — hit a pad, hear a sound instantly. Drums mode maps
General-MIDI notes to drum voices (and never leaves a pad silent); `--chromatic`
plays a melodic voice across the keys. New `audx/live.py` mapping module + tests,
and a guide at [docs/playing-live.md](docs/playing-live.md).
- **Push 2 pad LEDs**: audx now lights the Push 2 pads — `audx jam` auto-detects a
Push 2, paints the drum kit (one colour per voice) and flashes each pad as it's
struck. `audx push2 lights` lights the kit on its own for a quick check.
Implemented to Ableton's Push 2 MIDI spec (SysEx colour palette + note-on LEDs).
- Bundle the `python-rtmidi` MIDI backend so `midi`/`jam`/Push 2 work out of the box.
- **Built-in synth kit** (`audx/synth.py`): 20 pure-numpy procedurally-synthesised
voices with friendly aliases, per-voice tuning, velocity scaling and
deterministic (seeded) output.
- 14 drum/perc voices: `kick`, `sub`/`808`, `snare`, `clap`, `snap`, `hh`, `oh`,
`rim`, `tom`, `cowbell`, `perc`, `ride`, `crash`, `shaker`.
- 6 **melodic** voices: `bass`, `pluck`, `stab`, `keys` (alias `ep`), `saw`,
`sine` — pitched (band-limited sawtooth) and transposed via `tune`, so audx
does basslines, plucks and chord stabs, e.g. `bass e(5,16) | tune -7st`.
(`bass` is now its own voice, no longer an alias of `sub`.)
- **Song arrangements** (`audx/arrangement.py` `Song`/`Section`/`render_song`) plus
`audx song render <spec.json>` and `audx song info <spec.json>`: build
multi-section tracks (intro/verse/drop/outro) from a JSON spec and render them
to a single WAV.
- **Live finger-drum pads** in the TUI: keys `w e r a s d f z x c` (and `u i o`)
trigger built-in synth voices on dedicated channels while a project is open.
- **Marketing assets**: an animated terminal demo GIF in the README
(`scripts/make-demo-gif.py`, Pillow-only) and a runnable Remotion promo video
project under `marketing/remotion/`.
- **Automated PyPI releases**: `.github/workflows/release.yml` publishes on a
`v*` tag via PyPI Trusted Publishing (OIDC, no stored token).
- **Zero-config sound**: the offline renderer falls back to the synth kit when a
sample of the same name isn't found, so `audx render "kick 4/4"` makes sound
with no sample library. `--sample` is now optional.
- `audx demo [out.wav]` — render a full multi-track beat with the synth kit in
one command. The flagship "try it in 10 seconds" experience.
- `audx synths` — list every built-in voice and its aliases.
- `SynthVoice` in the live engine, plus synth fallback in the real-time callback,
so the TUI makes sound without any samples loaded.

### Changed
- **PortAudio is now optional for offline use.** `sounddevice` is imported lazily,
so the CLI and all offline features (render, export, demo, diff, project ops)
work on machines without the PortAudio system library. Only live real-time
playback requires it, with a clear install hint when missing.
- Type checking now covers the **entire package** (was 4 files) and passes clean.

### Fixed
- `audx mix set <ch> gain -3` no longer errors on the negative dB value — the
command accepts dash-leading values as documented.

### Quality
- Test suite expanded from 30 to 120+ tests; coverage raised from ~28% to ~43%.
CI coverage gate raised to 40%.

## [0.2.0]

### Added
- Terminal-native DAW core: pattern DSL, fixed-grid scheduler, 16-channel audio
engine with mute/gain/level meters, Textual TUI with mixer strips and tap tempo.
- Pattern DSL: `4/4`, `16x8`, Euclidean `e(k,n,rot)`, explicit `[1.0.1.0]` grids
and `x---` grids, with `vel`, `channel`, `swing`, `humanize`, `chance`, `gain`,
`pan` and `tune` pipe modifiers.
- Sample indexing/search, `.audx` JSON project save/load, offline WAV + stems
rendering, project diffing, cheap forking, hot-reload `watch`, read-only `serve`
dashboard.
- MIDI export to Standard MIDI File, MIDI clock-out, MIDI input recording.
- Pattern slots (A/B/C/D), vim-style macro registers, OS-keychain AI key store.
- Optional AI extras (librosa): text-to-pattern, sample similarity, auto-tagging,
groove extraction.
- Honest scaffolds: plugin discovery, Push 2 MIDI map, Heartmula bridge,
sadact-finisher bridge, minimal local `audxd` daemon, latency calibration.
- `audx doctor` diagnostics and CI across Python 3.10–3.12.

[Unreleased]: https://github.com/totalaudiopromo/audx/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/totalaudiopromo/audx/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/totalaudiopromo/audx/releases/tag/v0.2.0
66 changes: 66 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Contributing to audx

Thanks for hacking on audx. It's a terminal-native DAW that should feel like an
instrument, so the bar is high on both correctness and feel.

## Setup

```bash
uv sync # install runtime + dev deps into .venv
uv run audx demo loop.wav # smoke test: should render a beat with no setup
```

For live real-time audio (the TUI / `audx play`) you also need PortAudio:

```bash
brew install portaudio # macOS
sudo apt install libportaudio2 # Debian/Ubuntu
```

Offline work (rendering, MIDI export, the synth kit, project ops) needs none of
this — it's pure Python + numpy.

## Quality bar

Every change must keep these green:

```bash
uv run pytest -q # tests
uv run ruff check src tests # lint + import order
uv run mypy src/audx # types (whole package, must stay clean)
```

- **Tests are required** for new behaviour. Offline features should be testable
without an audio device — follow the `typer.testing.CliRunner` pattern in
`tests/test_cli.py` and the synth/render tests.
- **Types matter.** mypy checks the entire `src/audx` package. `warn_return_any`
is intentionally off (numpy stubs return `Any`); everything else is strict.
- **No silent failures.** Unfinished features should print an honest "not wired
up yet" message, never pretend or crash.

## How the pieces fit

- `pattern.py` — the DSL parser + deterministic step scheduler. A `Pattern`
parses its `dsl` string into `Step`s (instrument, beat, velocity, channel, …).
- `synth.py` — the built-in synth kit. Each voice is a pure-numpy function that
returns a mono `float32` one-shot. Add a voice by writing a `_name(sr, rng)`
renderer, registering it in `_RENDERERS`/`SYNTH_VOICES`, and adding aliases.
- `arrangement.py` — offline rendering. `render_arrangement` resolves each step
to audio via `_voice_audio`: a real sample if the library has one, otherwise
the synth kit, otherwise skipped.
- `engine.py` — the real-time engine. `sounddevice` is imported lazily inside
`start()`; never import it at module top.
- `cli.py` — the typer command surface. Keep help text accurate to behaviour.

## Pull requests

- Branch from `main`, keep PRs focused, write a clear description.
- Make sure `pytest`, `ruff` and `mypy` pass locally before pushing.
- Update `CHANGELOG.md` (`[Unreleased]`) and the docs when behaviour changes.

## Philosophy

> Code is the controller. Sound is the canvas. Terminal is the dimension.

If a change makes audx feel more like an instrument and less like a program,
you're on the right track.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Chris Schofield

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
31 changes: 31 additions & 0 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,37 @@ Both versions must keep version numbers in sync.

---

## Automated PyPI release (recommended)

Releases are published to PyPI by `.github/workflows/release.yml` using
**PyPI Trusted Publishing (OIDC)** — no API token is stored in the repo.

**One-time setup** (on PyPI, by the project owner):
1. Create the `audx` project on PyPI (or run the first release to TestPyPI).
2. Add a *trusted publisher*: PyPI → project → Settings → Publishing →
add GitHub, owner `totalaudiopromo`, repo `audx`, workflow `release.yml`,
environment `pypi`. See https://docs.pypi.org/trusted-publishers/.
3. In GitHub repo settings, create an environment named `pypi`.

**Each release:**
```bash
# 1. bump version in pyproject.toml AND package.json (keep them in sync)
# 2. update CHANGELOG.md [Unreleased] -> the new version
git commit -am "release: audx v0.3.0"
git tag v0.3.0
git push origin main --tags # the tag triggers the Release workflow
```
The workflow builds the sdist + wheel with `uv build`, runs `twine check`, and
publishes to PyPI. Verify a clean install afterwards: `pip install audx`.

To build/inspect locally without publishing:
```bash
uv build # -> dist/audx-<version>.tar.gz + .whl
uvx twine check dist/*
```

---

## Prerequisites

### npm
Expand Down
Loading
Loading