From 938635ecc4e817cde5acb508135f88008cd60298 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 08:45:23 +0000 Subject: [PATCH] Make audx share-ready: live demo, honest docs, working genre jam Ship a public Vercel demo, fix install/docs overclaims that would embarrass visitors, and wire jam --genre to generate_track so the quickstart actually changes the music. Co-authored-by: Chris Schofield --- .github/workflows/pages.yml | 2 +- .vercel/project.json | 1 + CHANGELOG.md | 6 +- PUBLISHING.md | 143 +++++++-------------------- README.md | 133 ++++++++++++------------- SECURITY.md | 9 +- docs/getting-started.md | 37 ++++--- docs/handoff-next-session.md | 67 ++++--------- docs/playing-live.md | 53 +++++----- marketing/remotion/src/AudxPromo.tsx | 2 +- scripts/make-demo-gif.py | 2 +- site/index.html | 24 ++--- site/play.html | 2 +- site/studio.html | 2 +- src/audx/cli.py | 35 ++++++- tests/test_cli_spec_commands.py | 10 ++ 16 files changed, 237 insertions(+), 291 deletions(-) create mode 100644 .vercel/project.json diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 0bfe45a..d0b66c8 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,7 +2,7 @@ name: Deploy landing page # Publishes the static site/ to GitHub Pages on every push to main that touches it. # One-time setup: repo Settings → Pages → Source = "GitHub Actions". -# URL: https://totalaudiopromo.github.io/audx/ +# URL: https://chrisschouk.github.io/audx/ (enable Pages → Source = GitHub Actions) on: push: diff --git a/.vercel/project.json b/.vercel/project.json new file mode 100644 index 0000000..04814d0 --- /dev/null +++ b/.vercel/project.json @@ -0,0 +1 @@ +{"orgId":"team_bZYVJlMKYUB7W0IH0bwvmrIG","projectId":"prj_V4NomFFqLRTFZcQpupgkrZYjsVmp"} diff --git a/CHANGELOG.md b/CHANGELOG.md index f424a80..c6d4529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 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 +[Unreleased]: https://github.com/chrisschouk/audx/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/chrisschouk/audx/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/chrisschouk/audx/releases/tag/v0.2.0 diff --git a/PUBLISHING.md b/PUBLISHING.md index 2d9cef4..f6f10ef 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -1,36 +1,51 @@ # Publishing audx -## Overview -audx uses a dual distribution model: +## Status (honest) + +**Current package version: `0.3.0`** (see `pyproject.toml` / root `package.json`). + +**Not published to PyPI under the name `audx`.** That name on PyPI is already taken by an +unrelated audio converter. Until a free name (or transfer) is secured, install from GitHub: + +```bash +python -m pip install "git+https://github.com/chrisschouk/audx.git" +``` + +The root npm package is `"private": true` — dual npm+PyPI publish is aspirational, not live. + +--- + +## Overview (when ready to publish) + +Intended dual distribution: - **Python package** (via pip/uv) — primary distribution for the CLI -- **npm package** — namespace reservation + potential JS wrapper +- **npm package** — optional scoped name + JS wrapper Both versions must keep version numbers in sync. --- -## Automated PyPI release (recommended) +## Automated PyPI release (when name is available) -Releases are published to PyPI by `.github/workflows/release.yml` using +Releases are published 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). +1. Create a project on PyPI under an available name (not the occupied `audx` converter). 2. Add a *trusted publisher*: PyPI → project → Settings → Publishing → - add GitHub, owner `totalaudiopromo`, repo `audx`, workflow `release.yml`, + add GitHub, owner `chrisschouk`, repo `audx`, workflow `release.yml`, environment `pypi`. See https://docs.pypi.org/trusted-publishers/. 3. In GitHub repo settings, create an environment named `pypi`. +4. Update `pyproject.toml` / docs install lines to match the chosen name. **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 commit -am "release: audx v0.3.1" +git tag v0.3.1 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 @@ -40,118 +55,28 @@ uvx twine check dist/* --- -## Prerequisites - -### npm -```bash -# Create npm account (one-time) -npm adduser - -# Verify login -npm whoami -``` - -### Python build tools -```bash -# Using uv (recommended) -uv pip install build twine - -# Or standard pip -python -m pip install build twine --upgrade -``` - ---- - -## Release Workflow - -### Option A: One-step full release (recommended) -```bash -cd ~/workspace/active/audx -bash scripts/release.sh -``` -This script will: -1. Verify Python + npm version match -2. Build Python wheel + sdist -3. Commit with git tag -4. Publish to npm -5. Push to GitHub (you'll need to manually upload to PyPI unless you modify script) - -### Option B: Step-by-step - -**1. Bump version** (edit both files to same version): -- `pyproject.toml`: `version = "0.1.1"` -- `package.json`: `"version": "0.1.1"` - -**2. Build Python distributions:** -```bash -bash scripts/build.sh -# Artifacts appear in dist/ -``` - -**3. Publish to npm:** -```bash -bash scripts/publish-npm.sh -# Confirms version, requires manual 'yes' input -``` - -**4. Upload to PyPI (optional):** -```bash -# TestPyPI first -twine upload --repository testpypi dist/* - -# Then production -twine upload dist/* -``` - -**5. Git tag & push:** -```bash -git add -A -git commit -m "release: audx v0.1.1" -git tag "v0.1.1" -git push origin main --tags -``` - ---- - -## Post-Publish Checklist - -- [ ] npm page shows correct version: https://www.npmjs.com/package/audx -- [ ] PyPI page (if published): https://pypi.org/project/audx/ -- [ ] GitHub release draft created with changelog -- [ ] Homebrew formula updated (if distributing via brew) -- [ ] Installation instructions tested: - ```bash - npm install -g audx # if JS wrapper exists - uv pip install audx # Python - ``` - ---- - ## Version Strategy - **Major (X.0.0):** Breaking changes to CLI API - **Minor (0.X.0):** New features, backwards-compatible - **Patch (0.0.X):** Bug fixes, documentation updates -Current: `0.1.0` — initial functional release +Current: `0.3.0` — terminal DAW + browser studio; not on PyPI yet. --- ## Troubleshooting -### npm E404: package name taken -If `audx` gets taken before you publish: -- Use scoped name: `@totalaudiopromo/audx` -- Update `package.json`: `"name": "@totalaudiopromo/audx"` +### PyPI name taken +Do **not** advertise `pip install audx` until this project owns that name. +Use a scoped/alternate name, or keep the git install path. + +### npm package name +If publishing JS: use a scoped name such as `@chrisschouk/audx` and set +`package.json` `"private": false` only when ready. ### Python build fails ```bash -# Clean and retry rm -rf dist/ build/ audx.egg-info/ uv run python -m build --clean ``` - -### Permission errors on scripts -```bash -chmod +x scripts/*.sh -``` diff --git a/README.md b/README.md index 6ed9bd2..8299fe8 100644 --- a/README.md +++ b/README.md @@ -4,109 +4,105 @@ A terminal-native digital audio workstation for pattern sequencing, live-coded sample playback, and Ableton session export. Built for a calm, local, hackable workflow with zero cloud dependency. -## ⚡ 10-Second Quickstart +**v0.3 experimental** — core music tools work; some advanced CLI surfaces are still experimental. + +[Try the browser studio](https://audx-five.vercel.app) · [Type a pattern](https://audx-five.vercel.app/play.html) · [GitHub](https://github.com/chrisschouk/audx) + +![audx demo](docs/assets/audx-demo.gif) + + + +## Try it in the browser (no install) + +Open **[audx studio](https://audx-five.vercel.app/studio.html)** — build a groove with the real synth kit, export WAV/stems, or share a link. Web MIDI / Push 2 works best in Chromium. + +Or open the **[pattern playground](https://audx-five.vercel.app/play.html)** and type DSL like `kick 4/4`. + +--- + +## Terminal quickstart ```bash -# 1. Install audx in a virtual environment python3 -m venv .venv source .venv/bin/activate -python -m pip install audx - -# Native audio drivers -brew install portaudio # macOS -# or: sudo apt install libportaudio2 # Linux +python -m pip install "git+https://github.com/chrisschouk/audx.git" -# 2. Sanity check & hard drive sample scan +# Offline proof — no samples, no audio hardware required audx doctor -audx samples scan - -# 3. Jam instantly! -audx jam --genre techno +audx demo loop.wav ``` ---- +> **Note:** `pip install audx` on PyPI is a *different* unrelated package. Install from this GitHub repo as shown above until a dedicated PyPI name ships. -## 🎛 Primary User Journey +### Live jam (needs PortAudio + optional MIDI) ```bash -audx doctor # Diagnostics -audx samples scan # Auto-scan hard drive for audio samples -audx jam --genre house # Instant live jam on the spot -audx open my-track # Open terminal DAW TUI -audx open my-track --web # Open Web companion on http://localhost:8080/app -audx export als my-track/project.audx # Export to native Ableton Live Set (.als) -audx song render my-track/project.audx # Render project to WAV +# Native audio drivers +brew install portaudio # macOS +# or: sudo apt install libportaudio2 # Linux + +audx samples scan # optional: index local samples +audx jam # drum pads / Push 2 +audx jam --genre techno # pads + looping techno patterns @ 128 BPM +audx open my-track # terminal DAW TUI +audx open my-track --serve # TUI + read-only live dashboard on :8080 +audx export als my-track/project.audx +audx song render my-track/project.audx ``` --- -## 💻 Commands +## Commands that work today ```bash -audx doctor # Run diagnostics (PortAudio, MIDI, CLI stack) -audx jam [--genre techno|house|hiphop] # Live interactive jam session (with synth audio fallbacks) +audx doctor # Diagnostics (PortAudio, MIDI, CLI stack) +audx demo [out.wav] # Render a synth-kit demo beat (offline) +audx jam [--genre techno|house|hiphop|ukg|ambient] + # Live MIDI jam; --genre loads a pattern pack audx jam --chromatic # Pitched chromatic synth keyboard mode -audx samples scan # Auto-scan ~/Music, ~/Downloads, ~/Samples for audio files -audx init # Scaffold project folder (stems/, renders/, git init) -audx open [project] [--web] # Open terminal TUI or Web browser companion -audx push2 lights # Test Push 2 pad LED lighting matrix grid +audx samples scan # Auto-scan ~/Music, ~/Downloads, ~/Samples +audx init # Scaffold project folder +audx open [project] [--serve] # Terminal TUI; optional read-only dashboard +audx serve --port 8080 # Read-only live monitor dashboard +audx push2 lights # Test Push 2 pad LED grid audx push2 map # Print Push 2 MIDI mapping scaffold -audx midi list # List MIDI input and output ports -audx midi out "Push 2" # Send 24 PPQN MIDI clock sync +audx midi list # List MIDI ports +audx midi out "Push 2" # Send 24 PPQN MIDI clock audx midi rec --bars 1 # Record incoming MIDI as a pattern -audx export als project.audx # Export project to native Ableton Live Set (.als) -audx export midi out.mid # Export patterns to Standard MIDI File -audx song render project.audx # Render a saved project to WAV -audx render-project project.audx # Alias for song render +audx export als project.audx # Export Ableton Live Set (.als) +audx export midi out.mid # Export Standard MIDI File +audx song render project.audx # Render project to WAV audx pattern create "" # Parse/check a pattern audx pattern set "" # Replace a channel's DSL line -audx pattern step [on|off] # Toggle/set one step in a channel grid audx pattern list # List patterns in current process -audx load sample.wav --ch 0 --project project.audx - # Copy audio into stems/ and bind to channel -audx track add "" -c 2 # Add a track to the engine -audx track rm # Remove a track -audx mix set gain # Set channel gain -audx mix set mute on|off # Set channel mute -audx mute # Toggle channel mute +audx synths # List built-in synth voices audx stems search 909 kick # Fuzzy-search the sample index audx diff a.audx b.audx # Human-readable project diff -audx finish project.audx --profile ukg # Render + master via sadact-finisher audx fork project new-name # Cheap branching -audx save beat.audx # Save current in-process state -audx load beat.audx # Load and print project state -audx projects list # List saved project files +audx save beat.audx / audx load beat.audx +audx projects list audx watch project.audx # Hot-reload .audx on save -audx serve --port 8080 # Monitor dashboard + /app playable browser UI -audx version # Print version +audx version ``` +Experimental / optional bridges (need extra local services or are incomplete): `audx finish`, plugin scan, voice, AI extras. See source for details — not part of the happy path yet. + --- -## 🛠 Troubleshooting +## Troubleshooting ### `zsh: command not found: pip` -Use `python3 -m pip` or create a virtual environment: -```bash -python3 -m venv .venv -source .venv/bin/activate -python -m pip install audx -``` -Or install globally via `pipx` / `uv`: -```bash -pipx install audx -# or: uv tool install audx -``` +Use `python3 -m pip` or a venv (see quickstart above). ### PEP 668 `externally-managed-environment` -Homebrew Python and macOS protect system packages. Always install inside a venv (`python3 -m venv .venv && source .venv/bin/activate`). +Always install inside a venv on Homebrew/macOS Python. -### `ModuleNotFoundError: No module named 'click'` -Re-install inside a fresh venv: `python -m pip install --force-reinstall audx`. +### Real-time audio fails +Install PortAudio (`brew install portaudio` / `apt install libportaudio2`). Offline `audx demo` and render still work without it. --- -## 🎹 Pattern DSL +## Pattern DSL ```bash audx pattern create kick "kick 4/4" # four on the floor @@ -119,19 +115,20 @@ audx pattern create clap "clap [1.0.1.0.1.1.0.0]" # explicit grid --- -## 🛠 Development +## Development ```bash make dev uv run pytest -q uv run ruff check src tests -uv run mypy src/audx # clean across the whole package +uv run mypy src/audx +cd web && npm ci && npm test && npm run build ``` --- -## 💡 Philosophy +## Philosophy > Code is the controller. Sound is the canvas. Terminal is the dimension. -Can Chris open a terminal, hit play, and feel like he is controlling a musical instrument rather than debugging Python? Yes. +Local-first. Hackable. No cloud lock-in. MIT licensed. diff --git a/SECURITY.md b/SECURITY.md index d4366d8..8e8ad46 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,13 +16,16 @@ Include: - reproduction steps - whether local files, network access, or credentials are involved -## Local File Access +## Local Network Surfaces -The browser UI served by `audx serve` can read local audio/project files through -localhost endpoints. Only run it on trusted machines and keep the default +`audx serve` (and `audx open --serve`) hosts a **read-only** live monitor on +localhost by default. It exposes session state for convenience — keep the default localhost binding unless you intentionally want another device on your network to connect. +The static browser studio (`site/studio.html`) runs entirely in the browser and +does not read your local filesystem via the CLI server. + ## AI and Network Features AI-related features are opt-in and require user-provided credentials. API keys diff --git a/docs/getting-started.md b/docs/getting-started.md index 7f44631..771ca48 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,26 +2,26 @@ `audx` is a terminal-native DAW for pattern sequencing, stem mixing, live-coded sample playback, and Ableton session export. +**Prefer no install?** Open the [browser studio](https://audx-five.vercel.app/studio.html) or [pattern playground](https://audx-five.vercel.app/play.html). + ## Installation -### Recommended: Python Virtual Environment +Install from GitHub (PyPI's `audx` package is a different unrelated project): + ```bash python3 -m venv .venv source .venv/bin/activate -python -m pip install audx +python -m pip install "git+https://github.com/chrisschouk/audx.git" ``` -### Alternative: Global Tool Install -```bash -pipx install audx -# or: uv tool install audx -``` +### Native Audio Drivers (for live playback) -### Native Audio Drivers `audx` uses PortAudio for low-latency audio callbacks: - **macOS**: `brew install portaudio` - **Linux**: `sudo apt install libportaudio2` +Offline commands (`audx demo`, render, export) work without PortAudio. + --- ## 5-Minute Quickstart @@ -30,20 +30,23 @@ pipx install audx # 1. Run diagnostics audx doctor -# 2. Auto-scan hard drive for audio samples +# 2. Offline proof — render a synth-kit demo beat +audx demo loop.wav + +# 3. Optional: index local samples audx samples scan -# 3. Scaffold a new project +# 4. Scaffold a new project audx init my-beat -# 4. Jam live on the spot +# 5. Jam live (pads / Push 2); --genre loads a looping pattern pack audx jam --genre techno -# 5. Open project in terminal TUI or Web browser +# 6. Open the terminal TUI (optional read-only dashboard) audx open my-beat -audx open my-beat --web +audx open my-beat --serve -# 6. Render WAV or export to Ableton Live Set (.als) +# 7. Render WAV or export to Ableton Live Set (.als) audx song render my-beat/project.audx --output my-beat/renders/master.wav audx export als my-beat/project.audx -o my-beat/session.als ``` @@ -55,11 +58,13 @@ audx export als my-beat/project.audx -o my-beat/session.als | Command | Description | |---|---| | `audx doctor` | Run system diagnostics & check PortAudio/MIDI setup | +| `audx demo` | Render a synth-kit demo beat (offline, no hardware) | | `audx jam` | Start interactive live jam session | -| `audx jam --genre ` | Auto-generate track (`techno`, `house`, `hiphop`, `ukg`, `ambient`) | +| `audx jam --genre ` | Load genre pattern pack (`techno`, `house`, `hiphop`, `ukg`, `ambient`) | | `audx samples scan` | Auto-scan hard drive for `.wav`/`.flac`/`.mp3` audio files | | `audx open [project]` | Open terminal TUI on project file or directory | -| `audx open --web` | Serve local Web Audio app on `http://localhost:8080/app` | +| `audx open --serve` | Also host the read-only live dashboard on port 8080 | +| `audx serve` | Read-only live monitor dashboard | | `audx song render ` | Render project to WAV | | `audx export als ` | Export project to native Ableton Live Set (`.als`) | | `audx push2 lights` | Test Push 2 LED pad lighting matrix | diff --git a/docs/handoff-next-session.md b/docs/handoff-next-session.md index 44b0ed7..ade657f 100644 --- a/docs/handoff-next-session.md +++ b/docs/handoff-next-session.md @@ -1,30 +1,24 @@ -# Handoff — next session +# Internal handoff notes (not a public getting-started guide) +# +# Prefer README.md, docs/getting-started.md, and the hosted studio for visitors. +# Kept for maintainers who need historical context on the web/studio work. -_Last updated: 2026-06-21. Branch: `claude/audx-team-launch-wyajz5` is the configured -dev branch, but **all of this session's work lives on `claude/audx-web-and-serve` (PR #5)** -to keep that PR coherent. Latest commit: `ef30c66`._ +# Handoff — next session (historical) + +_Last updated: 2026-09-04 (share-readiness). Original session notes from 2026-06-21._ ## TL;DR -A full **browser version of audx** ("audx studio") was built this session on top of the -existing CLI, plus `audx serve` and a GitHub Pages landing site. Everything is verified -locally (108 web tests, full Python suite, ruff/mypy, `tsc`, esbuild build). **Two things -are blocked on the user, not on code.** +A full **browser version of audx** ("audx studio") was built on top of the +existing CLI, plus `audx serve` and a landing site under `site/`. + +**Public demo:** deploy `site/` via Vercel (see `vercel.json`) or enable GitHub Pages +(Settings → Pages → Source = GitHub Actions). Repo: `chrisschouk/audx`. -## ⚠️ Blockers for the user (do these first) -1. **CI is not triggering.** Despite "Actions sorted", pushes to `claude/audx-web-and-serve` - produce **zero** workflow runs (last repo run was 10:52Z on `main`, before PR #5 existed; - my 20:28Z push triggered nothing). `ci.yml` fires on `pull_request` (needs a push) and - `push` to `main`. Evidence points to an **Actions usage/billing cap** (CI ran fine for - `claude/audx-team-launch-wyajz5` in the morning, then stopped ~10:52). - → Check **Settings → Billing → Actions minutes / spending limit**, not just the on/off - toggle. If not billing, **close + reopen PR #5** to force a fresh `pull_request` event. - Until CI runs, the `web` + Python CI jobs have never validated on a clean checkout — but - they pass locally. -2. **GitHub Pages**: enable once at **Settings → Pages → Source = "GitHub Actions"** to - publish `site/` → https://totalaudiopromo.github.io/audx/ (studio at `/studio.html`, - DSL playground at `/play.html`). +## Historical blockers (may be stale) +1. **CI / Actions billing** — if workflows stop firing, check spending limits. +2. **GitHub Pages** — enable Actions as the Pages source if using that path. -## What shipped (all on PR #5) +## What shipped - `audx serve` live dashboard, and `audx open --serve` to host it from the TUI. - Pages landing site (`site/`) + Pyodide DSL playground (`site/play.html`). - **audx studio** (`site/studio.html` + `web/` TypeScript, bundled to `site/studio.js`): @@ -32,34 +26,9 @@ are blocked on the user, not on code.** share links + WAV + **stems** export, **your-own-samples** (IndexedDB), **Web MIDI + Push 2 LEDs**, and **CLI-interop songs** with **live song playback**. -## Verification model (important context) +## Verification model The `web/` code mirrors Python and is **golden-vector tested** against it: fixtures are generated by `scripts/gen_web_fixtures.py` → `web/fixtures/*.json`, guarded by -`tests/test_web_fixtures.py` (Python) and the CI `web` job. Parity covered: DSL parser, -all 20 synth voices, Push 2 LED bytes, song timeline. -**Not verifiable here (no browser):** visual look/feel, live audio timing, `decodeAudioData`, -IndexedDB, File drag-drop, Web MIDI. These need a human in a browser → **ask the user to -open `studio.html` and report** spacing/timing/levels. - -## Web app file map (`web/src/`) -- `dsl.ts` — pattern DSL parser (port of `pattern.py`). -- `synth.ts` — 20 voices (port of `synth.py`). -- `types.ts` — Track/ProjectState, pan/velocity helpers. -- `project.ts` — v3 URL/share + localStorage encoding (sample ref/name only). -- `render.ts` — offline render: `renderProject` / `renderStems` / `renderSong`. -- `samples.ts` — IndexedDB sample store + decode (CLI precedence: sample > synth). -- `zip.ts` — store-only ZIP (stems bundle). -- `song.ts` — Scene/Song, `timeline`, `songStepPlan`, CLI Song JSON interop. -- `push2.ts` / `midi.ts` — Push 2 LED bytes + Web MIDI glue. -- `studio.ts` — the app (scheduler, grid UI, transport, song panel). -Build: `cd web && npm ci && npm run build` (esbuild → `../site/studio.js`). -Test: `npm test` (vitest, 108) + `npx tsc --noEmit`. Python: `uv run pytest -q`. - -## Suggested next work (optional, not started) -- Euclidean quick-fill per track (export `euclideanGrid` from `dsl.ts`), per-track - copy/clear/shift, computer-keyboard finger-drumming. -- Once CI is green: confirm the `web` job passes on a clean checkout (npm ci/lockfile, - node setup, the `git diff --exit-code web/fixtures` drift gate). -- Consider merging PR #5 once CI is green and the user has eyeballed the studio. +`tests/test_web_fixtures.py` (Python) and vitest in `web/`. Plan doc with full scope/parity detail: `docs/web-version-plan.md`. diff --git a/docs/playing-live.md b/docs/playing-live.md index 0e01883..e697d51 100644 --- a/docs/playing-live.md +++ b/docs/playing-live.md @@ -1,14 +1,14 @@ # Playing audx live with a MIDI controller, Push 2, or Terminal -## ⚡ The 10-Second Jam +## The 10-Second Jam -Get sound coming out of your speakers or pads in under 10 seconds: +Get sound coming out of your speakers or pads quickly: ```bash -# 1. Install audx in a clean virtual environment +# 1. Install from GitHub in a clean virtual environment python3 -m venv .venv source .venv/bin/activate -python -m pip install audx +python -m pip install "git+https://github.com/chrisschouk/audx.git" # Native audio drivers brew install portaudio # macOS @@ -20,13 +20,19 @@ audx midi list # 3. Jam! audx jam # Instant drum pad session -audx jam --genre techno # Auto-generated 128 BPM techno jam +audx jam --genre techno # Pads + looping techno patterns @ 128 BPM audx jam --chromatic # Pitched synth keyboard mode ``` +No MIDI controller? Start with the offline demo or the [browser studio](https://audx-five.vercel.app/studio.html): + +```bash +audx demo loop.wav +``` + --- -## 🚀 Instant HD Sample Auto-Scanning & Track Generation +## Instant HD Sample Auto-Scanning & Track Generation `audx` can automatically index usable sample libraries across your computer so you don't need to specify manual paths: @@ -34,7 +40,7 @@ audx jam --chromatic # Pitched synth keyboard mode # Scan ~/Music, ~/Downloads, ~/Samples, ~/Documents for audio files audx samples scan -# Jam on the spot using scanned local samples +# Jam on the spot using scanned local samples + genre pattern pack audx jam --genre house audx jam --genre hiphop audx jam --genre ukg @@ -46,7 +52,7 @@ If no sample files are found on your hard drive, `audx` automatically uses built --- -## 🎛 Push 2 Hardware Integration (Direct USB & Display) +## Push 2 Hardware Integration (Direct USB & Display) `audx` features direct USB driver integration for Ableton Push 2 on macOS and Linux: @@ -74,7 +80,7 @@ audx push2 map --- -## 🎹 Live MIDI Recording & Clock Output +## Live MIDI Recording & Clock Output Lock external hardware or Ableton Live to `audx` tempo: @@ -91,7 +97,7 @@ audx midi rec my_pattern --bars 1 --ch 0 --- -## 🎚 Ableton Live Session Export +## Ableton Live Session Export Export your terminal creations directly to an Ableton Live Set (`.als`): @@ -103,32 +109,31 @@ Double-click `my-session.als` to open your clips, tracks, and tempo inside Ablet --- -## 🌐 Local Web Companion +## Local Web Companion -Launch the playable browser instrument alongside the terminal DAW: +For a **playable** browser instrument, use the hosted studio (or open `site/studio.html` locally) — that is the Web Audio drum machine. + +The CLI dashboard is a **read-only** live monitor for an already-running session: ```bash -audx open --web +audx open my-track --serve # or: audx serve --port 8080 ``` -Opens `http://127.0.0.1:8080/app` with interactive Web Audio pattern playback. +Opens `http://127.0.0.1:8080/` with session state (not a full playable `/app` UI). + +Browser studio tip: Web MIDI / Push 2 works best in Chromium-based browsers. --- -## 🛠 Troubleshooting Modern Python Environments +## Troubleshooting Modern Python Environments ### 1. `zsh: command not found: pip` Many macOS installs do not place `pip` directly on `PATH`. Use `python3 -m pip`: ```bash python3 -m venv .venv source .venv/bin/activate -python -m pip install audx -``` -Or install via `pipx` / `uv`: -```bash -pipx install audx -# or: uv tool install audx +python -m pip install "git+https://github.com/chrisschouk/audx.git" ``` ### 2. PEP 668 `externally-managed-environment` @@ -136,11 +141,11 @@ Homebrew Python and macOS block global `pip install` to protect system packages. ```bash python3 -m venv .venv source .venv/bin/activate -python -m pip install audx +python -m pip install "git+https://github.com/chrisschouk/audx.git" ``` ### 3. `ModuleNotFoundError: No module named 'click'` -`audx` explicitly declares `click>=8.0.0` in its package manifest. Re-install in a fresh venv: +Re-install in a fresh venv from GitHub: ```bash -python -m pip install --force-reinstall audx +python -m pip install --force-reinstall "git+https://github.com/chrisschouk/audx.git" ``` diff --git a/marketing/remotion/src/AudxPromo.tsx b/marketing/remotion/src/AudxPromo.tsx index c99b91e..2e55e40 100644 --- a/marketing/remotion/src/AudxPromo.tsx +++ b/marketing/remotion/src/AudxPromo.tsx @@ -335,7 +335,7 @@ const CTA: React.FC = () => { letterSpacing: 1, }} > - github.com/totalaudiopromo/audx + github.com/chrisschouk/audx diff --git a/scripts/make-demo-gif.py b/scripts/make-demo-gif.py index 43a8a32..de32c60 100644 --- a/scripts/make-demo-gif.py +++ b/scripts/make-demo-gif.py @@ -36,7 +36,7 @@ # Each "line" is a list of (text, color, bold) segments. None = blank line. Seg = tuple[str, tuple[int, int, int], bool] SCRIPT: list[list[Seg] | None] = [ - [("$ ", PINK, True), ("pip install audx", TEXT, True)], + [("$ ", PINK, True), ('pip install "git+https://github.com/chrisschouk/audx.git"', TEXT, True)], [("Successfully installed audx-0.3.0", MUTED, False)], None, [("$ ", PINK, True), ("audx demo loop.wav", TEXT, True)], diff --git a/site/index.html b/site/index.html index 514a48c..efd694e 100644 --- a/site/index.html +++ b/site/index.html @@ -11,7 +11,7 @@
@@ -22,12 +22,18 @@

Code your music.
Own your sound.

hackable. No cloud. No mouse. No lock-in.

-
+

+ ▶ open the studio + type a pattern +

+

Try it in the browser — no install. Chromium recommended for Web MIDI.

+ +
- make a beat in 10 seconds + or make a beat from your terminal
-
$ pip install audx
+        
$ pip install "git+https://github.com/chrisschouk/audx.git"
 $ audx demo loop.wav
   audx · demo
     ♪ kick     kick 4/4
@@ -37,10 +43,6 @@ 

Code your music.
Own your sound.

✓ rendered 4 bars @ 124 BPM → loop.wav

No samples. No audio hardware. No config. Just sound.

-

- ▶ open the studio - type a pattern -

@@ -102,14 +104,14 @@

Speak in patterns

Open a terminal. Hit play.

-
$ pip install audx && audx demo loop.wav
- Read the docs on GitHub ↗ +
$ pip install "git+https://github.com/chrisschouk/audx.git" && audx demo loop.wav
+ Read the docs on GitHub ↗

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

- +
diff --git a/site/play.html b/site/play.html index b6a0c02..11d500d 100644 --- a/site/play.html +++ b/site/play.html @@ -84,7 +84,7 @@

play in your browser

| tune -7st, | humanize 8%, | chance 70%. Rhythms: 4/4, 16x8, e(5,16,2), [1.0.1.0]. Full reference on - GitHub. + GitHub.

diff --git a/site/studio.html b/site/studio.html index 7a4252e..62d0f58 100644 --- a/site/studio.html +++ b/site/studio.html @@ -131,7 +131,7 @@

audx studio

- the real synth, in your browser — no install + the real synth, in your browser — no install · Web MIDI works best in Chromium ← audx
diff --git a/src/audx/cli.py b/src/audx/cli.py index e4bc67b..8682b09 100644 --- a/src/audx/cli.py +++ b/src/audx/cli.py @@ -296,10 +296,12 @@ def jam( False, "--chromatic", help="Play one melodic voice across the keys (vs. drum pads)" ), voice: str = typer.Option("keys", "--voice", help="Melodic voice for --chromatic mode"), - bpm: float = typer.Option(124.0, "--bpm", help="Engine tempo"), + bpm: float = typer.Option(124.0, "--bpm", help="Engine tempo (overridden by --genre preset tempo)"), no_lights: bool = typer.Option(False, "--no-lights", help="Don't light Push 2 pads"), once: bool = typer.Option(False, "--once", help="Run jam loop once and exit"), - genre: str | None = typer.Option(None, "--genre", help="Genre preset"), + genre: str | None = typer.Option( + None, "--genre", help="Load a genre pattern pack: techno|house|hiphop|ukg|ambient" + ), ) -> None: """Play the synth kit live from a MIDI controller or Push 2 — instant sound. @@ -308,6 +310,7 @@ def jam( to play a melodic voice across a keyboard: audx jam # drum pads → kick/snare/hat/... + audx jam --genre techno # same pads + looping techno patterns audx jam --chromatic # keyboard plays the 'keys' voice audx jam --chromatic --voice bass """ @@ -315,10 +318,29 @@ def jam( from audx.midi import list_inputs from audx.push2 import open_push2_lights, push2_input_name, push2_pad_layout + if genre: + from audx.generator import Genre, generate_track + + try: + chosen_genre = Genre(genre.strip().lower()) + except ValueError as exc: + options = ", ".join(g.value for g in Genre) + typer.echo(f"Unknown genre '{genre}'. Choose: {options}", err=True) + raise typer.Exit(1) from exc + bpm, patterns = generate_track(chosen_genre) + pe = get_pattern_engine() + pe.set_bpm(bpm) + typer.echo(f" genre: {chosen_genre.value} @ {bpm:g} BPM") + for pattern in patterns: + pe.add_pattern(pattern) + typer.echo(f" ♪ {pattern.name:<10} {pattern.dsl}") + pe.start() + inputs = list_inputs() in_port = port or push2_input_name() or (inputs[0] if inputs else "virtual") typer.echo(f" MIDI in: {in_port}") + engine = None try: engine = init_engine() engine.set_bpm(bpm) @@ -353,10 +375,16 @@ def _feedback(note: int, v: str, vel: int) -> None: if once: if lights is not None: lights.close() - engine.stop() + if engine is not None: + engine.stop() + get_pattern_engine().stop() typer.echo("\n jam loop complete.") return + if engine is None: + typer.echo(" · no audio engine available — install PortAudio for live sound", err=True) + raise typer.Exit(1) + try: run_jam( engine, @@ -373,6 +401,7 @@ def _feedback(note: int, v: str, vel: int) -> None: if lights is not None: lights.close() engine.stop() + get_pattern_engine().stop() typer.echo("\n stopped.") diff --git a/tests/test_cli_spec_commands.py b/tests/test_cli_spec_commands.py index 0f3538b..e4826d6 100644 --- a/tests/test_cli_spec_commands.py +++ b/tests/test_cli_spec_commands.py @@ -90,6 +90,16 @@ def test_jam_command_with_genre_flag(): result = runner.invoke(app, ["jam", "--genre", "house", "--once"]) assert result.exit_code == 0 assert "house" in result.output.lower() + assert "124" in result.output # house genre tempo + assert "♪" in result.output + # Output lists generated tracks during the session + assert "kick" in result.output.lower() or "clap" in result.output.lower() + + +def test_jam_command_rejects_unknown_genre(): + result = runner.invoke(app, ["jam", "--genre", "not-a-genre", "--once"]) + assert result.exit_code != 0 + assert "unknown genre" in result.output.lower() def test_samples_scan_command():