audio-as-code 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/check_distribution.py +27 -1
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/smoke_installed.py +64 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/workflows/ci.yml +10 -5
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/workflows/publish.yml +6 -4
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/CHANGELOG.md +28 -3
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/CONTRIBUTING.md +2 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/PKG-INFO +39 -11
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/README.md +36 -10
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/agents.md +48 -14
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/architecture.md +9 -2
- audio_as_code-0.2.0/docs/arrangement.md +107 -0
- audio_as_code-0.2.0/docs/articulations.md +73 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/cloudflare-hosting.md +9 -5
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/expressive-engine.md +6 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/instrument-foundation.md +5 -2
- audio_as_code-0.2.0/docs/production-output.md +142 -0
- audio_as_code-0.2.0/docs/project-setup.md +48 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/releasing.md +4 -4
- audio_as_code-0.2.0/docs/roadmap.md +41 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/agents.md +46 -15
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/composition.md +15 -5
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/integrations.md +30 -8
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/llms.txt +16 -8
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/quickstart.md +62 -16
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/reference.md +60 -13
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/synthesis.md +6 -3
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/website.md +6 -2
- audio_as_code-0.2.0/examples/arrangement_workflow.py +97 -0
- audio_as_code-0.2.0/examples/articulations.py +117 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/build_site.py +7 -1
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/classic_reimaginations.py +2 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/classic_showcase.py +2 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/prepare_cloudflare.py +1 -1
- audio_as_code-0.2.0/examples/production_output.py +19 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/pyproject.toml +4 -1
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/schemas/song-v1.schema.json +32 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/skills/audio-as-code/SKILL.md +58 -21
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/__init__.py +33 -2
- audio_as_code-0.2.0/src/audio_as_code/_audio.py +229 -0
- audio_as_code-0.2.0/src/audio_as_code/_cli_progress.py +34 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_orchestra_profiles.py +30 -0
- audio_as_code-0.2.0/src/audio_as_code/_project_templates/AGENTS.md.txt +21 -0
- audio_as_code-0.2.0/src/audio_as_code/_project_templates/README.md.txt +66 -0
- audio_as_code-0.2.0/src/audio_as_code/_project_templates/compose.py.txt +49 -0
- audio_as_code-0.2.0/src/audio_as_code/_project_templates/gitignore.txt +4 -0
- audio_as_code-0.2.0/src/audio_as_code/_project_templates/pyproject.toml.txt +9 -0
- audio_as_code-0.2.0/src/audio_as_code/_project_templates/score.json +240 -0
- audio_as_code-0.2.0/src/audio_as_code/_render_control.py +67 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_voices.py +7 -2
- audio_as_code-0.2.0/src/audio_as_code/arrangement.py +248 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/cli.py +118 -15
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/inspection.py +10 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/instruments.py +14 -0
- audio_as_code-0.2.0/src/audio_as_code/loudness.py +128 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/midi.py +5 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/model.py +14 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/orchestra.py +37 -2
- audio_as_code-0.2.0/src/audio_as_code/project_setup.py +186 -0
- audio_as_code-0.2.0/src/audio_as_code/render.py +488 -0
- audio_as_code-0.2.0/tests/test_arrangement.py +148 -0
- audio_as_code-0.2.0/tests/test_articulation_exports.py +38 -0
- audio_as_code-0.2.0/tests/test_articulations.py +172 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_cloudflare_staging.py +1 -3
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_distribution_contract.py +13 -0
- audio_as_code-0.2.0/tests/test_loudness.py +188 -0
- audio_as_code-0.2.0/tests/test_production_cli.py +136 -0
- audio_as_code-0.2.0/tests/test_production_output.py +322 -0
- audio_as_code-0.2.0/tests/test_project_setup.py +225 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_site_docs.py +1 -1
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/uv.lock +232 -1
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/source.html +1 -1
- audio_as_code-0.1.0/src/audio_as_code/_audio.py +0 -87
- audio_as_code-0.1.0/src/audio_as_code/render.py +0 -230
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/FUNDING.yml +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/dependabot.yml +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/check_schema.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/smoke_distribution.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.gitignore +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/AGENTS.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/LICENSE +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/SECURITY.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/analytics.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/classic-reimaginations.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/classic-showcase.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/composition-tools.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/creative-workflows.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/extended-instruments.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/full-compositions.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/music-demos.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/orchestra.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/piano-sustain.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/render-performance.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/score-inspection.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/01_first_score.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/02_motif_and_progression.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/03_song_form.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/04_expressive_controls.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/05_agent_loop.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/06_agent_handoff.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/agent-score.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/website-discovery.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/_site_discovery.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/benchmark_render.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/composition_tools.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/creative_workflows.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/expressive-score.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/expressive_engine.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/famous_music.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/first_light.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/full_compositions.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/instrument_browser.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/README.md +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/build_classics_full.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/build_excerpts.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-full.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007-viola.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_allemande.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_courante.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_gigue.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_menuet_i.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_menuet_ii.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_prelude.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_sarabande.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-header.ily +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/fur_elise-fur_elise.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/greensleeves-greensleeves.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/ode_to_joy-ode_to_joy.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/turkish_march-KV331_3_RondoAllaTurca.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/turkish_march-KV331_3_RondoAllaTurcaNotes.ly +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/excerpts.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/physical_instruments.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/piano_sustain.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/serve_site.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/showcase.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/__main__.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_export_rules.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_paths.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/acoustics.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/automation.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/demo.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/effects.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/extended.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/pattern.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/physical.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/py.typed +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_acoustics.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_agent_handoff.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_agent_loop.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_agent_readiness.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_classic_showcase.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_cli_dx.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_core_export_regressions.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_creative_workflows.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_demo_performance.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_expressive_engine.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_extended_instruments.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_finite_strikes.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_full_compositions.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_instrument_refinement.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_instruments.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_midi_cli.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_midi_warnings.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_music_demos.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_orchestra.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_output_preflight.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_pattern_composition.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_physical.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_piano_sustain.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_render.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_render_performance.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_score.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_score_inspection.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_security_regressions.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_serve_site.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_site_analytics.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_site_discovery.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/media-handler.js +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/media-handler.test.js +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/package-lock.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/package.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/worker.js +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/instrument-browser.html +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/404.html +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/analytics-config.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/agent-handoff.js +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/analytics.js +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/Anybody-OFL.txt +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/Archivo-OFL.txt +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/FONTS.txt +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/JetBrainsMono-OFL.txt +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/anybody-latin.woff2 +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/archivo-latin.woff2 +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/jetbrains-mono-latin.woff2 +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/icon.svg +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/ring.svg +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/site.css +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/site.js +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/social.png +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/social.svg +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/torn.svg +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/identity.json +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/index.html +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/layout.html +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/snippets/loop.py +0 -0
- {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/tools/verify_site.py +0 -0
|
@@ -98,6 +98,11 @@ REQUIRED_SOURCE = {
|
|
|
98
98
|
"src/audio_as_code/_voices.py",
|
|
99
99
|
"src/audio_as_code/_export_rules.py",
|
|
100
100
|
"src/audio_as_code/_orchestra_profiles.py",
|
|
101
|
+
"src/audio_as_code/arrangement.py",
|
|
102
|
+
"src/audio_as_code/project_setup.py",
|
|
103
|
+
"src/audio_as_code/loudness.py",
|
|
104
|
+
"src/audio_as_code/_render_control.py",
|
|
105
|
+
"src/audio_as_code/_cli_progress.py",
|
|
101
106
|
"pyproject.toml",
|
|
102
107
|
"uv.lock",
|
|
103
108
|
"README.md",
|
|
@@ -113,6 +118,10 @@ REQUIRED_SOURCE = {
|
|
|
113
118
|
"docs/render-performance.md",
|
|
114
119
|
"docs/cloudflare-hosting.md",
|
|
115
120
|
"docs/releasing.md",
|
|
121
|
+
"docs/project-setup.md",
|
|
122
|
+
"docs/arrangement.md",
|
|
123
|
+
"docs/articulations.md",
|
|
124
|
+
"docs/production-output.md",
|
|
116
125
|
"schemas/song-v1.schema.json",
|
|
117
126
|
"examples/first_light.py",
|
|
118
127
|
"examples/classic_showcase.py",
|
|
@@ -173,9 +182,15 @@ def check_metadata(data: bytes, expected_version: str) -> None:
|
|
|
173
182
|
if (metadata["License-Expression"] or metadata["License"]) != "MIT":
|
|
174
183
|
raise ValueError("MIT license metadata is missing")
|
|
175
184
|
requirements = metadata.get_all("Requires-Dist", [])
|
|
176
|
-
|
|
185
|
+
required = [item for item in requirements if "extra ==" not in item]
|
|
186
|
+
optional = [item for item in requirements if "extra ==" in item]
|
|
187
|
+
names = {re.split(r"[<>=!~;\s\[]", item, maxsplit=1)[0].lower() for item in required}
|
|
177
188
|
if names != {"numpy", "pydantic", "mido"} or any("@" in item for item in requirements):
|
|
178
189
|
raise ValueError("Runtime dependencies changed or contain nonportable direct URLs")
|
|
190
|
+
if metadata.get_all("Provides-Extra", []) != ["loudness"] or [
|
|
191
|
+
item.replace('"', "'") for item in optional
|
|
192
|
+
] != ["pyloudnorm<0.3,>=0.2; extra == 'loudness'"]:
|
|
193
|
+
raise ValueError("Unexpected optional dependency metadata")
|
|
179
194
|
|
|
180
195
|
|
|
181
196
|
def check_wheel(path: Path, expected_version: str) -> dict:
|
|
@@ -206,6 +221,17 @@ def check_wheel(path: Path, expected_version: str) -> dict:
|
|
|
206
221
|
"_voices.py",
|
|
207
222
|
"_export_rules.py",
|
|
208
223
|
"_orchestra_profiles.py",
|
|
224
|
+
"arrangement.py",
|
|
225
|
+
"project_setup.py",
|
|
226
|
+
"loudness.py",
|
|
227
|
+
"_render_control.py",
|
|
228
|
+
"_cli_progress.py",
|
|
229
|
+
"_project_templates/compose.py.txt",
|
|
230
|
+
"_project_templates/score.json",
|
|
231
|
+
"_project_templates/README.md.txt",
|
|
232
|
+
"_project_templates/AGENTS.md.txt",
|
|
233
|
+
"_project_templates/pyproject.toml.txt",
|
|
234
|
+
"_project_templates/gitignore.txt",
|
|
209
235
|
"py.typed",
|
|
210
236
|
)
|
|
211
237
|
}
|
|
@@ -7,6 +7,7 @@ import subprocess
|
|
|
7
7
|
import sys
|
|
8
8
|
import tempfile
|
|
9
9
|
from importlib.metadata import distribution
|
|
10
|
+
from importlib.util import find_spec
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
import audio_as_code
|
|
@@ -40,6 +41,27 @@ def main() -> None:
|
|
|
40
41
|
)
|
|
41
42
|
assert json.loads(version.stdout) == {"version": metadata.version}
|
|
42
43
|
subprocess.run([*command, "--help"], cwd=work, capture_output=True, check=True)
|
|
44
|
+
health = subprocess.run(
|
|
45
|
+
[str(console), "doctor"], cwd=work, capture_output=True, text=True, check=True
|
|
46
|
+
)
|
|
47
|
+
assert json.loads(health.stdout)["ok"] and not health.stderr
|
|
48
|
+
starter = work / "composition project"
|
|
49
|
+
initialized = subprocess.run(
|
|
50
|
+
[str(console), "init", str(starter)],
|
|
51
|
+
cwd=work,
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
check=True,
|
|
55
|
+
)
|
|
56
|
+
assert len(json.loads(initialized.stdout)["files"]) == 6
|
|
57
|
+
assert f"audio-as-code=={metadata.version}" in (starter / "pyproject.toml").read_text()
|
|
58
|
+
subprocess.run(
|
|
59
|
+
[sys.executable, str(starter / "compose.py")],
|
|
60
|
+
cwd=work,
|
|
61
|
+
capture_output=True,
|
|
62
|
+
check=True,
|
|
63
|
+
)
|
|
64
|
+
assert (starter / "output" / "song.wav").is_file()
|
|
43
65
|
phrase = Pattern.sequence(["C4"], step=0.25)
|
|
44
66
|
phrase = phrase.overlay(phrase.transpose(12), offset=0.25).stretch(2).scale_velocity(0.5)
|
|
45
67
|
song = Song(
|
|
@@ -69,6 +91,48 @@ def main() -> None:
|
|
|
69
91
|
export_midi(song, work / "score.mid")
|
|
70
92
|
assert report["wav"] == analyze_wav(work / "score.wav")
|
|
71
93
|
assert not report["wav"]["silent"] and not report["wav"]["full_scale_samples"]
|
|
94
|
+
preview = subprocess.run(
|
|
95
|
+
[
|
|
96
|
+
str(console),
|
|
97
|
+
"preview",
|
|
98
|
+
str(score),
|
|
99
|
+
"-o",
|
|
100
|
+
str(work / "preview.wav"),
|
|
101
|
+
"--start",
|
|
102
|
+
"0.1",
|
|
103
|
+
"--duration",
|
|
104
|
+
"0.2",
|
|
105
|
+
"--format",
|
|
106
|
+
"float32",
|
|
107
|
+
"--progress-file",
|
|
108
|
+
str(work / "progress.jsonl"),
|
|
109
|
+
],
|
|
110
|
+
cwd=work,
|
|
111
|
+
capture_output=True,
|
|
112
|
+
text=True,
|
|
113
|
+
check=True,
|
|
114
|
+
)
|
|
115
|
+
assert json.loads(preview.stdout)["wav"]["wav_format"] == "float32"
|
|
116
|
+
assert not preview.stderr
|
|
117
|
+
assert (work / "progress.jsonl").read_text().strip()
|
|
118
|
+
if find_spec("pyloudnorm") is None:
|
|
119
|
+
missing = subprocess.run(
|
|
120
|
+
[
|
|
121
|
+
str(console),
|
|
122
|
+
"render",
|
|
123
|
+
str(score),
|
|
124
|
+
"-o",
|
|
125
|
+
str(work / "optional.wav"),
|
|
126
|
+
"--target-lufs",
|
|
127
|
+
"-18",
|
|
128
|
+
],
|
|
129
|
+
cwd=work,
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
)
|
|
133
|
+
assert missing.returncode == 2 and not missing.stdout
|
|
134
|
+
assert "optional extra" in json.loads(missing.stderr)["message"]
|
|
135
|
+
assert not (work / "optional.wav").exists()
|
|
72
136
|
validation = subprocess.run(
|
|
73
137
|
[str(console), "validate", str(score)],
|
|
74
138
|
cwd=work,
|
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
timeout-minutes: 5
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
17
|
with:
|
|
18
18
|
persist-credentials: false
|
|
19
19
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
@@ -37,29 +37,34 @@ jobs:
|
|
|
37
37
|
runs-on: ${{ matrix.os }}
|
|
38
38
|
timeout-minutes: 20
|
|
39
39
|
steps:
|
|
40
|
-
- uses: actions/checkout@
|
|
40
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
41
41
|
with:
|
|
42
42
|
persist-credentials: false
|
|
43
|
-
- uses: astral-sh/setup-uv@
|
|
43
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
44
44
|
with:
|
|
45
45
|
python-version: ${{ matrix.python }}
|
|
46
46
|
version: '0.12.15'
|
|
47
|
+
prune-cache: true
|
|
47
48
|
- run: uv sync --locked
|
|
48
49
|
- name: Schema stays in sync with the score model
|
|
49
50
|
run: uv run --locked python .github/scripts/check_schema.py
|
|
50
51
|
- run: uv run --locked pytest
|
|
52
|
+
- name: Optional loudness backend
|
|
53
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
|
|
54
|
+
run: uv run --locked --extra loudness pytest tests/test_loudness.py tests/test_production_output.py tests/test_production_cli.py -W error
|
|
51
55
|
|
|
52
56
|
distribution:
|
|
53
57
|
runs-on: ubuntu-latest
|
|
54
58
|
timeout-minutes: 15
|
|
55
59
|
steps:
|
|
56
|
-
- uses: actions/checkout@
|
|
60
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
57
61
|
with:
|
|
58
62
|
persist-credentials: false
|
|
59
|
-
- uses: astral-sh/setup-uv@
|
|
63
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
60
64
|
with:
|
|
61
65
|
python-version: '3.13'
|
|
62
66
|
version: '0.12.15'
|
|
67
|
+
prune-cache: true
|
|
63
68
|
- run: uv sync --locked
|
|
64
69
|
- run: uv run --locked ruff check .
|
|
65
70
|
- run: uv run --locked ruff format --check .
|
|
@@ -22,13 +22,14 @@ jobs:
|
|
|
22
22
|
permissions:
|
|
23
23
|
contents: read
|
|
24
24
|
steps:
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
26
|
with:
|
|
27
27
|
persist-credentials: false
|
|
28
|
-
- uses: astral-sh/setup-uv@
|
|
28
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
29
29
|
with:
|
|
30
30
|
python-version: '3.13'
|
|
31
31
|
version: '0.12.15'
|
|
32
|
+
prune-cache: true
|
|
32
33
|
- run: uv sync --locked
|
|
33
34
|
- name: Check the requested version against both source declarations
|
|
34
35
|
env:
|
|
@@ -90,13 +91,14 @@ jobs:
|
|
|
90
91
|
permissions:
|
|
91
92
|
contents: read
|
|
92
93
|
steps:
|
|
93
|
-
- uses: actions/checkout@
|
|
94
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
94
95
|
with:
|
|
95
96
|
persist-credentials: false
|
|
96
|
-
- uses: astral-sh/setup-uv@
|
|
97
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
97
98
|
with:
|
|
98
99
|
python-version: '3.13'
|
|
99
100
|
version: '0.12.15'
|
|
101
|
+
prune-cache: true
|
|
100
102
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
101
103
|
with:
|
|
102
104
|
name: pypi-distributions
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
are unreleased. Package versions and score schema versions are separate.
|
|
3
|
+
Audio as Code is an early prototype. Package versions and score schema versions
|
|
4
|
+
are separate.
|
|
6
5
|
|
|
7
6
|
## Unreleased
|
|
8
7
|
|
|
9
8
|
### Added
|
|
10
9
|
|
|
10
|
+
- `aac init` creates an editable composition project from installed wheel resources;
|
|
11
|
+
`aac doctor` checks the active runtime with a short local synthesis probe.
|
|
12
|
+
- Tempo-aware timestamp cues, named section replacement and repeated loop previews
|
|
13
|
+
with boundary measurements through the Python arrangement helpers.
|
|
14
|
+
- Optional `soft` and `accented` note/track articulations for 13 bowed-string and
|
|
15
|
+
wind voices, with shaped harmonic attacks and releases. Existing unarticulated
|
|
16
|
+
scores retain their deterministic audio and score hashes.
|
|
17
|
+
- PCM24 and float32 WAV export, full-context excerpt previews, progress callbacks,
|
|
18
|
+
cooperative cancellation and staged audio publication. CLI progress uses a
|
|
19
|
+
separate JSON Lines file.
|
|
20
|
+
- Optional `loudness` extra for BS.1770-4 integrated measurement and constant-gain
|
|
21
|
+
targeting with a sample-peak ceiling. It does not measure or limit true peak.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Documented versioned PyPI installation alongside the source workspace and pinned
|
|
26
|
+
Git routes. These onboarding updates follow the 0.1.0 package release.
|
|
27
|
+
- HTML responses allow compression after disabling the Cloudflare RUM injection;
|
|
28
|
+
post-deployment checks still verify that no beacon is added.
|
|
29
|
+
|
|
30
|
+
## 0.1.0 — 2026-10-03
|
|
31
|
+
|
|
32
|
+
Initial release on [PyPI](https://pypi.org/project/audio-as-code/0.1.0/).
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
11
36
|
- Website sitemap, canonical page URLs, structured project metadata, social
|
|
12
37
|
previews, and full plain-text agent documentation alongside `llms.txt`.
|
|
13
38
|
|
|
@@ -58,6 +58,8 @@ CLI entry points, including the normal error path. These checks do not publish.
|
|
|
58
58
|
|
|
59
59
|
CI runs the tests and schema check on Python 3.10 and 3.13 on Linux/Windows, and
|
|
60
60
|
3.13 on macOS. A separate job checks lint, formatting, and installed artifacts.
|
|
61
|
+
The Linux Python 3.13 job also tests the optional loudness backend. For changes
|
|
62
|
+
to that path, run `uv run --extra loudness pytest tests/test_loudness.py -W error`.
|
|
61
63
|
Report checks you could not run; do not label an unrun audition or CI job as passed.
|
|
62
64
|
|
|
63
65
|
For website hosting changes, install Node.js 24 and run the hosting checks from
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: audio-as-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: An agent-friendly music score, composition toolkit, and offline renderer.
|
|
5
5
|
Project-URL: Homepage, https://audioascode.com
|
|
6
6
|
Project-URL: Repository, https://github.com/joaoCarvalho1000/audio-as-code
|
|
@@ -24,6 +24,8 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Requires-Dist: mido<2,>=1.3
|
|
25
25
|
Requires-Dist: numpy<3,>=1.24
|
|
26
26
|
Requires-Dist: pydantic<3,>=2.7
|
|
27
|
+
Provides-Extra: loudness
|
|
28
|
+
Requires-Dist: pyloudnorm<0.3,>=0.2; extra == 'loudness'
|
|
27
29
|
Description-Content-Type: text/markdown
|
|
28
30
|
|
|
29
31
|
# Audio as Code
|
|
@@ -38,7 +40,7 @@ then carry the rendered WAV into your video, game or slides. See the
|
|
|
38
40
|
[creative workflow integrations](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/integrations.md) for Codex, Claude Code
|
|
39
41
|
and Hyperframes recipes.
|
|
40
42
|
|
|
41
|
-
**Status:
|
|
43
|
+
**Status: 0.2.0 alpha, [available on PyPI](https://pypi.org/project/audio-as-code/0.2.0/).** Python 3.10+, a versioned JSON score, a headless CLI, and an offline synthesis backend. No API keys, audio device, model weights, sample downloads, or DAW are required.
|
|
42
44
|
|
|
43
45
|
A **score** is the editable recipe for a piece: instruments, notes, timing,
|
|
44
46
|
volume and tempo. Write it in Python or JSON; rendering turns it into audio.
|
|
@@ -91,17 +93,31 @@ To browse and audition the catalog, run `uv run python examples/instrument_brows
|
|
|
91
93
|
For a coding agent, start with: **“Read https://audioascode.com/llms.txt and add
|
|
92
94
|
Audio as Code to this project. Then compose the music in my brief.”**
|
|
93
95
|
|
|
94
|
-
To add
|
|
96
|
+
To add version 0.2.0 to an existing uv project:
|
|
95
97
|
|
|
96
98
|
```sh
|
|
97
|
-
uv add "audio-as-code
|
|
99
|
+
uv add "audio-as-code==0.2.0"
|
|
98
100
|
uv run --locked aac instruments
|
|
99
101
|
uv run --locked aac schema
|
|
100
102
|
```
|
|
101
103
|
|
|
102
|
-
Commit your project's `uv.lock` to preserve the
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
Commit your project's `uv.lock` to preserve the package and dependency versions.
|
|
105
|
+
For a new composition project, run `uvx --from audio-as-code==0.2.0 aac init my-soundtrack`,
|
|
106
|
+
then `cd my-soundtrack`, `uv sync`, `uv run aac doctor` and `uv run python compose.py`.
|
|
107
|
+
The starter includes an editable composer, JSON score and agent instructions.
|
|
108
|
+
|
|
109
|
+
For a one-off CLI check, run `uvx --from audio-as-code==0.2.0 aac --help`.
|
|
110
|
+
The package contains the Python library and CLI. Use a source checkout or the
|
|
111
|
+
website's source ZIP for the complete examples, guides and portable skill.
|
|
112
|
+
|
|
113
|
+
Without uv, create a local environment with `python -m venv .venv`. On Windows,
|
|
114
|
+
run `.venv/Scripts/python.exe -m pip install audio-as-code==0.2.0`; on macOS/Linux,
|
|
115
|
+
run `.venv/bin/python -m pip install audio-as-code==0.2.0`. Use that interpreter
|
|
116
|
+
with `-m audio_as_code --help`; no activation or global installation is needed.
|
|
117
|
+
|
|
118
|
+
The [quickstart](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/quickstart.md)
|
|
119
|
+
also covers first compositions and installation from a pinned Git revision.
|
|
120
|
+
The standalone source-workspace route follows below.
|
|
105
121
|
|
|
106
122
|
For complete video, game-loop and presentation examples with exact durations and
|
|
107
123
|
verified revisions, see [creative workflows](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/creative-workflows.md).
|
|
@@ -270,6 +286,18 @@ render(song, "output/string.wav")
|
|
|
270
286
|
|
|
271
287
|
Brightness and decay work on the modeled decaying voices. Pluck position is available on plucked-string models and harpsichord. Held voices expose selected breath/vibrato controls; `aac instruments` reports each voice's supported settings and defaults. Velocity changes loudness and model brightness. By default notes fade within their score duration. Optional note/track `release_seconds` lets a sound ring after note-off. Ordered `tempo_map` steps, gain/pan automation, and procedural delay/reverb chains add expression; see the [composition guide](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/composition.md).
|
|
272
288
|
|
|
289
|
+
Use [arrangement helpers](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/arrangement.md)
|
|
290
|
+
to place cues by seconds across tempo changes, replace named sections and measure
|
|
291
|
+
repeated loop previews. Section names remain outside the version 1 score JSON.
|
|
292
|
+
Thirteen bowed-string and wind voices support optional `soft` and `accented`
|
|
293
|
+
[articulations](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/articulations.md).
|
|
294
|
+
Existing scores keep their sound and score identity when these controls are omitted.
|
|
295
|
+
|
|
296
|
+
[Production output](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/production-output.md)
|
|
297
|
+
adds higher-resolution WAV, exact-context excerpts, progress callbacks and
|
|
298
|
+
cooperative cancellation. Install `audio-as-code[loudness]` for optional integrated
|
|
299
|
+
LUFS targeting with a sample-peak ceiling. There is no true-peak limiter.
|
|
300
|
+
|
|
273
301
|
## An agent's working loop
|
|
274
302
|
|
|
275
303
|
1. Read the schema, discover available voices with `aac instruments`, and choose tempo, harmony, rhythm, and arrangement.
|
|
@@ -296,7 +324,7 @@ can report an export blocker, so check its readiness fields.
|
|
|
296
324
|
|
|
297
325
|
## Rendering and export
|
|
298
326
|
|
|
299
|
-
- WAV:
|
|
327
|
+
- WAV: stereo PCM16 by default, with explicit PCM24 and float32 exports. Rendering happens offline in memory and is limited to five minutes including tails. Excerpt previews preserve full-song context and still cost a full render; compose shorter test scores to reduce iteration time.
|
|
300
328
|
- Default normalization only reduces gain when the mix exceeds a 0.95 peak ceiling. It never increases a quiet mix. `--no-normalize` preserves mix gain and hard-clips values outside full scale on WAV export; the report exposes this.
|
|
301
329
|
- Stems include track gain, pan, master gain, and the same attenuation applied to the mix. They approximately reconstruct the mix within PCM rounding when there are no master effects and no individual stem clips. Stems include track effects but omit master effects, with a warning when the mix uses them. Numeric filenames avoid interpreting track names as paths.
|
|
302
330
|
- MIDI: type 1, 480 ticks per quarter note, tempo, track names, programs, volume, and pan. Up to 15 melodic tracks and one track per drum instrument. Overlapping notes of the same pitch on one channel are rejected, including collisions between a drum kit and an individual drum. Quantization can differ from WAV timing by a MIDI tick.
|
|
@@ -309,14 +337,14 @@ can report an export blocker, so check its readiness fields.
|
|
|
309
337
|
|
|
310
338
|
The framework's instrument direction is sound generated from code. The score is the extension boundary for more detailed physical models. Proposed next steps:
|
|
311
339
|
|
|
312
|
-
-
|
|
340
|
+
- Continuous tempo ramps, variable-length arrangement edits and clip scheduling.
|
|
313
341
|
- Coupled string/body, nonlinear reed/bore, and bow-friction models beyond the current approximations.
|
|
314
342
|
- Half-pedaling, instrument articulation switches, and more expression controls.
|
|
315
343
|
- An MCP server wrapping the existing validate/render/analyze operations.
|
|
316
|
-
-
|
|
344
|
+
- Faster bounded previews, true-peak analysis and agent-assisted audition workflows.
|
|
317
345
|
- Streaming renders, real-time playback, and a browser editor.
|
|
318
346
|
|
|
319
|
-
These are roadmap items, not implemented features. The 0.
|
|
347
|
+
These are roadmap items, not implemented features. The 0.2 API may change; score schema changes will use an explicit version.
|
|
320
348
|
|
|
321
349
|
Related projects worth exploring: [Strudel](https://strudel.cc/), [Sonic Pi](https://sonic-pi.net/), and [SCAMP](https://www.scamp.marcevanstein.com/). This prototype uses its own small score and synthesis implementation; it does not wrap those engines.
|
|
322
350
|
|
|
@@ -10,7 +10,7 @@ then carry the rendered WAV into your video, game or slides. See the
|
|
|
10
10
|
[creative workflow integrations](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/integrations.md) for Codex, Claude Code
|
|
11
11
|
and Hyperframes recipes.
|
|
12
12
|
|
|
13
|
-
**Status:
|
|
13
|
+
**Status: 0.2.0 alpha, [available on PyPI](https://pypi.org/project/audio-as-code/0.2.0/).** Python 3.10+, a versioned JSON score, a headless CLI, and an offline synthesis backend. No API keys, audio device, model weights, sample downloads, or DAW are required.
|
|
14
14
|
|
|
15
15
|
A **score** is the editable recipe for a piece: instruments, notes, timing,
|
|
16
16
|
volume and tempo. Write it in Python or JSON; rendering turns it into audio.
|
|
@@ -63,17 +63,31 @@ To browse and audition the catalog, run `uv run python examples/instrument_brows
|
|
|
63
63
|
For a coding agent, start with: **“Read https://audioascode.com/llms.txt and add
|
|
64
64
|
Audio as Code to this project. Then compose the music in my brief.”**
|
|
65
65
|
|
|
66
|
-
To add
|
|
66
|
+
To add version 0.2.0 to an existing uv project:
|
|
67
67
|
|
|
68
68
|
```sh
|
|
69
|
-
uv add "audio-as-code
|
|
69
|
+
uv add "audio-as-code==0.2.0"
|
|
70
70
|
uv run --locked aac instruments
|
|
71
71
|
uv run --locked aac schema
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Commit your project's `uv.lock` to preserve the
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
Commit your project's `uv.lock` to preserve the package and dependency versions.
|
|
75
|
+
For a new composition project, run `uvx --from audio-as-code==0.2.0 aac init my-soundtrack`,
|
|
76
|
+
then `cd my-soundtrack`, `uv sync`, `uv run aac doctor` and `uv run python compose.py`.
|
|
77
|
+
The starter includes an editable composer, JSON score and agent instructions.
|
|
78
|
+
|
|
79
|
+
For a one-off CLI check, run `uvx --from audio-as-code==0.2.0 aac --help`.
|
|
80
|
+
The package contains the Python library and CLI. Use a source checkout or the
|
|
81
|
+
website's source ZIP for the complete examples, guides and portable skill.
|
|
82
|
+
|
|
83
|
+
Without uv, create a local environment with `python -m venv .venv`. On Windows,
|
|
84
|
+
run `.venv/Scripts/python.exe -m pip install audio-as-code==0.2.0`; on macOS/Linux,
|
|
85
|
+
run `.venv/bin/python -m pip install audio-as-code==0.2.0`. Use that interpreter
|
|
86
|
+
with `-m audio_as_code --help`; no activation or global installation is needed.
|
|
87
|
+
|
|
88
|
+
The [quickstart](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/quickstart.md)
|
|
89
|
+
also covers first compositions and installation from a pinned Git revision.
|
|
90
|
+
The standalone source-workspace route follows below.
|
|
77
91
|
|
|
78
92
|
For complete video, game-loop and presentation examples with exact durations and
|
|
79
93
|
verified revisions, see [creative workflows](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/creative-workflows.md).
|
|
@@ -242,6 +256,18 @@ render(song, "output/string.wav")
|
|
|
242
256
|
|
|
243
257
|
Brightness and decay work on the modeled decaying voices. Pluck position is available on plucked-string models and harpsichord. Held voices expose selected breath/vibrato controls; `aac instruments` reports each voice's supported settings and defaults. Velocity changes loudness and model brightness. By default notes fade within their score duration. Optional note/track `release_seconds` lets a sound ring after note-off. Ordered `tempo_map` steps, gain/pan automation, and procedural delay/reverb chains add expression; see the [composition guide](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/composition.md).
|
|
244
258
|
|
|
259
|
+
Use [arrangement helpers](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/arrangement.md)
|
|
260
|
+
to place cues by seconds across tempo changes, replace named sections and measure
|
|
261
|
+
repeated loop previews. Section names remain outside the version 1 score JSON.
|
|
262
|
+
Thirteen bowed-string and wind voices support optional `soft` and `accented`
|
|
263
|
+
[articulations](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/articulations.md).
|
|
264
|
+
Existing scores keep their sound and score identity when these controls are omitted.
|
|
265
|
+
|
|
266
|
+
[Production output](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/production-output.md)
|
|
267
|
+
adds higher-resolution WAV, exact-context excerpts, progress callbacks and
|
|
268
|
+
cooperative cancellation. Install `audio-as-code[loudness]` for optional integrated
|
|
269
|
+
LUFS targeting with a sample-peak ceiling. There is no true-peak limiter.
|
|
270
|
+
|
|
245
271
|
## An agent's working loop
|
|
246
272
|
|
|
247
273
|
1. Read the schema, discover available voices with `aac instruments`, and choose tempo, harmony, rhythm, and arrangement.
|
|
@@ -268,7 +294,7 @@ can report an export blocker, so check its readiness fields.
|
|
|
268
294
|
|
|
269
295
|
## Rendering and export
|
|
270
296
|
|
|
271
|
-
- WAV:
|
|
297
|
+
- WAV: stereo PCM16 by default, with explicit PCM24 and float32 exports. Rendering happens offline in memory and is limited to five minutes including tails. Excerpt previews preserve full-song context and still cost a full render; compose shorter test scores to reduce iteration time.
|
|
272
298
|
- Default normalization only reduces gain when the mix exceeds a 0.95 peak ceiling. It never increases a quiet mix. `--no-normalize` preserves mix gain and hard-clips values outside full scale on WAV export; the report exposes this.
|
|
273
299
|
- Stems include track gain, pan, master gain, and the same attenuation applied to the mix. They approximately reconstruct the mix within PCM rounding when there are no master effects and no individual stem clips. Stems include track effects but omit master effects, with a warning when the mix uses them. Numeric filenames avoid interpreting track names as paths.
|
|
274
300
|
- MIDI: type 1, 480 ticks per quarter note, tempo, track names, programs, volume, and pan. Up to 15 melodic tracks and one track per drum instrument. Overlapping notes of the same pitch on one channel are rejected, including collisions between a drum kit and an individual drum. Quantization can differ from WAV timing by a MIDI tick.
|
|
@@ -281,14 +307,14 @@ can report an export blocker, so check its readiness fields.
|
|
|
281
307
|
|
|
282
308
|
The framework's instrument direction is sound generated from code. The score is the extension boundary for more detailed physical models. Proposed next steps:
|
|
283
309
|
|
|
284
|
-
-
|
|
310
|
+
- Continuous tempo ramps, variable-length arrangement edits and clip scheduling.
|
|
285
311
|
- Coupled string/body, nonlinear reed/bore, and bow-friction models beyond the current approximations.
|
|
286
312
|
- Half-pedaling, instrument articulation switches, and more expression controls.
|
|
287
313
|
- An MCP server wrapping the existing validate/render/analyze operations.
|
|
288
|
-
-
|
|
314
|
+
- Faster bounded previews, true-peak analysis and agent-assisted audition workflows.
|
|
289
315
|
- Streaming renders, real-time playback, and a browser editor.
|
|
290
316
|
|
|
291
|
-
These are roadmap items, not implemented features. The 0.
|
|
317
|
+
These are roadmap items, not implemented features. The 0.2 API may change; score schema changes will use an explicit version.
|
|
292
318
|
|
|
293
319
|
Related projects worth exploring: [Strudel](https://strudel.cc/), [Sonic Pi](https://sonic-pi.net/), and [SCAMP](https://www.scamp.marcevanstein.com/). This prototype uses its own small score and synthesis implementation; it does not wrap those engines.
|
|
294
320
|
|
|
@@ -2,11 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Start with the [portable composition skill](../skills/audio-as-code/SKILL.md).
|
|
4
4
|
It covers setup, interpreting a musical brief, composing, rendering, checking,
|
|
5
|
-
delivering files, and revisions.
|
|
6
|
-
|
|
5
|
+
delivering files, and revisions. Add the released library to an existing uv project:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
uv add "audio-as-code==0.2.0"
|
|
9
|
+
uv run --locked aac instruments
|
|
10
|
+
uv run --locked aac schema
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Keep the project's lockfile. The package contains the engine and CLI; get the
|
|
14
|
+
portable skill and complete examples from the source checkout or website's source
|
|
15
|
+
ZIP. See the [quickstart](site/quickstart.md) for pip and pinned Git alternatives.
|
|
16
|
+
For a new composition workspace, the installed CLI can scaffold one with
|
|
17
|
+
`aac init "my music"`; run `aac doctor` to check its imported runtime. Init does
|
|
18
|
+
not install dependencies or overwrite a nonempty directory. See
|
|
19
|
+
[project setup](project-setup.md).
|
|
20
|
+
Give your agent the skill and say:
|
|
7
21
|
|
|
8
22
|
```text
|
|
9
|
-
Read
|
|
23
|
+
Read the Audio as Code skill and use the installed dependency in this project
|
|
10
24
|
to create an original 30-second hopeful instrumental for piano, marimba and bass.
|
|
11
25
|
Build a memorable motif, vary the second phrase, and finish with a clear cadence.
|
|
12
26
|
Set up locally, validate and render, then deliver WAV, MIDI, editable score,
|
|
@@ -38,6 +52,8 @@ The CLI is the initial agent integration. Call it through your existing shell to
|
|
|
38
52
|
| Operation | Command | JSON response |
|
|
39
53
|
| --- | --- | --- |
|
|
40
54
|
| Identify engine | `aac --version` | Engine version |
|
|
55
|
+
| Start a project | `aac init "my music"` | Output directory, created filenames and next steps |
|
|
56
|
+
| Check setup | `aac doctor` | `ok`, per-check results and recovery hints |
|
|
41
57
|
| Discover playable voices | `aac instruments` | Families, engines, available voices, controls, MIDI mappings |
|
|
42
58
|
| Inspect a family | `aac instruments --all --family woodwinds` | Catalog entries with explicit availability status |
|
|
43
59
|
| Discover score shape | `aac schema` | JSON Schema |
|
|
@@ -45,18 +61,22 @@ The CLI is the initial agent integration. Call it through your existing shell to
|
|
|
45
61
|
| Check score | `aac validate song.json` | Validity, tempo, length, track/note counts |
|
|
46
62
|
| Inspect before rendering | `aac inspect song.json` | Per-track timing, pitches, polyphony, export readiness and structured issues |
|
|
47
63
|
| Render | `aac render song.json -o song.wav --report report.json` | Render metadata, measurements, warnings |
|
|
64
|
+
| Preview excerpt | `aac preview song.json -o excerpt.wav --start 12 --duration 8` | Excerpt report with full-render context; costs a complete render |
|
|
48
65
|
| Render stems | `aac render song.json -o song.wav --stems stems` | Same report plus track-to-file mapping |
|
|
66
|
+
| Production WAV | `aac render song.json -o song.wav --format pcm24` | 24-bit PCM report; `float32` is also available |
|
|
67
|
+
| Target loudness | `aac render song.json -o song.wav --target-lufs -18` | Optional `loudness` report; requires the `loudness` extra |
|
|
68
|
+
| Live progress | `aac render song.json -o song.wav --progress-file progress.jsonl` | JSON Lines progress in the file; one final JSON object on stdout |
|
|
49
69
|
| Export | `aac midi song.json -o song.mid` | MIDI path, duration, export warnings |
|
|
50
70
|
| Check audio | `aac analyze song.wav` | Signal measurements |
|
|
51
71
|
|
|
52
72
|
MIDI warnings describe features present in the score, such as tone controls,
|
|
53
73
|
percussion or effects. The receiver determines instrument sound in every MIDI export.
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
In a uv project, prefix commands with `uv run --locked`;
|
|
76
|
+
`uv run --locked python -m audio_as_code` is an equivalent entry point. For a
|
|
77
|
+
source checkout, run `uv sync --locked` first and work from its root. With pip,
|
|
78
|
+
invoke the local environment's Python with `-m audio_as_code`. Commands need no
|
|
79
|
+
network after installation.
|
|
60
80
|
|
|
61
81
|
Output files are replaced if they already exist. Input score, output audio,
|
|
62
82
|
report, and stem filenames must be distinct, including symlink and existing
|
|
@@ -125,7 +145,8 @@ Pass arguments as an array and avoid `shell=True`. Resolve relative score and
|
|
|
125
145
|
output paths against your chosen working directory. A missing executable,
|
|
126
146
|
process timeout, or termination is a process-level failure; it does not promise
|
|
127
147
|
a JSON diagnostic. Set a larger timeout for renders based on score length and
|
|
128
|
-
voice complexity.
|
|
148
|
+
voice complexity. Compose shorter candidate scores to bound iteration time;
|
|
149
|
+
`aac preview` crops a full render, so a short excerpt is not cheaper to synthesize.
|
|
129
150
|
|
|
130
151
|
Keep discovery separate from rendering: `aac instruments` returns playable
|
|
131
152
|
voices, `aac instruments --all` can also describe planned voices, and
|
|
@@ -142,14 +163,15 @@ Validate before rendering. Render a short arrangement, check the report, and aud
|
|
|
142
163
|
|
|
143
164
|
Report fields:
|
|
144
165
|
|
|
145
|
-
- `before_gain`: float mix measurements before
|
|
146
|
-
- `gain_applied`: global
|
|
147
|
-
|
|
148
|
-
- `
|
|
166
|
+
- `before_gain`: float mix measurements before the selected mix gain.
|
|
167
|
+
- `gain_applied`: global gain; at most 1 with default peak attenuation, but it may
|
|
168
|
+
exceed 1 when optional LUFS targeting boosts the mix.
|
|
169
|
+
- `audio`: float mix measurements after gain, before WAV encoding.
|
|
170
|
+
- `wav`: measurements from the saved WAV, normally 16-bit PCM.
|
|
149
171
|
- `warnings`: silence, gain reduction, clipping, or sub-sample notes.
|
|
150
172
|
- `score_sha256`, `engine_version`, `numpy_version`, and `seed`: reproduction metadata.
|
|
151
173
|
|
|
152
|
-
`clipped_samples` in float measurements counts individual channel samples with absolute amplitude at least 1.
|
|
174
|
+
`clipped_samples` in float measurements counts individual channel samples with absolute amplitude at least 1. In PCM WAV analysis, `full_scale_samples` counts samples at an integer rail; for float WAV it counts samples with absolute amplitude at least 1 and does not imply clipping. Silence means exact digital zero. dB values are `null` for silence so output remains valid JSON.
|
|
153
175
|
|
|
154
176
|
## Inspecting a score before rendering
|
|
155
177
|
|
|
@@ -180,6 +202,18 @@ All 49 catalog entries have generated prototypes. Read each entry's `description
|
|
|
180
202
|
|
|
181
203
|
The schema accepts no arbitrary extra fields and rejects unsupported versions, non-finite numbers, duplicate track names, out-of-range pitches, and notes extending beyond the arrangement. Names must be unique within a score. All score timing is in quarter-note beats. Optional `tempo_map` entries change BPM at ordered beat positions. Track gain/pan and song master-gain automation use ordered points with linear or step interpolation. Track/song effects support generated delay and reverb. There is no meter metadata, swing field, clip graph or sample loading. Swing can be expressed by placing individual notes at explicit beat positions.
|
|
182
204
|
|
|
205
|
+
For exact media cues across tempo changes, use `beat_at_seconds` or
|
|
206
|
+
`place_at_seconds`; `Arrangement` and `Section` support bounded named-section
|
|
207
|
+
revisions, and `render_loop_preview` repeats a region for join inspection. These
|
|
208
|
+
are Python helpers around score version 1, not extra JSON fields. The loop
|
|
209
|
+
preview renders the complete score before extracting cycles and still needs
|
|
210
|
+
listening in the target player. See [arrangement](arrangement.md). Track and note
|
|
211
|
+
`articulation` support `soft` and `accented` gestures on 13 bowed-string and wind
|
|
212
|
+
voices; discover support in the catalog and see [articulations](articulations.md).
|
|
213
|
+
Automation point values are absolute gain or pan values, not multipliers. For
|
|
214
|
+
production encodings, optional LUFS targeting, exact excerpts, and progress or
|
|
215
|
+
cancellation, see [production output](production-output.md).
|
|
216
|
+
|
|
183
217
|
For `piano` only, `Track.pedal` accepts ordered `PedalEvent(beat=..., down=True/False)`
|
|
184
218
|
events and MIDI exports binary CC64. This models a binary damper gate, with no
|
|
185
219
|
half-pedaling or sympathetic resonance. Other voices, including `electric_piano`,
|
|
@@ -24,11 +24,15 @@ flowchart TD
|
|
|
24
24
|
| `__init__.py`, `__main__.py` | Public Python exports/version and `python -m audio_as_code` entry point. |
|
|
25
25
|
| `model.py` | Immutable Pydantic score objects, strict validation, pitch conversion, piecewise tempo integration, and release/effect-tail duration. |
|
|
26
26
|
| `pattern.py` | Explicit phrase length and composition transforms, including sequence, repeat, transpose, append, overlay, stretch, velocity scaling, and placement. |
|
|
27
|
+
| `arrangement.py` | Timestamp placement, named section edits and measured loop previews; section metadata stays outside score JSON. |
|
|
28
|
+
| `project_setup.py`, `_project_templates/` | Installed project scaffolds and local runtime diagnostics. |
|
|
27
29
|
| `instruments.py` | Central family/engine catalog, playable versus planned status, tone capabilities, and MIDI mappings. |
|
|
28
30
|
| `inspection.py` | Read-only facts and static WAV/MIDI readiness checks with structured issues; no synthesis or file writes. |
|
|
29
31
|
| `render.py` | Sample scheduling, stable note seeds, track mixing, normalization and render/stem orchestration. |
|
|
30
32
|
| `_voices.py` | Instrument dispatch, electronic voices, drum-kit routing and note envelopes. |
|
|
31
33
|
| `_audio.py` | PCM WAV encoding and signal measurements; independent of score objects and synthesis engines. |
|
|
34
|
+
| `loudness.py` | Optional integrated metering and constant-gain targeting; imports its backend only when requested. |
|
|
35
|
+
| `_render_control.py`, `_cli_progress.py` | Cooperative cancellation, staged audio publication and separate JSON Lines progress. |
|
|
32
36
|
| `_export_rules.py` | Shared sample/tick rounding, percussion pitch mapping and export limits for rendering, MIDI and inspection. |
|
|
33
37
|
| `physical.py` | Original generated string and modal instrument models. |
|
|
34
38
|
| `orchestra.py` | Additional procedural instrument synthesis and source/filter approximations. |
|
|
@@ -59,11 +63,14 @@ half-pedaling and sympathetic resonance are not modeled. See [piano sustain](pia
|
|
|
59
63
|
`schemas/song-v1.schema.json` is generated from `Song.model_json_schema()`. Runtime
|
|
60
64
|
checks additionally enforce unique track names, note bounds, supported controls,
|
|
61
65
|
and cross-field relationships. Schema version `"1"` is independent of package
|
|
62
|
-
version `0.
|
|
66
|
+
version `0.2.0`. Breaking the score contract requires explicit version/migration
|
|
63
67
|
work; adding a Python helper does not automatically change the score schema.
|
|
64
68
|
|
|
65
69
|
Rendering revalidates scores and is limited to 300 seconds including tails. It
|
|
66
|
-
produces stereo float32 audio internally and 16-bit PCM WAV on export
|
|
70
|
+
produces stereo float32 audio internally and defaults to 16-bit PCM WAV on export;
|
|
71
|
+
24-bit PCM and 32-bit float WAV are optional. Context-preserving previews crop a
|
|
72
|
+
complete render. See [production output](production-output.md) for encodings,
|
|
73
|
+
progress, cancellation, and publication behavior. Seeds are
|
|
67
74
|
derived deterministically from score/track/note context. Reproducibility is scoped
|
|
68
75
|
to a fixed environment; changing algorithms or numeric dependencies can change
|
|
69
76
|
samples. Reports include a score hash, engine/NumPy versions, duration, peak/RMS,
|