danceflow-stemlab 1.3.4__tar.gz → 1.4.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 (172) hide show
  1. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/PKG-INFO +63 -1
  2. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/README.md +60 -0
  3. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/mcp-registry/server.json +2 -2
  4. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/.codex-plugin/plugin.json +1 -1
  5. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/plugin.json +1 -1
  6. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/skills/stemlab/SKILL.md +6 -0
  7. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/pyproject.toml +2 -0
  8. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/danceflow_stemlab.egg-info/PKG-INFO +63 -1
  9. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/danceflow_stemlab.egg-info/SOURCES.txt +5 -0
  10. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/danceflow_stemlab.egg-info/requires.txt +3 -0
  11. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/__init__.py +1 -1
  12. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/loops.py +25 -10
  13. danceflow_stemlab-1.4.0/src/stemlab/analysis/phrase_loops.py +290 -0
  14. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/registry.py +10 -0
  15. danceflow_stemlab-1.4.0/src/stemlab/analysis/rudiment_matches.py +461 -0
  16. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/runner.py +16 -0
  17. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/cli.py +72 -0
  18. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/pipeline.py +3 -0
  19. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/types.py +4 -0
  20. danceflow_stemlab-1.4.0/tests/test_arcadians_rudiments.py +39 -0
  21. danceflow_stemlab-1.4.0/tests/test_phrase_loops.py +181 -0
  22. danceflow_stemlab-1.4.0/tests/test_rudiment_matches.py +123 -0
  23. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/.agents/plugins/marketplace.json +0 -0
  24. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/.github/workflows/codex-plugin.yml +0 -0
  25. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/.github/workflows/release.yml +0 -0
  26. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/AGENTS.md +0 -0
  27. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/CONTRIBUTING.md +0 -0
  28. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/LICENSE +0 -0
  29. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/MANIFEST.in +0 -0
  30. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/branding/README.md +0 -0
  31. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/branding/logo-monochrome.svg +0 -0
  32. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/branding/logo.png +0 -0
  33. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/branding/logo.svg +0 -0
  34. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/codex-plugin.md +0 -0
  35. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/codex-publishing.md +0 -0
  36. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/cue-alignment.md +0 -0
  37. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/evidence-models.md +0 -0
  38. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/docs/midi.md +0 -0
  39. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/.mcp.json +0 -0
  40. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/README.md +0 -0
  41. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/branding/logo-monochrome.svg +0 -0
  42. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/branding/logo.png +0 -0
  43. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/branding/logo.svg +0 -0
  44. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/mcp.json +0 -0
  45. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/plugins/stemlab/skills/stemlab/references/tools.md +0 -0
  46. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/benchmark_evidence_corpus.py +0 -0
  47. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/build_codex_plugin.py +0 -0
  48. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/capture_loop_workflow.py +0 -0
  49. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/check_codex_timeline.py +0 -0
  50. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/loop_demo.py +0 -0
  51. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/prepare_codex_marketplace.py +0 -0
  52. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/publish_mcp_registry.py +0 -0
  53. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/scripts/smoke_midi.py +0 -0
  54. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/setup.cfg +0 -0
  55. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/danceflow_stemlab.egg-info/dependency_links.txt +0 -0
  56. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/danceflow_stemlab.egg-info/entry_points.txt +0 -0
  57. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/danceflow_stemlab.egg-info/top_level.txt +0 -0
  58. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/__main__.py +0 -0
  59. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/__init__.py +0 -0
  60. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/evidence/__init__.py +0 -0
  61. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/evidence/adapters.py +0 -0
  62. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/evidence/registry.py +0 -0
  63. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/evidence/runner.py +0 -0
  64. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/evidence/worker.py +0 -0
  65. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/harmony.py +0 -0
  66. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/lyrics.py +0 -0
  67. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/midi/__init__.py +0 -0
  68. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/midi/adapters.py +0 -0
  69. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/midi/artifacts.py +0 -0
  70. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/midi/registry.py +0 -0
  71. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/midi/runner.py +0 -0
  72. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/midi/worker.py +0 -0
  73. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/rhythm.py +0 -0
  74. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/semantic_audio.py +0 -0
  75. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/semantic_text.py +0 -0
  76. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/song_map.py +0 -0
  77. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/sonic.py +0 -0
  78. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/structure.py +0 -0
  79. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/tempo_regimes.py +0 -0
  80. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/analysis/transcription.py +0 -0
  81. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/audio.py +0 -0
  82. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/beats/__init__.py +0 -0
  83. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/beats/base.py +0 -0
  84. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/beats/beatnet.py +0 -0
  85. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/beats/beatthis.py +0 -0
  86. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/beats/beattransformer.py +0 -0
  87. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/beats/common.py +0 -0
  88. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/bootstrap.py +0 -0
  89. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/branding.py +0 -0
  90. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/canonical.py +0 -0
  91. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/__init__.py +0 -0
  92. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/__main__.py +0 -0
  93. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/bridge.py +0 -0
  94. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/jobs.py +0 -0
  95. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/paths.py +0 -0
  96. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/server.py +0 -0
  97. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/tasks.py +0 -0
  98. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/timeline.py +0 -0
  99. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/codex/worker.py +0 -0
  100. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/cue_cli.py +0 -0
  101. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/cues.py +0 -0
  102. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/lyric_lrc.py +0 -0
  103. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/manifest.py +0 -0
  104. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/models.py +0 -0
  105. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/__init__.py +0 -0
  106. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/base.py +0 -0
  107. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/bsroformer.py +0 -0
  108. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/common.py +0 -0
  109. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/demucs.py +0 -0
  110. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/openunmix.py +0 -0
  111. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/separators/scnet.py +0 -0
  112. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/server.py +0 -0
  113. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/sonic_visualiser.py +0 -0
  114. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/spectrogram.py +0 -0
  115. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/spectrogram_repair.py +0 -0
  116. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/speech.py +0 -0
  117. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/util.py +0 -0
  118. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/vamp.py +0 -0
  119. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/vamp_runtime.py +0 -0
  120. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/web/app.js +0 -0
  121. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/web/arcadians-cover.jpg +0 -0
  122. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/web/arcadians-reference.json +0 -0
  123. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/web/index.html +0 -0
  124. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/web/style.css +0 -0
  125. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/web/timeline.css +0 -0
  126. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/webui.py +0 -0
  127. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/src/stemlab/word_alignment.py +0 -0
  128. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_analysis_registry.py +0 -0
  129. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_arcadians_fixture.py +0 -0
  130. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_audio_normalization.py +0 -0
  131. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_beat_consensus.py +0 -0
  132. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_beat_this_bootstrap.py +0 -0
  133. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_beatnet_bootstrap.py +0 -0
  134. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_beatnet_compat.py +0 -0
  135. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_branding.py +0 -0
  136. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_bsroformer_adapter.py +0 -0
  137. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_canonical.py +0 -0
  138. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_canonical_extended.py +0 -0
  139. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_codex_bridge.py +0 -0
  140. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_codex_jobs.py +0 -0
  141. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_codex_package.py +0 -0
  142. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_codex_protocol.py +0 -0
  143. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_codex_release.py +0 -0
  144. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_codex_timeline.py +0 -0
  145. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_cue_integration.py +0 -0
  146. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_cues.py +0 -0
  147. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_deep_harmony.py +0 -0
  148. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_deep_lyrics.py +0 -0
  149. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_deep_sonic_rhythm.py +0 -0
  150. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_distribution_identity.py +0 -0
  151. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_docker_contract.py +0 -0
  152. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_evidence_models.py +0 -0
  153. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_loop_integration.py +0 -0
  154. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_loops.py +0 -0
  155. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_lyric_lrc.py +0 -0
  156. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_manifest.py +0 -0
  157. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_midi_adapters.py +0 -0
  158. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_midi_extraction.py +0 -0
  159. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_midi_integration.py +0 -0
  160. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_midi_registry.py +0 -0
  161. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_models.py +0 -0
  162. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_repository_hygiene.py +0 -0
  163. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_server.py +0 -0
  164. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_song_map.py +0 -0
  165. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_sonic_visualiser.py +0 -0
  166. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_sonic_visualiser_workspace.py +0 -0
  167. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_spectrogram.py +0 -0
  168. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_speech_repetition.py +0 -0
  169. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_vamp.py +0 -0
  170. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_vamp_runtime.py +0 -0
  171. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_web_frontend.py +0 -0
  172. {danceflow_stemlab-1.3.4 → danceflow_stemlab-1.4.0}/tests/test_word_alignment.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: danceflow-stemlab
3
- Version: 1.3.4
3
+ Version: 1.4.0
4
4
  Summary: Local music analysis for stems, timing, beats, harmony, lyrics, MIDI and loopable sections for Shorts. https://kieransimkin.co.uk/
5
5
  Author: Kieran Simkin
6
6
  Maintainer: Kieran Simkin
@@ -38,6 +38,8 @@ Requires-Dist: platformdirs>=4.2
38
38
  Requires-Dist: librosa>=0.11
39
39
  Requires-Dist: pyloudnorm>=0.2
40
40
  Requires-Dist: cmudict>=1.1.3
41
+ Provides-Extra: rudiments
42
+ Requires-Dist: dancerudiments<1,>=0.2.4; extra == "rudiments"
41
43
  Provides-Extra: codex
42
44
  Requires-Dist: mcp<3,>=2.2; extra == "codex"
43
45
  Requires-Dist: fastapi<1,>=0.115; extra == "codex"
@@ -255,6 +257,55 @@ The derived artifacts live under `deep/` (`sonic/`, `rhythm/`, `harmony/`, `stru
255
257
  `lyrics/`, `semantic_text/`, optional `semantic_audio/` and `basic_pitch/`, plus
256
258
  `song_map/song_map.json` and `summary.json`).
257
259
 
260
+ ## Find DanceRudiments matching candidates from each stem
261
+
262
+ Install the separately maintained **optional** native package with
263
+ `python -m pip install -e ".[rudiments]"`. StemLab calls
264
+ [`dancerudiments.catalogue()` and `dancerudiments.sample()`](https://github.com/kieransimkin/DanceRudiments)
265
+ directly—no cloned movement functions or bundled catalogue. It extracts per-stem
266
+ RMS attack peaks, maps them onto detected beats, and ranks 16-beat windows
267
+ against position/speed/acceleration salience of actual native motion samples.
268
+ Scores are **comparative similarities**, not probabilities or guarantees of a good motion.
269
+
270
+ ```sh
271
+ # Match the mix plus every saved stem without running separation again.
272
+ stemlab rudiments ./analysis-master --top 20
273
+ # Limit an existing analysis to two stems, or choose one of six official collections.
274
+ stemlab rudiments ./analysis-master --stem drums --stem bass --collection club
275
+ # Include matching as an opt-in part of a new full analysis.
276
+ stemlab analyze master.wav --output ./analysis-master --rudiments
277
+ # The bundled Arcadians MP3 has a documented BPM but no pre-separated stems.
278
+ stemlab rudiments 'examples/arcadians/Arcadians - 320kbps.mp3' \
279
+ --bpm 145 --collection initial --top 28 --output ./arcadians-rudiments
280
+ ```
281
+
282
+ ### Measured Arcadians benchmark
283
+
284
+ The mix-only offline benchmark found **1,821** energy-rise peaks and **15 candidate
285
+ patterns at ≥0.68** similarity among the authentic 28-pattern DanceRudiments
286
+ initial collection, led by `groove_b_played` (0.739), `groove_b_grid` (0.737),
287
+ and `lfo_morph` (0.724). The benchmark read DanceRudiments' officially compiled
288
+ sample tables through an **offline adapter**; the native C++-extension test is
289
+ separate and skips when that optional package is not installed. The BPM grid
290
+ starts at zero with **unverified beat phase**. No stem separation was claimed.
291
+
292
+ These are **actual Chromium screenshots** of the measured local HTML report,
293
+ not schematic illustrations or captures of the main React timeline.
294
+
295
+ ![Actual Arcadians audio attack peaks overlaid with a matching DanceRudiments motion descriptor in local report](docs/screenshots/arcadians-rudiments-report.png)
296
+
297
+ ![Actual ranked Arcadians DanceRudiments candidate table captured in Chromium](docs/screenshots/arcadians-rudiments-ranked.png)
298
+
299
+ The analysis writes `deep/rudiments/report.json`, `index.html`, and `overview.png`.
300
+ See **[method, score caveats, provenance, examples and screenshot reproduction](docs/rudiments.md)**.
301
+
302
+ **Native CI coverage:** the main GitHub Actions `CI` workflow now has a dedicated
303
+ Python 3.10/3.11 job that installs the official DanceRudiments *binary wheel*,
304
+ requires the actual C++ module, and runs the Arcadians test with skipping disabled.
305
+ Fewer than **12 real-audio matches** fails CI; passing runs publish the JSON,
306
+ HTML and measured overlay PNG as downloadable CI artifacts. See the
307
+ [native CI validation instructions](docs/rudiments.md#native-github-actions-gate).
308
+
258
309
  ## Timeline analysis gallery
259
310
 
260
311
  The browser workspace is intended to make the analysis output inspectable, not just downloadable. The illustrations below explain the main analysis families using the same shared timeline vocabulary as the frontend: one clock, one playhead, zoomable lanes, and consistent section alignment across spectrogram, beat, lyric and deep-analysis layers.
@@ -368,6 +419,13 @@ exported WAVs and writes no files. **Zoom to loop** focuses the shared timeline.
368
419
  See [the complete loop workflow](docs/loops.md) for installation of both patches,
369
420
  sample indexing, thresholds, limitations and reproducible screenshot examples.
370
421
 
422
+ For line-complete alternatives when a musically satisfying restart is the goal,
423
+ use `stemlab phrase-loops analysis-master --feedback artist-ratings.json --export-audio`.
424
+ This opt-in search compares the musical context around both cuts, preserves
425
+ canonical lyric occurrences, uses explicit artist feedback and selects diverse
426
+ passages. Suggestions remain separate from strict accepted loops. See
427
+ [phrase-loop methodology, feedback format and synthetic example](docs/phrase-loops.md).
428
+
371
429
  ![Loop selection and playback in the existing analysis frontend](docs/screenshots/loops-playing.png)
372
430
 
373
431
  ## Audio-to-MIDI model comparison
@@ -611,6 +669,10 @@ analysis-master/
611
669
  lyrics/
612
670
  semantic_text/
613
671
  song_map/
672
+ rudiments/ # optional native DanceRudiments candidate report
673
+ report.json
674
+ index.html
675
+ overview.png
614
676
  summary.json
615
677
  sonic_visualiser/
616
678
  session.sv
@@ -130,6 +130,55 @@ The derived artifacts live under `deep/` (`sonic/`, `rhythm/`, `harmony/`, `stru
130
130
  `lyrics/`, `semantic_text/`, optional `semantic_audio/` and `basic_pitch/`, plus
131
131
  `song_map/song_map.json` and `summary.json`).
132
132
 
133
+ ## Find DanceRudiments matching candidates from each stem
134
+
135
+ Install the separately maintained **optional** native package with
136
+ `python -m pip install -e ".[rudiments]"`. StemLab calls
137
+ [`dancerudiments.catalogue()` and `dancerudiments.sample()`](https://github.com/kieransimkin/DanceRudiments)
138
+ directly—no cloned movement functions or bundled catalogue. It extracts per-stem
139
+ RMS attack peaks, maps them onto detected beats, and ranks 16-beat windows
140
+ against position/speed/acceleration salience of actual native motion samples.
141
+ Scores are **comparative similarities**, not probabilities or guarantees of a good motion.
142
+
143
+ ```sh
144
+ # Match the mix plus every saved stem without running separation again.
145
+ stemlab rudiments ./analysis-master --top 20
146
+ # Limit an existing analysis to two stems, or choose one of six official collections.
147
+ stemlab rudiments ./analysis-master --stem drums --stem bass --collection club
148
+ # Include matching as an opt-in part of a new full analysis.
149
+ stemlab analyze master.wav --output ./analysis-master --rudiments
150
+ # The bundled Arcadians MP3 has a documented BPM but no pre-separated stems.
151
+ stemlab rudiments 'examples/arcadians/Arcadians - 320kbps.mp3' \
152
+ --bpm 145 --collection initial --top 28 --output ./arcadians-rudiments
153
+ ```
154
+
155
+ ### Measured Arcadians benchmark
156
+
157
+ The mix-only offline benchmark found **1,821** energy-rise peaks and **15 candidate
158
+ patterns at ≥0.68** similarity among the authentic 28-pattern DanceRudiments
159
+ initial collection, led by `groove_b_played` (0.739), `groove_b_grid` (0.737),
160
+ and `lfo_morph` (0.724). The benchmark read DanceRudiments' officially compiled
161
+ sample tables through an **offline adapter**; the native C++-extension test is
162
+ separate and skips when that optional package is not installed. The BPM grid
163
+ starts at zero with **unverified beat phase**. No stem separation was claimed.
164
+
165
+ These are **actual Chromium screenshots** of the measured local HTML report,
166
+ not schematic illustrations or captures of the main React timeline.
167
+
168
+ ![Actual Arcadians audio attack peaks overlaid with a matching DanceRudiments motion descriptor in local report](docs/screenshots/arcadians-rudiments-report.png)
169
+
170
+ ![Actual ranked Arcadians DanceRudiments candidate table captured in Chromium](docs/screenshots/arcadians-rudiments-ranked.png)
171
+
172
+ The analysis writes `deep/rudiments/report.json`, `index.html`, and `overview.png`.
173
+ See **[method, score caveats, provenance, examples and screenshot reproduction](docs/rudiments.md)**.
174
+
175
+ **Native CI coverage:** the main GitHub Actions `CI` workflow now has a dedicated
176
+ Python 3.10/3.11 job that installs the official DanceRudiments *binary wheel*,
177
+ requires the actual C++ module, and runs the Arcadians test with skipping disabled.
178
+ Fewer than **12 real-audio matches** fails CI; passing runs publish the JSON,
179
+ HTML and measured overlay PNG as downloadable CI artifacts. See the
180
+ [native CI validation instructions](docs/rudiments.md#native-github-actions-gate).
181
+
133
182
  ## Timeline analysis gallery
134
183
 
135
184
  The browser workspace is intended to make the analysis output inspectable, not just downloadable. The illustrations below explain the main analysis families using the same shared timeline vocabulary as the frontend: one clock, one playhead, zoomable lanes, and consistent section alignment across spectrogram, beat, lyric and deep-analysis layers.
@@ -243,6 +292,13 @@ exported WAVs and writes no files. **Zoom to loop** focuses the shared timeline.
243
292
  See [the complete loop workflow](docs/loops.md) for installation of both patches,
244
293
  sample indexing, thresholds, limitations and reproducible screenshot examples.
245
294
 
295
+ For line-complete alternatives when a musically satisfying restart is the goal,
296
+ use `stemlab phrase-loops analysis-master --feedback artist-ratings.json --export-audio`.
297
+ This opt-in search compares the musical context around both cuts, preserves
298
+ canonical lyric occurrences, uses explicit artist feedback and selects diverse
299
+ passages. Suggestions remain separate from strict accepted loops. See
300
+ [phrase-loop methodology, feedback format and synthetic example](docs/phrase-loops.md).
301
+
246
302
  ![Loop selection and playback in the existing analysis frontend](docs/screenshots/loops-playing.png)
247
303
 
248
304
  ## Audio-to-MIDI model comparison
@@ -486,6 +542,10 @@ analysis-master/
486
542
  lyrics/
487
543
  semantic_text/
488
544
  song_map/
545
+ rudiments/ # optional native DanceRudiments candidate report
546
+ report.json
547
+ index.html
548
+ overview.png
489
549
  summary.json
490
550
  sonic_visualiser/
491
551
  session.sv
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.kieransimkin/stemlab",
4
4
  "title": "StemLab \u2014 timing and loops for Shorts",
5
5
  "description": "Music and stem analysis, timing and loopable Shorts. https://kieransimkin.co.uk/",
6
- "version": "1.3.4",
6
+ "version": "1.4.0",
7
7
  "websiteUrl": "https://kieransimkin.co.uk/my-songs/",
8
8
  "repository": {
9
9
  "url": "https://github.com/kieransimkin/stemlab",
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "pypi",
15
15
  "identifier": "danceflow-stemlab",
16
- "version": "1.3.4",
16
+ "version": "1.4.0",
17
17
  "runtimeHint": "uvx",
18
18
  "runtimeArguments": [
19
19
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stemlab",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "Local music analysis for stems, timing, beats, harmony, lyrics, MIDI and loopable sections for Shorts. https://kieransimkin.co.uk/",
5
5
  "skills": "./skills/",
6
6
  "mcpServers": "./.mcp.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "stemlab",
4
- "version": "1.3.4",
4
+ "version": "1.4.0",
5
5
  "description": "Local music analysis for stems, timing, beats, harmony, lyrics, MIDI and loopable sections for Shorts. https://kieransimkin.co.uk/",
6
6
  "author": {
7
7
  "name": "Kieran Simkin",
@@ -39,6 +39,12 @@ Created by Kieran Simkin — https://kieransimkin.co.uk/my-songs/
39
39
  9. Quote `start_sample` inclusive and `end_sample` exclusive in native source sample
40
40
  frames, not interleaved channel values. Do not move cuts to force a preferred
41
41
  lyric. Vocal energy and timing gates are heuristics; listening QA is still needed.
42
+ 10. For complete lyric-line alternatives and musical restarts, the local CLI
43
+ `stemlab phrase-loops RESULTS --feedback RATINGS.json --export-audio` reuses
44
+ explicit canonical timings and saved evidence. See `docs/phrase-loops.md`.
45
+ Its separate report contains suggestions, never strict accepted loops or
46
+ listening approval. Artist ratings are source-hash-bound; keep private
47
+ ratings and song material out of public examples and packages.
42
48
 
43
49
  ## Evidence and privacy
44
50
 
@@ -50,6 +50,8 @@ Changelog = "https://github.com/kieransimkin/stemlab/blob/main/CHANGELOG.md"
50
50
  Contact = "https://kieransimkin.co.uk/contact/"
51
51
 
52
52
  [project.optional-dependencies]
53
+ # Explicit opt-in: native motions/catalogue stay entirely in DanceRudiments.
54
+ rudiments = ["dancerudiments>=0.2.4,<1"]
53
55
  # Local MCP transport and read-only viewer for the existing React timeline.
54
56
  # Model backends remain separately selected; no duplicate React bundle is shipped.
55
57
  codex = ["mcp>=2.2,<3", "fastapi>=0.115,<1", "uvicorn>=0.34,<1"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: danceflow-stemlab
3
- Version: 1.3.4
3
+ Version: 1.4.0
4
4
  Summary: Local music analysis for stems, timing, beats, harmony, lyrics, MIDI and loopable sections for Shorts. https://kieransimkin.co.uk/
5
5
  Author: Kieran Simkin
6
6
  Maintainer: Kieran Simkin
@@ -38,6 +38,8 @@ Requires-Dist: platformdirs>=4.2
38
38
  Requires-Dist: librosa>=0.11
39
39
  Requires-Dist: pyloudnorm>=0.2
40
40
  Requires-Dist: cmudict>=1.1.3
41
+ Provides-Extra: rudiments
42
+ Requires-Dist: dancerudiments<1,>=0.2.4; extra == "rudiments"
41
43
  Provides-Extra: codex
42
44
  Requires-Dist: mcp<3,>=2.2; extra == "codex"
43
45
  Requires-Dist: fastapi<1,>=0.115; extra == "codex"
@@ -255,6 +257,55 @@ The derived artifacts live under `deep/` (`sonic/`, `rhythm/`, `harmony/`, `stru
255
257
  `lyrics/`, `semantic_text/`, optional `semantic_audio/` and `basic_pitch/`, plus
256
258
  `song_map/song_map.json` and `summary.json`).
257
259
 
260
+ ## Find DanceRudiments matching candidates from each stem
261
+
262
+ Install the separately maintained **optional** native package with
263
+ `python -m pip install -e ".[rudiments]"`. StemLab calls
264
+ [`dancerudiments.catalogue()` and `dancerudiments.sample()`](https://github.com/kieransimkin/DanceRudiments)
265
+ directly—no cloned movement functions or bundled catalogue. It extracts per-stem
266
+ RMS attack peaks, maps them onto detected beats, and ranks 16-beat windows
267
+ against position/speed/acceleration salience of actual native motion samples.
268
+ Scores are **comparative similarities**, not probabilities or guarantees of a good motion.
269
+
270
+ ```sh
271
+ # Match the mix plus every saved stem without running separation again.
272
+ stemlab rudiments ./analysis-master --top 20
273
+ # Limit an existing analysis to two stems, or choose one of six official collections.
274
+ stemlab rudiments ./analysis-master --stem drums --stem bass --collection club
275
+ # Include matching as an opt-in part of a new full analysis.
276
+ stemlab analyze master.wav --output ./analysis-master --rudiments
277
+ # The bundled Arcadians MP3 has a documented BPM but no pre-separated stems.
278
+ stemlab rudiments 'examples/arcadians/Arcadians - 320kbps.mp3' \
279
+ --bpm 145 --collection initial --top 28 --output ./arcadians-rudiments
280
+ ```
281
+
282
+ ### Measured Arcadians benchmark
283
+
284
+ The mix-only offline benchmark found **1,821** energy-rise peaks and **15 candidate
285
+ patterns at ≥0.68** similarity among the authentic 28-pattern DanceRudiments
286
+ initial collection, led by `groove_b_played` (0.739), `groove_b_grid` (0.737),
287
+ and `lfo_morph` (0.724). The benchmark read DanceRudiments' officially compiled
288
+ sample tables through an **offline adapter**; the native C++-extension test is
289
+ separate and skips when that optional package is not installed. The BPM grid
290
+ starts at zero with **unverified beat phase**. No stem separation was claimed.
291
+
292
+ These are **actual Chromium screenshots** of the measured local HTML report,
293
+ not schematic illustrations or captures of the main React timeline.
294
+
295
+ ![Actual Arcadians audio attack peaks overlaid with a matching DanceRudiments motion descriptor in local report](docs/screenshots/arcadians-rudiments-report.png)
296
+
297
+ ![Actual ranked Arcadians DanceRudiments candidate table captured in Chromium](docs/screenshots/arcadians-rudiments-ranked.png)
298
+
299
+ The analysis writes `deep/rudiments/report.json`, `index.html`, and `overview.png`.
300
+ See **[method, score caveats, provenance, examples and screenshot reproduction](docs/rudiments.md)**.
301
+
302
+ **Native CI coverage:** the main GitHub Actions `CI` workflow now has a dedicated
303
+ Python 3.10/3.11 job that installs the official DanceRudiments *binary wheel*,
304
+ requires the actual C++ module, and runs the Arcadians test with skipping disabled.
305
+ Fewer than **12 real-audio matches** fails CI; passing runs publish the JSON,
306
+ HTML and measured overlay PNG as downloadable CI artifacts. See the
307
+ [native CI validation instructions](docs/rudiments.md#native-github-actions-gate).
308
+
258
309
  ## Timeline analysis gallery
259
310
 
260
311
  The browser workspace is intended to make the analysis output inspectable, not just downloadable. The illustrations below explain the main analysis families using the same shared timeline vocabulary as the frontend: one clock, one playhead, zoomable lanes, and consistent section alignment across spectrogram, beat, lyric and deep-analysis layers.
@@ -368,6 +419,13 @@ exported WAVs and writes no files. **Zoom to loop** focuses the shared timeline.
368
419
  See [the complete loop workflow](docs/loops.md) for installation of both patches,
369
420
  sample indexing, thresholds, limitations and reproducible screenshot examples.
370
421
 
422
+ For line-complete alternatives when a musically satisfying restart is the goal,
423
+ use `stemlab phrase-loops analysis-master --feedback artist-ratings.json --export-audio`.
424
+ This opt-in search compares the musical context around both cuts, preserves
425
+ canonical lyric occurrences, uses explicit artist feedback and selects diverse
426
+ passages. Suggestions remain separate from strict accepted loops. See
427
+ [phrase-loop methodology, feedback format and synthetic example](docs/phrase-loops.md).
428
+
371
429
  ![Loop selection and playback in the existing analysis frontend](docs/screenshots/loops-playing.png)
372
430
 
373
431
  ## Audio-to-MIDI model comparison
@@ -611,6 +669,10 @@ analysis-master/
611
669
  lyrics/
612
670
  semantic_text/
613
671
  song_map/
672
+ rudiments/ # optional native DanceRudiments candidate report
673
+ report.json
674
+ index.html
675
+ overview.png
614
676
  summary.json
615
677
  sonic_visualiser/
616
678
  session.sv
@@ -69,8 +69,10 @@ src/stemlab/analysis/__init__.py
69
69
  src/stemlab/analysis/harmony.py
70
70
  src/stemlab/analysis/loops.py
71
71
  src/stemlab/analysis/lyrics.py
72
+ src/stemlab/analysis/phrase_loops.py
72
73
  src/stemlab/analysis/registry.py
73
74
  src/stemlab/analysis/rhythm.py
75
+ src/stemlab/analysis/rudiment_matches.py
74
76
  src/stemlab/analysis/runner.py
75
77
  src/stemlab/analysis/semantic_audio.py
76
78
  src/stemlab/analysis/semantic_text.py
@@ -120,6 +122,7 @@ src/stemlab/web/style.css
120
122
  src/stemlab/web/timeline.css
121
123
  tests/test_analysis_registry.py
122
124
  tests/test_arcadians_fixture.py
125
+ tests/test_arcadians_rudiments.py
123
126
  tests/test_audio_normalization.py
124
127
  tests/test_beat_consensus.py
125
128
  tests/test_beat_this_bootstrap.py
@@ -152,7 +155,9 @@ tests/test_midi_extraction.py
152
155
  tests/test_midi_integration.py
153
156
  tests/test_midi_registry.py
154
157
  tests/test_models.py
158
+ tests/test_phrase_loops.py
155
159
  tests/test_repository_hygiene.py
160
+ tests/test_rudiment_matches.py
156
161
  tests/test_server.py
157
162
  tests/test_song_map.py
158
163
  tests/test_sonic_visualiser.py
@@ -106,6 +106,9 @@ psutil>=5.9
106
106
  transkun==2.0.1
107
107
  setuptools<81
108
108
 
109
+ [rudiments]
110
+ dancerudiments<1,>=0.2.4
111
+
109
112
  [semantic-nc]
110
113
  muq
111
114
 
@@ -7,4 +7,4 @@ Created by Kieran Simkin — https://kieransimkin.co.uk/
7
7
  Music catalogue — https://kieransimkin.co.uk/my-songs/
8
8
  """
9
9
 
10
- __version__ = "1.3.4"
10
+ __version__ = "1.4.0"
@@ -546,11 +546,9 @@ def _inside(root: Path, value: str) -> Path:
546
546
  return path
547
547
 
548
548
 
549
- def analyze_existing_loops(root: Path, *, export_audio: bool = False,
550
- config: LoopConfig | None = None) -> dict:
551
- """Reuse saved models. Also works after canonical section/timing edits."""
549
+ def load_loop_evidence(root: Path) -> dict:
550
+ """Load verified native master and saved evidence for both search strategies."""
552
551
  from .song_map import _choose_sections
553
- from stemlab.manifest import write_manifest
554
552
 
555
553
  root = root.expanduser().resolve()
556
554
  analysis = _read(root / "analysis.json")
@@ -577,21 +575,38 @@ def analyze_existing_loops(root: Path, *, export_audio: bool = False,
577
575
  stems.append(StemArtifact(str(item["model"]), str(item["stem"]), _inside(root, item["path"])))
578
576
  for item in model.get("normalization", []):
579
577
  gains[str(_inside(root, item["path"]))] = float(item.get("gain_db", 0.0))
580
- report = analyze_loops(master, root / "deep/loops", song_map=song_map, beat_results=results,
581
- stems=stems, whisper_result=_read(root / "speech/whisper.json"),
582
- lyrics_result=_read(root / "deep/lyrics/lyrics.json"), canonical=canonical,
583
- normalization_gains=gains, export_audio=export_audio, config=config)
578
+ return dict(audio_path=master, song_map=song_map, beat_results=results, stems=stems,
579
+ whisper_result=_read(root / "speech/whisper.json"),
580
+ lyrics_result=_read(root / "deep/lyrics/lyrics.json"), canonical=canonical,
581
+ normalization_gains=gains)
582
+
583
+
584
+ def index_loop_report(root: Path, name: str, relative_path: str) -> None:
585
+ """Refresh existing analysis discovery and hashes after an explicit rescan."""
586
+ from stemlab.manifest import write_manifest
587
+
588
+ analysis = _read(root / "analysis.json")
584
589
  summary_path = root / "deep/summary.json"
585
590
  summary = _read(summary_path)
586
- summary.setdefault("analyses", {})["loops"] = {"available": True, "path": "deep/loops/loops.json"}
591
+ summary.setdefault("analyses", {})[name] = {"available": True, "path": relative_path}
587
592
  _write_json(summary_path, summary)
588
593
  if not isinstance(analysis.get("deep_analysis"), dict):
589
594
  analysis["deep_analysis"] = {}
590
- analysis["deep_analysis"].setdefault("analyses", {})["loops"] = summary["analyses"]["loops"]
595
+ analysis["deep_analysis"].setdefault("analyses", {})[name] = summary["analyses"][name]
591
596
  _write_json(root / "analysis.json", analysis)
592
597
  old_manifest = _read(root / "manifest.json")
593
598
  write_manifest(root, extra={k: v for k, v in old_manifest.items()
594
599
  if k not in {"generated_at", "platform", "python", "files"}})
600
+
601
+
602
+ def analyze_existing_loops(root: Path, *, export_audio: bool = False,
603
+ config: LoopConfig | None = None) -> dict:
604
+ """Reuse saved models. Also works after canonical section/timing edits."""
605
+ root = root.expanduser().resolve()
606
+ evidence = load_loop_evidence(root)
607
+ report = analyze_loops(output_dir=root / "deep/loops", **evidence,
608
+ export_audio=export_audio, config=config)
609
+ index_loop_report(root, "loops", "deep/loops/loops.json")
595
610
  return report
596
611
 
597
612