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.
Files changed (210) hide show
  1. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/check_distribution.py +27 -1
  2. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/smoke_installed.py +64 -0
  3. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/workflows/ci.yml +10 -5
  4. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/workflows/publish.yml +6 -4
  5. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/CHANGELOG.md +28 -3
  6. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/CONTRIBUTING.md +2 -0
  7. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/PKG-INFO +39 -11
  8. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/README.md +36 -10
  9. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/agents.md +48 -14
  10. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/architecture.md +9 -2
  11. audio_as_code-0.2.0/docs/arrangement.md +107 -0
  12. audio_as_code-0.2.0/docs/articulations.md +73 -0
  13. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/cloudflare-hosting.md +9 -5
  14. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/expressive-engine.md +6 -0
  15. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/instrument-foundation.md +5 -2
  16. audio_as_code-0.2.0/docs/production-output.md +142 -0
  17. audio_as_code-0.2.0/docs/project-setup.md +48 -0
  18. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/releasing.md +4 -4
  19. audio_as_code-0.2.0/docs/roadmap.md +41 -0
  20. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/agents.md +46 -15
  21. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/composition.md +15 -5
  22. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/integrations.md +30 -8
  23. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/llms.txt +16 -8
  24. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/quickstart.md +62 -16
  25. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/reference.md +60 -13
  26. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/synthesis.md +6 -3
  27. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/website.md +6 -2
  28. audio_as_code-0.2.0/examples/arrangement_workflow.py +97 -0
  29. audio_as_code-0.2.0/examples/articulations.py +117 -0
  30. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/build_site.py +7 -1
  31. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/classic_reimaginations.py +2 -0
  32. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/classic_showcase.py +2 -0
  33. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/prepare_cloudflare.py +1 -1
  34. audio_as_code-0.2.0/examples/production_output.py +19 -0
  35. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/pyproject.toml +4 -1
  36. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/schemas/song-v1.schema.json +32 -0
  37. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/skills/audio-as-code/SKILL.md +58 -21
  38. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/__init__.py +33 -2
  39. audio_as_code-0.2.0/src/audio_as_code/_audio.py +229 -0
  40. audio_as_code-0.2.0/src/audio_as_code/_cli_progress.py +34 -0
  41. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_orchestra_profiles.py +30 -0
  42. audio_as_code-0.2.0/src/audio_as_code/_project_templates/AGENTS.md.txt +21 -0
  43. audio_as_code-0.2.0/src/audio_as_code/_project_templates/README.md.txt +66 -0
  44. audio_as_code-0.2.0/src/audio_as_code/_project_templates/compose.py.txt +49 -0
  45. audio_as_code-0.2.0/src/audio_as_code/_project_templates/gitignore.txt +4 -0
  46. audio_as_code-0.2.0/src/audio_as_code/_project_templates/pyproject.toml.txt +9 -0
  47. audio_as_code-0.2.0/src/audio_as_code/_project_templates/score.json +240 -0
  48. audio_as_code-0.2.0/src/audio_as_code/_render_control.py +67 -0
  49. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_voices.py +7 -2
  50. audio_as_code-0.2.0/src/audio_as_code/arrangement.py +248 -0
  51. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/cli.py +118 -15
  52. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/inspection.py +10 -0
  53. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/instruments.py +14 -0
  54. audio_as_code-0.2.0/src/audio_as_code/loudness.py +128 -0
  55. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/midi.py +5 -0
  56. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/model.py +14 -0
  57. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/orchestra.py +37 -2
  58. audio_as_code-0.2.0/src/audio_as_code/project_setup.py +186 -0
  59. audio_as_code-0.2.0/src/audio_as_code/render.py +488 -0
  60. audio_as_code-0.2.0/tests/test_arrangement.py +148 -0
  61. audio_as_code-0.2.0/tests/test_articulation_exports.py +38 -0
  62. audio_as_code-0.2.0/tests/test_articulations.py +172 -0
  63. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_cloudflare_staging.py +1 -3
  64. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_distribution_contract.py +13 -0
  65. audio_as_code-0.2.0/tests/test_loudness.py +188 -0
  66. audio_as_code-0.2.0/tests/test_production_cli.py +136 -0
  67. audio_as_code-0.2.0/tests/test_production_output.py +322 -0
  68. audio_as_code-0.2.0/tests/test_project_setup.py +225 -0
  69. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_site_docs.py +1 -1
  70. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/uv.lock +232 -1
  71. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/source.html +1 -1
  72. audio_as_code-0.1.0/src/audio_as_code/_audio.py +0 -87
  73. audio_as_code-0.1.0/src/audio_as_code/render.py +0 -230
  74. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/FUNDING.yml +0 -0
  75. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  76. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  77. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  78. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  79. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/dependabot.yml +0 -0
  80. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/check_schema.py +0 -0
  81. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.github/scripts/smoke_distribution.py +0 -0
  82. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/.gitignore +0 -0
  83. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/AGENTS.md +0 -0
  84. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/LICENSE +0 -0
  85. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/SECURITY.md +0 -0
  86. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/analytics.md +0 -0
  87. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/classic-reimaginations.md +0 -0
  88. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/classic-showcase.md +0 -0
  89. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/composition-tools.md +0 -0
  90. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/creative-workflows.md +0 -0
  91. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/extended-instruments.md +0 -0
  92. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/full-compositions.md +0 -0
  93. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/music-demos.md +0 -0
  94. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/orchestra.md +0 -0
  95. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/piano-sustain.md +0 -0
  96. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/render-performance.md +0 -0
  97. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/score-inspection.md +0 -0
  98. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/01_first_score.py +0 -0
  99. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/02_motif_and_progression.py +0 -0
  100. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/03_song_form.py +0 -0
  101. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/04_expressive_controls.py +0 -0
  102. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/05_agent_loop.py +0 -0
  103. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/06_agent_handoff.py +0 -0
  104. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/site/examples/agent-score.json +0 -0
  105. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/docs/website-discovery.md +0 -0
  106. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/_site_discovery.py +0 -0
  107. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/benchmark_render.py +0 -0
  108. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/composition_tools.py +0 -0
  109. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/creative_workflows.py +0 -0
  110. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/expressive-score.json +0 -0
  111. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/expressive_engine.py +0 -0
  112. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/famous_music.py +0 -0
  113. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/first_light.py +0 -0
  114. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/full_compositions.py +0 -0
  115. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/instrument_browser.py +0 -0
  116. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/README.md +0 -0
  117. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/build_classics_full.py +0 -0
  118. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/build_excerpts.py +0 -0
  119. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-full.json +0 -0
  120. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007-viola.ly +0 -0
  121. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007.ly +0 -0
  122. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_allemande.ly +0 -0
  123. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_courante.ly +0 -0
  124. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_gigue.ly +0 -0
  125. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_menuet_i.ly +0 -0
  126. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_menuet_ii.ly +0 -0
  127. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_prelude.ly +0 -0
  128. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-bwv1007_sarabande.ly +0 -0
  129. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/cello_prelude-header.ily +0 -0
  130. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/fur_elise-fur_elise.ly +0 -0
  131. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/greensleeves-greensleeves.ly +0 -0
  132. {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
  133. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/turkish_march-KV331_3_RondoAllaTurca.ly +0 -0
  134. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/classics-sources/turkish_march-KV331_3_RondoAllaTurcaNotes.ly +0 -0
  135. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/music/excerpts.json +0 -0
  136. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/physical_instruments.py +0 -0
  137. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/piano_sustain.py +0 -0
  138. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/serve_site.py +0 -0
  139. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/examples/showcase.py +0 -0
  140. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/__main__.py +0 -0
  141. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_export_rules.py +0 -0
  142. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/_paths.py +0 -0
  143. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/acoustics.py +0 -0
  144. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/automation.py +0 -0
  145. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/demo.py +0 -0
  146. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/effects.py +0 -0
  147. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/extended.py +0 -0
  148. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/pattern.py +0 -0
  149. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/physical.py +0 -0
  150. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/src/audio_as_code/py.typed +0 -0
  151. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_acoustics.py +0 -0
  152. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_agent_handoff.py +0 -0
  153. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_agent_loop.py +0 -0
  154. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_agent_readiness.py +0 -0
  155. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_classic_showcase.py +0 -0
  156. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_cli_dx.py +0 -0
  157. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_core_export_regressions.py +0 -0
  158. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_creative_workflows.py +0 -0
  159. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_demo_performance.py +0 -0
  160. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_expressive_engine.py +0 -0
  161. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_extended_instruments.py +0 -0
  162. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_finite_strikes.py +0 -0
  163. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_full_compositions.py +0 -0
  164. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_instrument_refinement.py +0 -0
  165. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_instruments.py +0 -0
  166. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_midi_cli.py +0 -0
  167. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_midi_warnings.py +0 -0
  168. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_music_demos.py +0 -0
  169. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_orchestra.py +0 -0
  170. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_output_preflight.py +0 -0
  171. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_pattern_composition.py +0 -0
  172. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_physical.py +0 -0
  173. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_piano_sustain.py +0 -0
  174. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_render.py +0 -0
  175. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_render_performance.py +0 -0
  176. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_score.py +0 -0
  177. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_score_inspection.py +0 -0
  178. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_security_regressions.py +0 -0
  179. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_serve_site.py +0 -0
  180. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_site_analytics.py +0 -0
  181. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/tests/test_site_discovery.py +0 -0
  182. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/media-handler.js +0 -0
  183. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/media-handler.test.js +0 -0
  184. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/package-lock.json +0 -0
  185. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/package.json +0 -0
  186. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/cloudflare/worker.js +0 -0
  187. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/instrument-browser.html +0 -0
  188. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/404.html +0 -0
  189. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/analytics-config.json +0 -0
  190. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/agent-handoff.js +0 -0
  191. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/analytics.js +0 -0
  192. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/Anybody-OFL.txt +0 -0
  193. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/Archivo-OFL.txt +0 -0
  194. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/FONTS.txt +0 -0
  195. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/JetBrainsMono-OFL.txt +0 -0
  196. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/anybody-latin.woff2 +0 -0
  197. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/archivo-latin.woff2 +0 -0
  198. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/fonts/jetbrains-mono-latin.woff2 +0 -0
  199. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/icon.svg +0 -0
  200. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/ring.svg +0 -0
  201. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/site.css +0 -0
  202. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/site.js +0 -0
  203. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/social.png +0 -0
  204. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/social.svg +0 -0
  205. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/assets/torn.svg +0 -0
  206. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/identity.json +0 -0
  207. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/index.html +0 -0
  208. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/layout.html +0 -0
  209. {audio_as_code-0.1.0 → audio_as_code-0.2.0}/web/site/snippets/loop.py +0 -0
  210. {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
- names = {re.split(r"[<>=!~;\s\[]", item, maxsplit=1)[0].lower() for item in requirements}
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@11d5960a326750d5838078e36cf38b85af677262 # v4
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@11d5960a326750d5838078e36cf38b85af677262 # v4
40
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
41
41
  with:
42
42
  persist-credentials: false
43
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
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@11d5960a326750d5838078e36cf38b85af677262 # v4
60
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57
61
  with:
58
62
  persist-credentials: false
59
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
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@11d5960a326750d5838078e36cf38b85af677262 # v4
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
26
  with:
27
27
  persist-credentials: false
28
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
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@11d5960a326750d5838078e36cf38b85af677262 # v4
94
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
94
95
  with:
95
96
  persist-credentials: false
96
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
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
- This project is an unpublished prototype. `0.1.0` identifies the current development
4
- baseline; it is not a claim that a package or tagged release exists. Changes below
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.1.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: working 0.1 prototype.** 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.
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 it to an existing uv project with Git installed:
96
+ To add version 0.2.0 to an existing uv project:
95
97
 
96
98
  ```sh
97
- uv add "audio-as-code @ git+https://github.com/joaoCarvalho1000/audio-as-code.git"
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 resolved Git revision and
103
- dependency versions. See the [quickstart](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/quickstart.md) for a first
104
- composition and the standalone source-workspace route below.
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: 16-bit stereo PCM, synthesized offline in memory. The prototype limits each render, including release/effect tails, to five minutes. A five-minute render can consume hundreds of MB of RAM; shorter previews are preferable in agent loops.
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
- - Named clips and sections, continuous tempo ramps, and arrangement edits.
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
- - Richer analysis, preview excerpts, and agent-assisted audition workflows.
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.1 API may change; score schema changes will use an explicit version.
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: working 0.1 prototype.** 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.
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 it to an existing uv project with Git installed:
66
+ To add version 0.2.0 to an existing uv project:
67
67
 
68
68
  ```sh
69
- uv add "audio-as-code @ git+https://github.com/joaoCarvalho1000/audio-as-code.git"
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 resolved Git revision and
75
- dependency versions. See the [quickstart](https://github.com/joaoCarvalho1000/audio-as-code/blob/main/docs/site/quickstart.md) for a first
76
- composition and the standalone source-workspace route below.
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: 16-bit stereo PCM, synthesized offline in memory. The prototype limits each render, including release/effect tails, to five minutes. A five-minute render can consume hundreds of MB of RAM; shorter previews are preferable in agent loops.
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
- - Named clips and sections, continuous tempo ramps, and arrangement edits.
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
- - Richer analysis, preview excerpts, and agent-assisted audition workflows.
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.1 API may change; score schema changes will use an explicit version.
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. Give your agent this checkout (or the website's
6
- source ZIP) and say:
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 skills/audio-as-code/SKILL.md in the Audio as Code source folder and use it
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
- When running from the source checkout, first run `uv sync --locked`, then prefix
56
- commands with `uv run`. `uv run python -m audio_as_code` is an equivalent entry
57
- point. The package is not published to PyPI; use the local checkout. Commands
58
- need no network after installation. Run from the checkout root, or invoke the
59
- installed environment's Python by its absolute path.
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. Start with short previews to bound memory and iteration time.
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 optional peak attenuation.
146
- - `gain_applied`: global attenuation, at most 1.
147
- - `audio`: float mix measurements after attenuation, before PCM quantization.
148
- - `wav`: measurements from the saved 16-bit file.
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. `full_scale_samples` in WAV analysis counts samples at a PCM rail; this is evidence of full-scale values, not proof of upstream clipping. Silence means exact digital zero. dB values are `null` for silence so output remains valid JSON.
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.1.0`. Breaking the score contract requires explicit version/migration
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. Seeds are
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,