diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f5526626..c298fa74 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,19 +1,9 @@ { "permissions": { "allow": [ - "Bash(./venv/bin/python -m unittest tests.test_server.Phase2EndpointTests -v 2>&1)", - "Bash(../../venv/bin/python -m unittest tests.test_server.Phase2EndpointTests -v)", - "Bash(venv/bin/python -m unittest tests.test_server.Phase2EndpointTests)", - "Bash(venv/bin/python -m unittest discover -s tests)", + "Bash(*)", "mcp__gemini-claude-code-mcp__summarize_project", - "Bash(./venv/bin/python -c \"import server; print\\(len\\(server.PHASE2_PROMPT_TEMPLATE\\)\\)\")", - "Bash(./venv/bin/python -m unittest discover -s tests)", - "Bash(./venv/bin/python -c \":*)", - "Bash(./venv/bin/python -m unittest tests.test_analyze.AnalyzeStructuralSnapshotTests.test_rhythm_detail_exposes_full_grid_downbeats_and_bar_positions)", - "Bash(./venv/bin/python -m unittest tests.test_analysis_runtime -v)", - "Bash(./venv/bin/python -m unittest tests.test_server -v)", - "Bash(./venv/bin/python -m unittest tests.test_analysis_runtime tests.test_server -v)", - "Skill(commit-commands:commit-push-pr)" + "Skill(update-config)" ] } } diff --git a/.claude/worktrees/awesome-brahmagupta b/.claude/worktrees/awesome-brahmagupta new file mode 160000 index 00000000..616bd261 --- /dev/null +++ b/.claude/worktrees/awesome-brahmagupta @@ -0,0 +1 @@ +Subproject commit 616bd261b6a05f6b86ae00075406d5dc96077809 diff --git a/ASA_System_Design.docx b/ASA_System_Design.docx new file mode 100644 index 00000000..ede1fc56 Binary files /dev/null and b/ASA_System_Design.docx differ diff --git a/CLAUDE.md b/CLAUDE.md index 40a65c68..5c76dffe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,3 +113,11 @@ Phase 2 is gated by `VITE_ENABLE_PHASE2_GEMINI`. `GEMINI_API_KEY` is backend-onl ## Backport Candidates `BACKLOG.md` lists 12 DSP services + 2 data files from `active/sonic-architect-app` that are candidates for porting into ASA. Consult it before implementing genre detection, mix analysis, or synthesis features — implementations may already exist in that reference project. + +## IMPORTANT: Sound Notification + +After finishing responding to my request or running a command, run this command to notify me by sound: + +```bash +afplay /System/Library/Sounds/Funk.aiff +``` diff --git a/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3 b/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3 new file mode 100644 index 00000000..d3522248 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3 differ diff --git a/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3-shm b/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3-shm new file mode 100644 index 00000000..fe9ac284 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3-shm differ diff --git a/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3-wal b/apps/backend/.runtime.backup.20260319-154319/analysis_runs.sqlite3-wal new file mode 100644 index 00000000..e69de29b diff --git a/apps/backend/.runtime/artifacts/0a6a487c-0829-4606-8414-590d5c166a98.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/00023a45-279c-426e-815f-905bb522648b.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/0a6a487c-0829-4606-8414-590d5c166a98.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/00023a45-279c-426e-815f-905bb522648b.mp3 diff --git a/apps/backend/.runtime/artifacts/13e471df-9081-4b2e-ac36-910b27170125.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/085e9770-cad7-4f24-b246-05531048da14.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/13e471df-9081-4b2e-ac36-910b27170125.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/085e9770-cad7-4f24-b246-05531048da14.mp3 diff --git a/apps/backend/.runtime/artifacts/15ec4643-dd99-4c82-a76c-05dd6caa938b.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/0a6a487c-0829-4606-8414-590d5c166a98.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/15ec4643-dd99-4c82-a76c-05dd6caa938b.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/0a6a487c-0829-4606-8414-590d5c166a98.mp3 diff --git a/apps/backend/.runtime/artifacts/1ca32a4e-3199-4d48-af6a-392d35a4e178.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/0d97014a-c360-41ed-9394-ee52ab1f5398.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/1ca32a4e-3199-4d48-af6a-392d35a4e178.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/0d97014a-c360-41ed-9394-ee52ab1f5398.mp3 diff --git a/apps/backend/.runtime/artifacts/20b106c8-1133-4622-8946-66fe0efcec92.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/0ea1d443-e2f8-40c9-96a8-71781f9cf2f6.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/20b106c8-1133-4622-8946-66fe0efcec92.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/0ea1d443-e2f8-40c9-96a8-71781f9cf2f6.mp3 diff --git a/apps/backend/.runtime/artifacts/23d883e1-e918-4381-91ac-412b89c8d467.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/13e471df-9081-4b2e-ac36-910b27170125.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/23d883e1-e918-4381-91ac-412b89c8d467.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/13e471df-9081-4b2e-ac36-910b27170125.mp3 diff --git a/apps/backend/.runtime/artifacts/3938c1f3-eb7d-4dac-b342-10fc934781b2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/15ec4643-dd99-4c82-a76c-05dd6caa938b.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/3938c1f3-eb7d-4dac-b342-10fc934781b2.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/15ec4643-dd99-4c82-a76c-05dd6caa938b.mp3 diff --git a/apps/backend/.runtime/artifacts/4373b82b-e56f-4cc0-8f69-5cf5e9692d30.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/18d33c37-3b6c-4a31-a7a6-75fea22975e7.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/4373b82b-e56f-4cc0-8f69-5cf5e9692d30.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/18d33c37-3b6c-4a31-a7a6-75fea22975e7.mp3 diff --git a/apps/backend/.runtime/artifacts/44c33ea0-dbd2-49aa-8e80-b3ff580a8cef.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/1c5db6dc-52c7-4ba5-aa46-56dae3917a70.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/44c33ea0-dbd2-49aa-8e80-b3ff580a8cef.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/1c5db6dc-52c7-4ba5-aa46-56dae3917a70.mp3 diff --git a/apps/backend/.runtime/artifacts/47ffe0d7-70a9-4d3c-b774-08805f65aa77.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/1ca32a4e-3199-4d48-af6a-392d35a4e178.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/47ffe0d7-70a9-4d3c-b774-08805f65aa77.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/1ca32a4e-3199-4d48-af6a-392d35a4e178.mp3 diff --git a/apps/backend/.runtime/artifacts/49a0b1ba-d0b0-4fbb-b291-50824f0cb520.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/20585017-6453-41c9-88e0-6a6cf9cd858b.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/49a0b1ba-d0b0-4fbb-b291-50824f0cb520.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/20585017-6453-41c9-88e0-6a6cf9cd858b.mp3 diff --git a/apps/backend/.runtime/artifacts/4af136ed-8020-4ffa-acc4-dd104fac2ae7.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/20b106c8-1133-4622-8946-66fe0efcec92.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/4af136ed-8020-4ffa-acc4-dd104fac2ae7.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/20b106c8-1133-4622-8946-66fe0efcec92.mp3 diff --git a/apps/backend/.runtime/artifacts/59cb0a6f-c50f-49e0-85fb-1eeb83f57d57.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/23d883e1-e918-4381-91ac-412b89c8d467.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/59cb0a6f-c50f-49e0-85fb-1eeb83f57d57.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/23d883e1-e918-4381-91ac-412b89c8d467.mp3 diff --git a/apps/backend/.runtime/artifacts/5b991347-2c08-46d1-bc55-7f7e728f72ff.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/29e9434d-dc0f-403b-9a47-706e4d94a65a.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/5b991347-2c08-46d1-bc55-7f7e728f72ff.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/29e9434d-dc0f-403b-9a47-706e4d94a65a.mp3 diff --git a/apps/backend/.runtime/artifacts/6579ba85-ce1b-4e3a-b0f1-06fd062206aa.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/2cf62d59-7cd1-4dd7-9607-5c0e67c203a0.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/6579ba85-ce1b-4e3a-b0f1-06fd062206aa.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/2cf62d59-7cd1-4dd7-9607-5c0e67c203a0.mp3 diff --git a/apps/backend/.runtime/artifacts/688fe179-7599-4b1c-ba4b-6c7b31b7ecc8.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/2db040ca-720a-4d4c-b0bd-1f88c293e1e1.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/688fe179-7599-4b1c-ba4b-6c7b31b7ecc8.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/2db040ca-720a-4d4c-b0bd-1f88c293e1e1.mp3 diff --git a/apps/backend/.runtime/artifacts/6d9c9917-a23c-47d8-ade0-4aafd87e792b.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/30ddd8bb-4a92-44b9-abf9-62fb586e070f.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/6d9c9917-a23c-47d8-ade0-4aafd87e792b.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/30ddd8bb-4a92-44b9-abf9-62fb586e070f.mp3 diff --git a/apps/backend/.runtime/artifacts/6ffdcb65-6d99-41f4-bb37-c079664f10b2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/31df54fd-f1de-4b89-84e9-0ecebe5d65e2.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/6ffdcb65-6d99-41f4-bb37-c079664f10b2.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/31df54fd-f1de-4b89-84e9-0ecebe5d65e2.mp3 diff --git a/apps/backend/.runtime/artifacts/755e756a-9f06-4726-9083-8c4ddbbeca85.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/32a94160-8a2c-497d-8d37-add40170c7ca.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/755e756a-9f06-4726-9083-8c4ddbbeca85.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/32a94160-8a2c-497d-8d37-add40170c7ca.mp3 diff --git a/apps/backend/.runtime/artifacts/7f04dfd3-1e9e-426d-b986-24d5538727d8.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/34ca8108-ce10-4c3a-8ca7-4b8a24ce21a9.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/7f04dfd3-1e9e-426d-b986-24d5538727d8.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/34ca8108-ce10-4c3a-8ca7-4b8a24ce21a9.mp3 diff --git a/apps/backend/.runtime/artifacts/86dd7eaa-d658-44bc-88d9-9db302d657b2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/37fbc5d2-21db-4bba-a67b-c28c63ad3d30.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/86dd7eaa-d658-44bc-88d9-9db302d657b2.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/37fbc5d2-21db-4bba-a67b-c28c63ad3d30.mp3 diff --git a/apps/backend/.runtime/artifacts/86fb6b4e-4c29-493c-8142-f23e246bdb10.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/38465e56-1dd3-423c-a884-8e522ef56c21.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/86fb6b4e-4c29-493c-8142-f23e246bdb10.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/38465e56-1dd3-423c-a884-8e522ef56c21.mp3 diff --git a/apps/backend/.runtime/artifacts/8b89ed12-bef5-4f36-98cb-aa6049f72909.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/3938c1f3-eb7d-4dac-b342-10fc934781b2.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/8b89ed12-bef5-4f36-98cb-aa6049f72909.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/3938c1f3-eb7d-4dac-b342-10fc934781b2.mp3 diff --git a/apps/backend/.runtime/artifacts/8e50f731-d7ae-47cc-bb13-07eb05b72c86.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/401f5ca2-2edd-4438-9ef4-af98929e3adf.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/8e50f731-d7ae-47cc-bb13-07eb05b72c86.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/401f5ca2-2edd-4438-9ef4-af98929e3adf.mp3 diff --git a/apps/backend/.runtime/artifacts/974b7ad9-ad37-40da-af9b-75ecfd8ee394.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/4241ec93-3086-4489-a99f-f0921053e99f.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/974b7ad9-ad37-40da-af9b-75ecfd8ee394.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/4241ec93-3086-4489-a99f-f0921053e99f.mp3 diff --git a/apps/backend/.runtime/artifacts/ad7e6d51-971e-4afa-8284-af970b5db26b.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/4373b82b-e56f-4cc0-8f69-5cf5e9692d30.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/ad7e6d51-971e-4afa-8284-af970b5db26b.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/4373b82b-e56f-4cc0-8f69-5cf5e9692d30.mp3 diff --git a/apps/backend/.runtime/artifacts/b60e4c88-9590-437a-a760-0313a1e9edfc.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/44c33ea0-dbd2-49aa-8e80-b3ff580a8cef.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/b60e4c88-9590-437a-a760-0313a1e9edfc.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/44c33ea0-dbd2-49aa-8e80-b3ff580a8cef.mp3 diff --git a/apps/backend/.runtime/artifacts/bcf868f3-7a42-48a0-96dd-f47aa107e609.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/45def2c2-f970-4a12-aea0-a9a363fcdfac.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/bcf868f3-7a42-48a0-96dd-f47aa107e609.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/45def2c2-f970-4a12-aea0-a9a363fcdfac.mp3 diff --git a/apps/backend/.runtime/artifacts/c3d9790a-fee3-4d08-b332-9b6638b1232e.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/47ffe0d7-70a9-4d3c-b774-08805f65aa77.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/c3d9790a-fee3-4d08-b332-9b6638b1232e.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/47ffe0d7-70a9-4d3c-b774-08805f65aa77.mp3 diff --git a/apps/backend/.runtime/artifacts/c421f82f-cb13-4f9a-aa17-2b4912f9dfb9.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/49302c90-b430-4417-86f4-a2b3ef9c2cab.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/c421f82f-cb13-4f9a-aa17-2b4912f9dfb9.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/49302c90-b430-4417-86f4-a2b3ef9c2cab.mp3 diff --git a/apps/backend/.runtime/artifacts/c4ab698e-2dc8-480b-b1d7-84334c4efbc4.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/49a0b1ba-d0b0-4fbb-b291-50824f0cb520.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/c4ab698e-2dc8-480b-b1d7-84334c4efbc4.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/49a0b1ba-d0b0-4fbb-b291-50824f0cb520.mp3 diff --git a/apps/backend/.runtime/artifacts/d4d0b0f4-5056-433c-a7d5-58d9955edf5c.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/4af136ed-8020-4ffa-acc4-dd104fac2ae7.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/d4d0b0f4-5056-433c-a7d5-58d9955edf5c.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/4af136ed-8020-4ffa-acc4-dd104fac2ae7.mp3 diff --git a/apps/backend/.runtime/artifacts/d6dd2e5e-0cba-4181-be0f-38b9d8eba979.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/4e6ef4bb-527c-4fd0-995f-dd827f7bcff0.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/d6dd2e5e-0cba-4181-be0f-38b9d8eba979.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/4e6ef4bb-527c-4fd0-995f-dd827f7bcff0.mp3 diff --git a/apps/backend/.runtime/artifacts/d8d62e19-56cf-4adb-a583-28ec75f481d6.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/4ff0e29f-fb2d-4673-b65d-984245e8083e.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/d8d62e19-56cf-4adb-a583-28ec75f481d6.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/4ff0e29f-fb2d-4673-b65d-984245e8083e.mp3 diff --git a/apps/backend/.runtime/artifacts/dd47cd2a-b034-44bd-b4b6-a1638c8ab96e.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/51d7b6b3-a5d7-4e06-af71-89cf07d8c075.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/dd47cd2a-b034-44bd-b4b6-a1638c8ab96e.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/51d7b6b3-a5d7-4e06-af71-89cf07d8c075.mp3 diff --git a/apps/backend/.runtime/artifacts/e4551d8f-40a3-4a32-b75c-a236d34d5c87.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/5556e2a3-221e-418f-b177-1213c6c76fe1.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/e4551d8f-40a3-4a32-b75c-a236d34d5c87.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/5556e2a3-221e-418f-b177-1213c6c76fe1.mp3 diff --git a/apps/backend/.runtime/artifacts/ee622e8f-480f-4c75-a3c0-038cb7636831.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/59cb0a6f-c50f-49e0-85fb-1eeb83f57d57.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/ee622e8f-480f-4c75-a3c0-038cb7636831.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/59cb0a6f-c50f-49e0-85fb-1eeb83f57d57.mp3 diff --git a/apps/backend/.runtime/artifacts/ef5a4575-8ca3-4760-959a-e612ded13cc8.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/5b991347-2c08-46d1-bc55-7f7e728f72ff.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/ef5a4575-8ca3-4760-959a-e612ded13cc8.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/5b991347-2c08-46d1-bc55-7f7e728f72ff.mp3 diff --git a/apps/backend/.runtime/artifacts/f380c7a3-26a6-4a9f-9916-6fb2feca217d.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/6180fcbf-28ad-4e99-8add-b0f5a21bcc6b.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/f380c7a3-26a6-4a9f-9916-6fb2feca217d.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/6180fcbf-28ad-4e99-8add-b0f5a21bcc6b.mp3 diff --git a/apps/backend/.runtime/artifacts/f440e094-db9f-445c-a845-e5a59d46e3b5.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/6579ba85-ce1b-4e3a-b0f1-06fd062206aa.mp3 similarity index 100% rename from apps/backend/.runtime/artifacts/f440e094-db9f-445c-a845-e5a59d46e3b5.mp3 rename to apps/backend/.runtime.backup.20260319-154319/artifacts/6579ba85-ce1b-4e3a-b0f1-06fd062206aa.mp3 diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/67494437-fc2d-4cc1-8810-e71c6f64293f.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/67494437-fc2d-4cc1-8810-e71c6f64293f.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/67494437-fc2d-4cc1-8810-e71c6f64293f.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/688fe179-7599-4b1c-ba4b-6c7b31b7ecc8.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/688fe179-7599-4b1c-ba4b-6c7b31b7ecc8.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/688fe179-7599-4b1c-ba4b-6c7b31b7ecc8.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/6a21a88b-98a6-4df8-ac5b-8e742ec9e048.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/6a21a88b-98a6-4df8-ac5b-8e742ec9e048.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/6a21a88b-98a6-4df8-ac5b-8e742ec9e048.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/6d9c9917-a23c-47d8-ade0-4aafd87e792b.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/6d9c9917-a23c-47d8-ade0-4aafd87e792b.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/6d9c9917-a23c-47d8-ade0-4aafd87e792b.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/6e438b32-5c59-4657-b24e-87b00994c8d5.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/6e438b32-5c59-4657-b24e-87b00994c8d5.mp3 new file mode 100644 index 00000000..bdef0b7a Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154319/artifacts/6e438b32-5c59-4657-b24e-87b00994c8d5.mp3 differ diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/6e438b32-5c59-4657-b24e-87b00994c8d5_melody.mid b/apps/backend/.runtime.backup.20260319-154319/artifacts/6e438b32-5c59-4657-b24e-87b00994c8d5_melody.mid new file mode 100644 index 00000000..42078437 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154319/artifacts/6e438b32-5c59-4657-b24e-87b00994c8d5_melody.mid differ diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/6ffdcb65-6d99-41f4-bb37-c079664f10b2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/6ffdcb65-6d99-41f4-bb37-c079664f10b2.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/6ffdcb65-6d99-41f4-bb37-c079664f10b2.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/717d09cd-128b-4a51-ac8e-674bcf5b8fc1.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/717d09cd-128b-4a51-ac8e-674bcf5b8fc1.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/717d09cd-128b-4a51-ac8e-674bcf5b8fc1.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/71ee20c8-179c-487a-ac87-9ec72b96b4f3.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/71ee20c8-179c-487a-ac87-9ec72b96b4f3.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/71ee20c8-179c-487a-ac87-9ec72b96b4f3.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/74c55c1d-f79d-4c00-8076-0a6735e1823a.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/74c55c1d-f79d-4c00-8076-0a6735e1823a.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/74c55c1d-f79d-4c00-8076-0a6735e1823a.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/755e756a-9f06-4726-9083-8c4ddbbeca85.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/755e756a-9f06-4726-9083-8c4ddbbeca85.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/755e756a-9f06-4726-9083-8c4ddbbeca85.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/764f3820-6d8c-4171-b934-2395af877747.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/764f3820-6d8c-4171-b934-2395af877747.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/764f3820-6d8c-4171-b934-2395af877747.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/7a764be3-42ff-45a2-8fac-df553242d4d6.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/7a764be3-42ff-45a2-8fac-df553242d4d6.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/7a764be3-42ff-45a2-8fac-df553242d4d6.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/7d04902c-1fa5-456f-bce8-b21a11f88051.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/7d04902c-1fa5-456f-bce8-b21a11f88051.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/7d04902c-1fa5-456f-bce8-b21a11f88051.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/7daa3249-8791-4a6c-8662-ea17b02801a7.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/7daa3249-8791-4a6c-8662-ea17b02801a7.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/7daa3249-8791-4a6c-8662-ea17b02801a7.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/7e8b23a8-286b-421d-b0eb-3d360be23888.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/7e8b23a8-286b-421d-b0eb-3d360be23888.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/7e8b23a8-286b-421d-b0eb-3d360be23888.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/7f04dfd3-1e9e-426d-b986-24d5538727d8.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/7f04dfd3-1e9e-426d-b986-24d5538727d8.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/7f04dfd3-1e9e-426d-b986-24d5538727d8.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/7f1444fe-21cf-49af-937f-4da5d05609e4.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/7f1444fe-21cf-49af-937f-4da5d05609e4.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/7f1444fe-21cf-49af-937f-4da5d05609e4.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/86782f16-4772-4d36-8ccd-63385c559f2a.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/86782f16-4772-4d36-8ccd-63385c559f2a.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/86782f16-4772-4d36-8ccd-63385c559f2a.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/86dd7eaa-d658-44bc-88d9-9db302d657b2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/86dd7eaa-d658-44bc-88d9-9db302d657b2.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/86dd7eaa-d658-44bc-88d9-9db302d657b2.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/86fb6b4e-4c29-493c-8142-f23e246bdb10.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/86fb6b4e-4c29-493c-8142-f23e246bdb10.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/86fb6b4e-4c29-493c-8142-f23e246bdb10.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/879130ef-ecba-401b-b986-ab5918ccaf54.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/879130ef-ecba-401b-b986-ab5918ccaf54.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/879130ef-ecba-401b-b986-ab5918ccaf54.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/8b89ed12-bef5-4f36-98cb-aa6049f72909.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/8b89ed12-bef5-4f36-98cb-aa6049f72909.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/8b89ed12-bef5-4f36-98cb-aa6049f72909.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/8c57f1a7-1f7b-4cd3-abf7-a37f8f4eb199.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/8c57f1a7-1f7b-4cd3-abf7-a37f8f4eb199.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/8c57f1a7-1f7b-4cd3-abf7-a37f8f4eb199.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/8e50f731-d7ae-47cc-bb13-07eb05b72c86.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/8e50f731-d7ae-47cc-bb13-07eb05b72c86.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/8e50f731-d7ae-47cc-bb13-07eb05b72c86.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/953b006d-7a7e-4a8d-ab66-f45e5f3312fb.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/953b006d-7a7e-4a8d-ab66-f45e5f3312fb.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/953b006d-7a7e-4a8d-ab66-f45e5f3312fb.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/95df1215-0e10-4b38-894f-587bb102fa5a.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/95df1215-0e10-4b38-894f-587bb102fa5a.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/95df1215-0e10-4b38-894f-587bb102fa5a.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/974b7ad9-ad37-40da-af9b-75ecfd8ee394.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/974b7ad9-ad37-40da-af9b-75ecfd8ee394.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/974b7ad9-ad37-40da-af9b-75ecfd8ee394.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/9c6e5c9c-ad8b-44cc-a39f-1d304b7a2f22.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/9c6e5c9c-ad8b-44cc-a39f-1d304b7a2f22.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/9c6e5c9c-ad8b-44cc-a39f-1d304b7a2f22.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/a037b450-5419-4eb8-9dc0-859ba54fe2fc.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/a037b450-5419-4eb8-9dc0-859ba54fe2fc.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/a037b450-5419-4eb8-9dc0-859ba54fe2fc.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/a3b6f618-ec21-417e-bcd1-085621b2cef7.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/a3b6f618-ec21-417e-bcd1-085621b2cef7.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/a3b6f618-ec21-417e-bcd1-085621b2cef7.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/a45e1380-4749-4947-8c84-215fe9f57e6a.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/a45e1380-4749-4947-8c84-215fe9f57e6a.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/a45e1380-4749-4947-8c84-215fe9f57e6a.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/a8aaf7ad-0edd-4d5a-a35f-067c2f3ce12d.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/a8aaf7ad-0edd-4d5a-a35f-067c2f3ce12d.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/a8aaf7ad-0edd-4d5a-a35f-067c2f3ce12d.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/a8bbcbf8-5018-4ded-8026-5251235f43a2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/a8bbcbf8-5018-4ded-8026-5251235f43a2.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/a8bbcbf8-5018-4ded-8026-5251235f43a2.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ac991c24-d8fb-471f-9000-3c101bca344b.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ac991c24-d8fb-471f-9000-3c101bca344b.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ac991c24-d8fb-471f-9000-3c101bca344b.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ad4b7538-390d-4198-b042-bd528b161f38.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ad4b7538-390d-4198-b042-bd528b161f38.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ad4b7538-390d-4198-b042-bd528b161f38.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ad7e6d51-971e-4afa-8284-af970b5db26b.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ad7e6d51-971e-4afa-8284-af970b5db26b.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ad7e6d51-971e-4afa-8284-af970b5db26b.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/b04f4fea-e6ee-4e9b-a02f-22868a28e0de.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/b04f4fea-e6ee-4e9b-a02f-22868a28e0de.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/b04f4fea-e6ee-4e9b-a02f-22868a28e0de.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/b2f49251-2f41-4ea2-a061-5eafe7e2989f.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/b2f49251-2f41-4ea2-a061-5eafe7e2989f.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/b2f49251-2f41-4ea2-a061-5eafe7e2989f.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/b60e4c88-9590-437a-a760-0313a1e9edfc.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/b60e4c88-9590-437a-a760-0313a1e9edfc.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/b60e4c88-9590-437a-a760-0313a1e9edfc.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/bba2e509-c1b3-46f3-9f60-8320bf1f7e0a.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/bba2e509-c1b3-46f3-9f60-8320bf1f7e0a.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/bba2e509-c1b3-46f3-9f60-8320bf1f7e0a.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/bcf868f3-7a42-48a0-96dd-f47aa107e609.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/bcf868f3-7a42-48a0-96dd-f47aa107e609.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/bcf868f3-7a42-48a0-96dd-f47aa107e609.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/beaefa0d-d415-49ec-9356-782a050b1579.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/beaefa0d-d415-49ec-9356-782a050b1579.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/beaefa0d-d415-49ec-9356-782a050b1579.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c04da87c-98cd-4179-99f8-a4f1b05f4190.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c04da87c-98cd-4179-99f8-a4f1b05f4190.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c04da87c-98cd-4179-99f8-a4f1b05f4190.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c2f9fc15-d94d-4975-b4ad-5ec0d493dd28.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c2f9fc15-d94d-4975-b4ad-5ec0d493dd28.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c2f9fc15-d94d-4975-b4ad-5ec0d493dd28.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c3d9790a-fee3-4d08-b332-9b6638b1232e.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c3d9790a-fee3-4d08-b332-9b6638b1232e.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c3d9790a-fee3-4d08-b332-9b6638b1232e.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c421f82f-cb13-4f9a-aa17-2b4912f9dfb9.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c421f82f-cb13-4f9a-aa17-2b4912f9dfb9.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c421f82f-cb13-4f9a-aa17-2b4912f9dfb9.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c4ab698e-2dc8-480b-b1d7-84334c4efbc4.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c4ab698e-2dc8-480b-b1d7-84334c4efbc4.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c4ab698e-2dc8-480b-b1d7-84334c4efbc4.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c501ebbc-f19b-469a-86e0-e1377b047815.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c501ebbc-f19b-469a-86e0-e1377b047815.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c501ebbc-f19b-469a-86e0-e1377b047815.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c613eb5a-2eb1-4e2f-a44b-0ded0f801d23.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c613eb5a-2eb1-4e2f-a44b-0ded0f801d23.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c613eb5a-2eb1-4e2f-a44b-0ded0f801d23.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c6c05e21-b682-4d93-a12e-46466770f44e.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c6c05e21-b682-4d93-a12e-46466770f44e.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c6c05e21-b682-4d93-a12e-46466770f44e.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c6e2551a-9dc0-4426-ace3-af213122f664.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c6e2551a-9dc0-4426-ace3-af213122f664.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c6e2551a-9dc0-4426-ace3-af213122f664.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c826be64-f18c-4a92-b96c-3397d5f84512.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c826be64-f18c-4a92-b96c-3397d5f84512.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c826be64-f18c-4a92-b96c-3397d5f84512.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/c928db85-2d4e-4eeb-a86d-2f06e2a11902.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/c928db85-2d4e-4eeb-a86d-2f06e2a11902.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/c928db85-2d4e-4eeb-a86d-2f06e2a11902.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/cd9ac5a7-71aa-4836-be74-68477ef10933.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/cd9ac5a7-71aa-4836-be74-68477ef10933.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/cd9ac5a7-71aa-4836-be74-68477ef10933.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/cf9d54b7-d2b0-426f-9fc2-d69d7c0ce3b5.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/cf9d54b7-d2b0-426f-9fc2-d69d7c0ce3b5.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/cf9d54b7-d2b0-426f-9fc2-d69d7c0ce3b5.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/d08f2e6b-4e3c-4199-97e6-aac95dbb111e.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/d08f2e6b-4e3c-4199-97e6-aac95dbb111e.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/d08f2e6b-4e3c-4199-97e6-aac95dbb111e.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/d10aaf22-f9db-4168-a838-c325a89739ee.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/d10aaf22-f9db-4168-a838-c325a89739ee.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/d10aaf22-f9db-4168-a838-c325a89739ee.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/d4d0b0f4-5056-433c-a7d5-58d9955edf5c.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/d4d0b0f4-5056-433c-a7d5-58d9955edf5c.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/d4d0b0f4-5056-433c-a7d5-58d9955edf5c.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/d569ab88-1382-447e-aa7f-3561b7dbcbf2.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/d569ab88-1382-447e-aa7f-3561b7dbcbf2.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/d569ab88-1382-447e-aa7f-3561b7dbcbf2.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/d6dd2e5e-0cba-4181-be0f-38b9d8eba979.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/d6dd2e5e-0cba-4181-be0f-38b9d8eba979.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/d6dd2e5e-0cba-4181-be0f-38b9d8eba979.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/d8d62e19-56cf-4adb-a583-28ec75f481d6.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/d8d62e19-56cf-4adb-a583-28ec75f481d6.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/d8d62e19-56cf-4adb-a583-28ec75f481d6.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/dd47cd2a-b034-44bd-b4b6-a1638c8ab96e.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/dd47cd2a-b034-44bd-b4b6-a1638c8ab96e.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/dd47cd2a-b034-44bd-b4b6-a1638c8ab96e.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/de62282a-1926-47c9-a244-6e6490f18823.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/de62282a-1926-47c9-a244-6e6490f18823.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/de62282a-1926-47c9-a244-6e6490f18823.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e2443612-f04d-486a-a7bf-c90f4a55683c.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e2443612-f04d-486a-a7bf-c90f4a55683c.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e2443612-f04d-486a-a7bf-c90f4a55683c.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e244cccb-2630-48c5-bfca-8ece41c9f165.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e244cccb-2630-48c5-bfca-8ece41c9f165.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e244cccb-2630-48c5-bfca-8ece41c9f165.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e3fb3330-e6fd-4932-8cac-4fca7661dd38.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e3fb3330-e6fd-4932-8cac-4fca7661dd38.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e3fb3330-e6fd-4932-8cac-4fca7661dd38.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e4551d8f-40a3-4a32-b75c-a236d34d5c87.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e4551d8f-40a3-4a32-b75c-a236d34d5c87.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e4551d8f-40a3-4a32-b75c-a236d34d5c87.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e7e883bd-1719-417f-a2aa-facb5321ab06.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e7e883bd-1719-417f-a2aa-facb5321ab06.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e7e883bd-1719-417f-a2aa-facb5321ab06.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e8a941cf-760a-47b4-9759-1a9755b5b1a4.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e8a941cf-760a-47b4-9759-1a9755b5b1a4.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e8a941cf-760a-47b4-9759-1a9755b5b1a4.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/e9f8abb1-0bab-41a4-9351-84b74f3ff668.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/e9f8abb1-0bab-41a4-9351-84b74f3ff668.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/e9f8abb1-0bab-41a4-9351-84b74f3ff668.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ea754a3d-c56e-47ea-a1ff-2fad35ee6d7a.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ea754a3d-c56e-47ea-a1ff-2fad35ee6d7a.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ea754a3d-c56e-47ea-a1ff-2fad35ee6d7a.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ec696e1f-332f-46a1-931a-b703ff4c3a22.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ec696e1f-332f-46a1-931a-b703ff4c3a22.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ec696e1f-332f-46a1-931a-b703ff4c3a22.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ee1cc7c5-c4fe-4a2a-8e01-8773950fbc06.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ee1cc7c5-c4fe-4a2a-8e01-8773950fbc06.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ee1cc7c5-c4fe-4a2a-8e01-8773950fbc06.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ee622e8f-480f-4c75-a3c0-038cb7636831.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ee622e8f-480f-4c75-a3c0-038cb7636831.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ee622e8f-480f-4c75-a3c0-038cb7636831.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ef5a4575-8ca3-4760-959a-e612ded13cc8.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ef5a4575-8ca3-4760-959a-e612ded13cc8.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ef5a4575-8ca3-4760-959a-e612ded13cc8.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/f380c7a3-26a6-4a9f-9916-6fb2feca217d.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/f380c7a3-26a6-4a9f-9916-6fb2feca217d.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/f380c7a3-26a6-4a9f-9916-6fb2feca217d.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/f440e094-db9f-445c-a845-e5a59d46e3b5.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/f440e094-db9f-445c-a845-e5a59d46e3b5.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/f440e094-db9f-445c-a845-e5a59d46e3b5.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/f59537fe-3ab1-4401-bc36-97f1cc1101fc.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/f59537fe-3ab1-4401-bc36-97f1cc1101fc.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/f59537fe-3ab1-4401-bc36-97f1cc1101fc.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/f7ed4a27-3aa7-40ec-9bd1-5dec090e0405.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/f7ed4a27-3aa7-40ec-9bd1-5dec090e0405.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/f7ed4a27-3aa7-40ec-9bd1-5dec090e0405.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/fb2e261d-3804-4994-abca-c130ed138f2c.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/fb2e261d-3804-4994-abca-c130ed138f2c.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/fb2e261d-3804-4994-abca-c130ed138f2c.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154319/artifacts/ffd97434-63af-4044-94f6-38dfd11c6bcf.mp3 b/apps/backend/.runtime.backup.20260319-154319/artifacts/ffd97434-63af-4044-94f6-38dfd11c6bcf.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime.backup.20260319-154319/artifacts/ffd97434-63af-4044-94f6-38dfd11c6bcf.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3 b/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3 new file mode 100644 index 00000000..95d89e89 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3 differ diff --git a/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3-shm b/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3-shm new file mode 100644 index 00000000..436a1255 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3-shm differ diff --git a/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3-wal b/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3-wal new file mode 100644 index 00000000..37ad2567 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154438/analysis_runs.sqlite3-wal differ diff --git a/apps/backend/.runtime.backup.20260319-154438/artifacts/48655048-2e1f-46a1-a43e-2f7850e94e48.mp3 b/apps/backend/.runtime.backup.20260319-154438/artifacts/48655048-2e1f-46a1-a43e-2f7850e94e48.mp3 new file mode 100644 index 00000000..bdef0b7a Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154438/artifacts/48655048-2e1f-46a1-a43e-2f7850e94e48.mp3 differ diff --git a/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3 b/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3 new file mode 100644 index 00000000..95d89e89 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3 differ diff --git a/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3-shm b/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3-shm new file mode 100644 index 00000000..fe9ac284 Binary files /dev/null and b/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3-shm differ diff --git a/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3-wal b/apps/backend/.runtime.backup.20260319-154710/analysis_runs.sqlite3-wal new file mode 100644 index 00000000..e69de29b diff --git a/apps/backend/.runtime/analysis_runs.sqlite3 b/apps/backend/.runtime/analysis_runs.sqlite3 index e1d6b68a..2b21d0ea 100644 Binary files a/apps/backend/.runtime/analysis_runs.sqlite3 and b/apps/backend/.runtime/analysis_runs.sqlite3 differ diff --git a/apps/backend/.runtime/analysis_runs.sqlite3-shm b/apps/backend/.runtime/analysis_runs.sqlite3-shm new file mode 100644 index 00000000..e1c4affc Binary files /dev/null and b/apps/backend/.runtime/analysis_runs.sqlite3-shm differ diff --git a/apps/backend/.runtime/analysis_runs.sqlite3-wal b/apps/backend/.runtime/analysis_runs.sqlite3-wal new file mode 100644 index 00000000..a2da2edb Binary files /dev/null and b/apps/backend/.runtime/analysis_runs.sqlite3-wal differ diff --git a/apps/backend/.runtime/artifacts/06009355-4753-4e23-85be-fb26a03bffca.mp3 b/apps/backend/.runtime/artifacts/06009355-4753-4e23-85be-fb26a03bffca.mp3 new file mode 100644 index 00000000..bdef0b7a Binary files /dev/null and b/apps/backend/.runtime/artifacts/06009355-4753-4e23-85be-fb26a03bffca.mp3 differ diff --git a/apps/backend/.runtime/artifacts/06009355-4753-4e23-85be-fb26a03bffca_melody.mid b/apps/backend/.runtime/artifacts/06009355-4753-4e23-85be-fb26a03bffca_melody.mid new file mode 100644 index 00000000..9f582a29 Binary files /dev/null and b/apps/backend/.runtime/artifacts/06009355-4753-4e23-85be-fb26a03bffca_melody.mid differ diff --git a/apps/backend/.runtime/artifacts/06263e7c-9736-4a40-b614-da646e32c8b1.mp3 b/apps/backend/.runtime/artifacts/06263e7c-9736-4a40-b614-da646e32c8b1.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/06263e7c-9736-4a40-b614-da646e32c8b1.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/109be1d8-8d77-431e-95c9-d12c9a10dea1.mp3 b/apps/backend/.runtime/artifacts/109be1d8-8d77-431e-95c9-d12c9a10dea1.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/109be1d8-8d77-431e-95c9-d12c9a10dea1.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/10abdcea-d20f-4768-a378-b2334fa0c4c8.mp3 b/apps/backend/.runtime/artifacts/10abdcea-d20f-4768-a378-b2334fa0c4c8.mp3 new file mode 100644 index 00000000..bdef0b7a Binary files /dev/null and b/apps/backend/.runtime/artifacts/10abdcea-d20f-4768-a378-b2334fa0c4c8.mp3 differ diff --git a/apps/backend/.runtime/artifacts/10abdcea-d20f-4768-a378-b2334fa0c4c8_melody.mid b/apps/backend/.runtime/artifacts/10abdcea-d20f-4768-a378-b2334fa0c4c8_melody.mid new file mode 100644 index 00000000..58df95e2 Binary files /dev/null and b/apps/backend/.runtime/artifacts/10abdcea-d20f-4768-a378-b2334fa0c4c8_melody.mid differ diff --git a/apps/backend/.runtime/artifacts/2455b0c9-8898-4437-8204-b789c9a54b02.mp3 b/apps/backend/.runtime/artifacts/2455b0c9-8898-4437-8204-b789c9a54b02.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/2455b0c9-8898-4437-8204-b789c9a54b02.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/2567e18a-69aa-4b5d-9a1c-d332b429ff73.mp3 b/apps/backend/.runtime/artifacts/2567e18a-69aa-4b5d-9a1c-d332b429ff73.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/2567e18a-69aa-4b5d-9a1c-d332b429ff73.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/270a8993-72fe-4c4a-b62a-35337e2f7179.mp3 b/apps/backend/.runtime/artifacts/270a8993-72fe-4c4a-b62a-35337e2f7179.mp3 new file mode 100644 index 00000000..bdef0b7a Binary files /dev/null and b/apps/backend/.runtime/artifacts/270a8993-72fe-4c4a-b62a-35337e2f7179.mp3 differ diff --git a/apps/backend/.runtime/artifacts/270a8993-72fe-4c4a-b62a-35337e2f7179_melody.mid b/apps/backend/.runtime/artifacts/270a8993-72fe-4c4a-b62a-35337e2f7179_melody.mid new file mode 100644 index 00000000..ab2dd5ec Binary files /dev/null and b/apps/backend/.runtime/artifacts/270a8993-72fe-4c4a-b62a-35337e2f7179_melody.mid differ diff --git a/apps/backend/.runtime/artifacts/48debfb6-1107-4272-b1bc-5282f7fbdc73.mp3 b/apps/backend/.runtime/artifacts/48debfb6-1107-4272-b1bc-5282f7fbdc73.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/48debfb6-1107-4272-b1bc-5282f7fbdc73.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/691b943d-61fb-4344-90e7-2117d56fe7ad.mp3 b/apps/backend/.runtime/artifacts/691b943d-61fb-4344-90e7-2117d56fe7ad.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/691b943d-61fb-4344-90e7-2117d56fe7ad.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/7b49ab3f-84ff-407c-84b7-d3332c739654.mp3 b/apps/backend/.runtime/artifacts/7b49ab3f-84ff-407c-84b7-d3332c739654.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/7b49ab3f-84ff-407c-84b7-d3332c739654.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/7d74ac22-c993-4c61-bca0-25ce86e43344.mp3 b/apps/backend/.runtime/artifacts/7d74ac22-c993-4c61-bca0-25ce86e43344.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/7d74ac22-c993-4c61-bca0-25ce86e43344.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/85dfc456-698f-42eb-8bbb-29cb32e1d32d.mp3 b/apps/backend/.runtime/artifacts/85dfc456-698f-42eb-8bbb-29cb32e1d32d.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/85dfc456-698f-42eb-8bbb-29cb32e1d32d.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/8bb2ba4d-4486-425f-bbe6-d69d166e8d68.mp3 b/apps/backend/.runtime/artifacts/8bb2ba4d-4486-425f-bbe6-d69d166e8d68.mp3 new file mode 100644 index 00000000..22f1faae --- /dev/null +++ b/apps/backend/.runtime/artifacts/8bb2ba4d-4486-425f-bbe6-d69d166e8d68.mp3 @@ -0,0 +1 @@ +fake-audio \ No newline at end of file diff --git a/apps/backend/.runtime/artifacts/912bd331-a6f8-4331-8526-3601ac6dd387.mp3 b/apps/backend/.runtime/artifacts/912bd331-a6f8-4331-8526-3601ac6dd387.mp3 new file mode 100644 index 00000000..bdef0b7a Binary files /dev/null and b/apps/backend/.runtime/artifacts/912bd331-a6f8-4331-8526-3601ac6dd387.mp3 differ diff --git a/apps/backend/.runtime/artifacts/912bd331-a6f8-4331-8526-3601ac6dd387_melody.mid b/apps/backend/.runtime/artifacts/912bd331-a6f8-4331-8526-3601ac6dd387_melody.mid new file mode 100644 index 00000000..734f468b Binary files /dev/null and b/apps/backend/.runtime/artifacts/912bd331-a6f8-4331-8526-3601ac6dd387_melody.mid differ diff --git a/apps/backend/.runtime/artifacts/ac90fe7d-2012-4fa2-9955-92384cd8deb4.mp3 b/apps/backend/.runtime/artifacts/ac90fe7d-2012-4fa2-9955-92384cd8deb4.mp3 new file mode 100644 index 00000000..73650955 Binary files /dev/null and b/apps/backend/.runtime/artifacts/ac90fe7d-2012-4fa2-9955-92384cd8deb4.mp3 differ diff --git a/apps/backend/.runtime/artifacts/ac90fe7d-2012-4fa2-9955-92384cd8deb4_melody.mid b/apps/backend/.runtime/artifacts/ac90fe7d-2012-4fa2-9955-92384cd8deb4_melody.mid new file mode 100644 index 00000000..b03afef2 Binary files /dev/null and b/apps/backend/.runtime/artifacts/ac90fe7d-2012-4fa2-9955-92384cd8deb4_melody.mid differ diff --git a/apps/backend/.runtime/artifacts/b76b2996-0c83-4135-933e-2b96b6563790.mp3 b/apps/backend/.runtime/artifacts/b76b2996-0c83-4135-933e-2b96b6563790.mp3 new file mode 100644 index 00000000..73650955 Binary files /dev/null and b/apps/backend/.runtime/artifacts/b76b2996-0c83-4135-933e-2b96b6563790.mp3 differ diff --git a/apps/backend/.runtime/artifacts/b76b2996-0c83-4135-933e-2b96b6563790_melody.mid b/apps/backend/.runtime/artifacts/b76b2996-0c83-4135-933e-2b96b6563790_melody.mid new file mode 100644 index 00000000..9f547562 Binary files /dev/null and b/apps/backend/.runtime/artifacts/b76b2996-0c83-4135-933e-2b96b6563790_melody.mid differ diff --git a/apps/backend/.runtime/artifacts/e9726f22-8cab-471d-9821-8451f75c805c.mp3 b/apps/backend/.runtime/artifacts/e9726f22-8cab-471d-9821-8451f75c805c.mp3 new file mode 100644 index 00000000..73650955 Binary files /dev/null and b/apps/backend/.runtime/artifacts/e9726f22-8cab-471d-9821-8451f75c805c.mp3 differ diff --git a/apps/backend/analysis_runtime.py b/apps/backend/analysis_runtime.py index 637d303a..745c1f3a 100644 --- a/apps/backend/analysis_runtime.py +++ b/apps/backend/analysis_runtime.py @@ -978,6 +978,51 @@ def recover_incomplete_attempts(self) -> None: (now,), ) + def update_measurement_progress( + self, + run_id: str, + *, + step_key: str, + message: str, + ) -> dict[str, Any] | None: + return self._update_stage_progress( + table="measurement_outputs", + identifier_column="run_id", + identifier=run_id, + step_key=step_key, + message=message, + ) + + def update_symbolic_attempt_progress( + self, + attempt_id: str, + *, + step_key: str, + message: str, + ) -> dict[str, Any] | None: + return self._update_stage_progress( + table="symbolic_extraction_attempts", + identifier_column="id", + identifier=attempt_id, + step_key=step_key, + message=message, + ) + + def update_interpretation_attempt_progress( + self, + attempt_id: str, + *, + step_key: str, + message: str, + ) -> dict[str, Any] | None: + return self._update_stage_progress( + table="interpretation_attempts", + identifier_column="id", + identifier=attempt_id, + step_key=step_key, + message=message, + ) + def _update_measurement_row( self, run_id: str, @@ -1035,6 +1080,58 @@ def _update_attempt_row( ), ) + def _update_stage_progress( + self, + *, + table: str, + identifier_column: str, + identifier: str, + step_key: str, + message: str, + ) -> dict[str, Any] | None: + now = _utc_now_iso() + with self._connect() as conn: + row = conn.execute( + f"SELECT status, diagnostics_json FROM {table} WHERE {identifier_column} = ?", + (identifier,), + ).fetchone() + if row is None: + raise KeyError(f"Unknown stage row for {identifier}") + if str(row["status"]) != "running": + return None + + diagnostics = _json_loads(row["diagnostics_json"]) + if not isinstance(diagnostics, dict): + diagnostics = {} + + progress = diagnostics.get("progress") + if isinstance(progress, dict): + seq_raw = progress.get("seq") + seq = int(seq_raw) + 1 if isinstance(seq_raw, int) else 1 + else: + seq = 1 + + progress_payload = { + "stepKey": step_key, + "message": message, + "updatedAt": now, + "seq": seq, + } + diagnostics["progress"] = progress_payload + conn.execute( + f""" + UPDATE {table} + SET diagnostics_json = ?, updated_at = ? + WHERE {identifier_column} = ? + """, + ( + _json_dumps(diagnostics), + now, + identifier, + ), + ) + return progress_payload + def _enqueue_requested_followups(self, run_id: str) -> None: with self._connect() as conn: run_row = conn.execute( diff --git a/apps/backend/server.py b/apps/backend/server.py index dfbb2d6a..cd0508d6 100644 --- a/apps/backend/server.py +++ b/apps/backend/server.py @@ -14,7 +14,7 @@ from math import ceil from math import isfinite from pathlib import Path -from typing import Any +from typing import Any, Callable from uuid import uuid4 try: @@ -560,6 +560,56 @@ def _resolve_symbolic_mode_for_legacy(transcribe: bool) -> str: return "stem_notes" if transcribe else "off" +def _consume_subprocess_stream( + stream: Any, + sink: list[str], + *, + on_line: Callable[[str], None] | None = None, +) -> None: + try: + while True: + line = stream.readline() + if line == "": + break + sink.append(line) + if on_line is not None: + on_line(line.rstrip("\r\n")) + finally: + stream.close() + + +def _measurement_progress_from_stderr_line(line: str) -> tuple[str, str] | None: + stripped = line.strip() + if not stripped: + return None + if stripped.startswith("Loading:"): + return ( + "loading_audio", + "Loading and validating uploaded audio for local analysis.", + ) + if stripped.startswith("Running source separation"): + return ( + "separating_stems", + "Separating stems with Demucs for downstream symbolic extraction.", + ) + if stripped == "Analyzing...": + return ( + "computing_measurements", + "Computing authoritative local DSP measurements.", + ) + if stripped == "Running fast analysis...": + return ( + "computing_fast_measurements", + "Computing fast local DSP measurements.", + ) + if stripped == "Done.": + return ( + "finalizing_output", + "Finalizing authoritative measurement output.", + ) + return None + + def _run_measurement_subprocess( *, audio_path: str, @@ -569,6 +619,7 @@ def _run_measurement_subprocess( run_separation: bool, run_transcribe: bool, run_fast: bool, + on_progress: Callable[[str, str], None] | None = None, ) -> dict[str, Any]: estimate = _build_backend_estimate(audio_path, run_separation, run_transcribe) command = ["./venv/bin/python", "analyze.py", audio_path, "--yes"] @@ -586,14 +637,14 @@ def _run_measurement_subprocess( timeout_seconds = _compute_timeout_seconds(estimate) analysis_started_at = _current_time() try: - result = subprocess.run( + result = subprocess.Popen( command, - capture_output=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, text=True, - check=False, - timeout=timeout_seconds, + bufsize=1, ) - except subprocess.TimeoutExpired as exc: + except Exception as exc: analysis_completed_at = _current_time() diagnostics = _build_diagnostics( request_id=request_id, @@ -606,26 +657,25 @@ def _run_measurement_subprocess( file_size_bytes=file_size_bytes, file_duration_seconds=None, engine_version=ENGINE_VERSION, - stdout=exc.stdout, - stderr=exc.stderr, + stderr=exc, ) return { "ok": False, - "statusCode": 504, - "errorCode": "ANALYZER_TIMEOUT", - "message": "Local DSP analysis timed out before completion.", - "retryable": True, + "statusCode": 500, + "errorCode": "BACKEND_INTERNAL_ERROR", + "message": "Local DSP backend hit an unexpected server error.", + "retryable": False, "estimate": estimate, "timeoutSeconds": timeout_seconds, "flagsUsed": flags_used, "requestStartedAt": request_started_at, "analysisStartedAt": analysis_started_at, "analysisCompletedAt": analysis_completed_at, - "stdout": exc.stdout, - "stderr": exc.stderr, + "stderr": exc, "diagnostics": diagnostics, } - except Exception as exc: + + if result.stdout is None or result.stderr is None: analysis_completed_at = _current_time() diagnostics = _build_diagnostics( request_id=request_id, @@ -638,7 +688,7 @@ def _run_measurement_subprocess( file_size_bytes=file_size_bytes, file_duration_seconds=None, engine_version=ENGINE_VERSION, - stderr=exc, + stderr="Local DSP subprocess did not expose stdout/stderr pipes.", ) return { "ok": False, @@ -652,11 +702,92 @@ def _run_measurement_subprocess( "requestStartedAt": request_started_at, "analysisStartedAt": analysis_started_at, "analysisCompletedAt": analysis_completed_at, - "stderr": exc, "diagnostics": diagnostics, } + stderr_progress = {"stepKey": None, "message": None} + + def _handle_stderr_line(line: str) -> None: + mapped = _measurement_progress_from_stderr_line(line) + if mapped is None or on_progress is None: + return + step_key, message = mapped + if ( + stderr_progress["stepKey"] == step_key + and stderr_progress["message"] == message + ): + return + try: + on_progress(step_key, message) + except Exception as exc: # pragma: no cover - defensive log path + logger.warning("Measurement progress callback failed: %s", exc) + return + stderr_progress["stepKey"] = step_key + stderr_progress["message"] = message + + stdout_lines: list[str] = [] + stderr_lines: list[str] = [] + stdout_thread = threading.Thread( + target=_consume_subprocess_stream, + args=(result.stdout, stdout_lines), + daemon=True, + ) + stderr_thread = threading.Thread( + target=_consume_subprocess_stream, + args=(result.stderr, stderr_lines), + kwargs={"on_line": _handle_stderr_line}, + daemon=True, + ) + stdout_thread.start() + stderr_thread.start() + + timed_out = False + try: + result.wait(timeout=timeout_seconds) + except subprocess.TimeoutExpired: + timed_out = True + result.kill() + finally: + result.wait() + stdout_thread.join(timeout=2.0) + stderr_thread.join(timeout=2.0) + analysis_completed_at = _current_time() + stdout = "".join(stdout_lines) + stderr = "".join(stderr_lines) + + if timed_out: + diagnostics = _build_diagnostics( + request_id=request_id, + estimate=estimate, + timeout_seconds=timeout_seconds, + request_started_at=request_started_at, + analysis_started_at=analysis_started_at, + analysis_completed_at=analysis_completed_at, + flags_used=flags_used, + file_size_bytes=file_size_bytes, + file_duration_seconds=None, + engine_version=ENGINE_VERSION, + stdout=stdout, + stderr=stderr, + ) + return { + "ok": False, + "statusCode": 504, + "errorCode": "ANALYZER_TIMEOUT", + "message": "Local DSP analysis timed out before completion.", + "retryable": True, + "estimate": estimate, + "timeoutSeconds": timeout_seconds, + "flagsUsed": flags_used, + "requestStartedAt": request_started_at, + "analysisStartedAt": analysis_started_at, + "analysisCompletedAt": analysis_completed_at, + "stdout": stdout, + "stderr": stderr, + "diagnostics": diagnostics, + } + if result.returncode != 0: diagnostics = _build_diagnostics( request_id=request_id, @@ -669,8 +800,8 @@ def _run_measurement_subprocess( file_size_bytes=file_size_bytes, file_duration_seconds=None, engine_version=ENGINE_VERSION, - stdout=result.stdout, - stderr=result.stderr, + stdout=stdout, + stderr=stderr, ) return { "ok": False, @@ -684,13 +815,13 @@ def _run_measurement_subprocess( "requestStartedAt": request_started_at, "analysisStartedAt": analysis_started_at, "analysisCompletedAt": analysis_completed_at, - "stdout": result.stdout, - "stderr": result.stderr, + "stdout": stdout, + "stderr": stderr, "diagnostics": diagnostics, } - stdout = result.stdout.strip() - if not stdout: + normalized_stdout = stdout.strip() + if not normalized_stdout: diagnostics = _build_diagnostics( request_id=request_id, estimate=estimate, @@ -702,7 +833,7 @@ def _run_measurement_subprocess( file_size_bytes=file_size_bytes, file_duration_seconds=None, engine_version=ENGINE_VERSION, - stderr=result.stderr, + stderr=stderr, ) return { "ok": False, @@ -716,12 +847,12 @@ def _run_measurement_subprocess( "requestStartedAt": request_started_at, "analysisStartedAt": analysis_started_at, "analysisCompletedAt": analysis_completed_at, - "stderr": result.stderr, + "stderr": stderr, "diagnostics": diagnostics, } try: - payload = json.loads(stdout) + payload = json.loads(normalized_stdout) except json.JSONDecodeError: diagnostics = _build_diagnostics( request_id=request_id, @@ -734,8 +865,8 @@ def _run_measurement_subprocess( file_size_bytes=file_size_bytes, file_duration_seconds=None, engine_version=ENGINE_VERSION, - stdout=stdout, - stderr=result.stderr, + stdout=normalized_stdout, + stderr=stderr, ) return { "ok": False, @@ -749,8 +880,8 @@ def _run_measurement_subprocess( "requestStartedAt": request_started_at, "analysisStartedAt": analysis_started_at, "analysisCompletedAt": analysis_completed_at, - "stdout": stdout, - "stderr": result.stderr, + "stdout": normalized_stdout, + "stderr": stderr, "diagnostics": diagnostics, } @@ -766,8 +897,8 @@ def _run_measurement_subprocess( file_size_bytes=file_size_bytes, file_duration_seconds=None, engine_version=ENGINE_VERSION, - stdout=stdout, - stderr=result.stderr, + stdout=normalized_stdout, + stderr=stderr, ) return { "ok": False, @@ -780,8 +911,8 @@ def _run_measurement_subprocess( "flagsUsed": flags_used, "analysisStartedAt": analysis_started_at, "analysisCompletedAt": analysis_completed_at, - "stdout": stdout, - "stderr": result.stderr, + "stdout": normalized_stdout, + "stderr": stderr, "diagnostics": diagnostics, } @@ -828,6 +959,11 @@ def _execute_measurement_run( run_separation=run_separation, run_transcribe=run_transcribe, run_fast=run_fast, + on_progress=lambda step_key, message: runtime.update_measurement_progress( + run_id, + step_key=step_key, + message=message, + ), ) provenance = _build_measurement_provenance( run_separation=run_separation, @@ -956,7 +1092,13 @@ def _execute_symbolic_attempt( attempt: dict[str, Any], ) -> None: started_at = _current_time() + attempt_id = str(attempt["attemptId"]) run_id = str(attempt["runId"]) + runtime.update_symbolic_attempt_progress( + attempt_id, + step_key="prepare_attempt", + message="Preparing symbolic extraction attempt.", + ) source_artifact = runtime.get_source_artifact(run_id) provenance = { "schemaVersion": "symbolic.v1", @@ -966,11 +1108,21 @@ def _execute_symbolic_attempt( try: stem_paths = None if attempt["mode"] == "stem_notes": + runtime.update_symbolic_attempt_progress( + attempt_id, + step_key="resolve_stems", + message="Resolving or materializing stems for transcription.", + ) stem_paths = _get_or_materialize_stem_paths( runtime, run_id, source_artifact["path"], ) + runtime.update_symbolic_attempt_progress( + attempt_id, + step_key="run_backend", + message="Running symbolic transcription backend.", + ) backend = _resolve_transcription_backend(str(attempt["backendId"])) symbolic_payload = analyze_transcription( source_artifact["path"], @@ -987,15 +1139,20 @@ def _execute_symbolic_attempt( } if isinstance(transcription_detail, dict): provenance["resolvedBackendId"] = transcription_detail.get("transcriptionMethod") + runtime.update_symbolic_attempt_progress( + attempt_id, + step_key="finalize_output", + message="Finalizing symbolic extraction output.", + ) runtime.complete_symbolic_attempt( - str(attempt["attemptId"]), + attempt_id, result=transcription_detail if isinstance(transcription_detail, dict) else None, provenance=provenance, diagnostics=diagnostics, ) except Exception as exc: runtime.fail_symbolic_attempt( - str(attempt["attemptId"]), + attempt_id, error={ "code": "SYMBOLIC_EXTRACTION_FAILED", "message": str(exc), @@ -1021,7 +1178,16 @@ def _run_interpretation_request( grounding_metadata: dict[str, Any], model_name: str, request_id: str, + on_progress: Callable[[str, str], None] | None = None, ) -> dict[str, Any]: + def _emit_progress(step_key: str, message: str) -> None: + if on_progress is None: + return + try: + on_progress(step_key, message) + except Exception as exc: # pragma: no cover - defensive log path + logger.warning("Interpretation progress callback failed: %s", exc) + request_started_at = _current_time() flags_used: list[str] = [] mime_type = _get_audio_mime_type(filename) @@ -1070,6 +1236,7 @@ def _run_interpretation_request( "diagnostics": None, } + _emit_progress("build_prompt", "Building grounded interpretation prompt.") prompt = profile_config["buildPrompt"]( measurement_result=measurement_result, symbolic_result=symbolic_result, @@ -1090,6 +1257,10 @@ def _run_interpretation_request( try: if file_size_bytes <= INLINE_SIZE_LIMIT: flags_used.append("inline") + _emit_progress( + "send_request", + "Sending grounded audio and prompt to the interpretation model.", + ) with open(source_path, "rb") as input_file: audio_bytes = input_file.read() audio_b64 = base64.b64encode(audio_bytes).decode("ascii") @@ -1107,6 +1278,7 @@ def _generate_inline() -> Any: message_suffix = profile_config["successMessage"] else: flags_used.append("files-api") + _emit_progress("upload_audio", "Uploading audio for interpretation.") def _upload_file() -> Any: return client.files.upload( @@ -1126,6 +1298,10 @@ def _upload_file() -> Any: "mime_type": uploaded_gemini_file.mime_type, } } + _emit_progress( + "generate_response", + "Generating grounded interpretation from uploaded audio.", + ) def _generate_files_api() -> Any: return client.models.generate_content( @@ -1145,6 +1321,10 @@ def _generate_files_api() -> Any: ) response_text: str | None = getattr(response, "text", None) + _emit_progress( + "parse_output", + "Parsing and validating interpretation output.", + ) interpretation_result, skip_message = profile_config["parseResult"](response_text) diagnostics = _build_diagnostics( request_id=request_id, @@ -1158,6 +1338,7 @@ def _generate_files_api() -> Any: file_duration_seconds=None, engine_version=model_name, ) + _emit_progress("finalize_output", "Finalizing interpretation output.") if skip_message: return { "ok": True, @@ -1207,6 +1388,12 @@ def _execute_interpretation_attempt( runtime: AnalysisRuntime, attempt: dict[str, Any], ) -> dict[str, Any]: + attempt_id = str(attempt["attemptId"]) + runtime.update_interpretation_attempt_progress( + attempt_id, + step_key="prepare_grounding", + message="Preparing grounded measurement and symbolic context.", + ) run_id = str(attempt["runId"]) profile_id = _coerce_string(attempt.get("profileId"), "producer_summary") source_artifact = runtime.get_source_artifact(run_id) @@ -1231,7 +1418,12 @@ def _execute_interpretation_attempt( symbolic_result=symbolic_result, grounding_metadata=grounding_metadata, model_name=model_name, - request_id=str(attempt["attemptId"]), + request_id=attempt_id, + on_progress=lambda step_key, message: runtime.update_interpretation_attempt_progress( + attempt_id, + step_key=step_key, + message=message, + ), ) provenance = { "schemaVersion": "interpretation.v1", @@ -1243,7 +1435,7 @@ def _execute_interpretation_attempt( } if execution["ok"]: runtime.complete_interpretation_attempt( - str(attempt["attemptId"]), + attempt_id, result=execution["interpretationResult"], provenance=provenance, diagnostics=execution["diagnostics"], @@ -1253,7 +1445,7 @@ def _execute_interpretation_attempt( return execution runtime.fail_interpretation_attempt( - str(attempt["attemptId"]), + attempt_id, error={ "code": execution["errorCode"], "message": execution["message"], diff --git a/apps/backend/tests/test_analysis_runtime.py b/apps/backend/tests/test_analysis_runtime.py index 511b5bcc..a9741857 100644 --- a/apps/backend/tests/test_analysis_runtime.py +++ b/apps/backend/tests/test_analysis_runtime.py @@ -143,6 +143,82 @@ def test_runtime_can_resolve_runs_by_legacy_request_id(self) -> None: created["runId"], ) + def test_stage_progress_updates_are_visible_in_stage_diagnostics(self) -> None: + runtime = self._runtime() + created = runtime.create_run( + filename="track.mp3", + content=b"fake-audio", + mime_type="audio/mpeg", + symbolic_mode="stem_notes", + symbolic_backend="auto", + interpretation_mode="async", + interpretation_profile="producer_summary", + interpretation_model="gemini-2.5-flash", + ) + + measurement_job = runtime.reserve_next_measurement_run() + self.assertIsNotNone(measurement_job) + progress = runtime.update_measurement_progress( + created["runId"], + step_key="loading_audio", + message="Loading and validating uploaded audio for local analysis.", + ) + self.assertIsNotNone(progress) + snapshot = runtime.get_run(created["runId"]) + self.assertEqual( + snapshot["stages"]["measurement"]["diagnostics"]["progress"]["stepKey"], + "loading_audio", + ) + self.assertEqual( + snapshot["stages"]["measurement"]["diagnostics"]["progress"]["seq"], + 1, + ) + + runtime.complete_measurement( + created["runId"], + payload={"bpm": 128, "durationSeconds": 60.0}, + provenance={"schemaVersion": "measurement.v1"}, + diagnostics={"backendDurationMs": 1000}, + ) + + symbolic_attempt = runtime.reserve_next_symbolic_attempt() + self.assertIsNotNone(symbolic_attempt) + symbolic_attempt_id = str(symbolic_attempt["attemptId"]) + progress = runtime.update_symbolic_attempt_progress( + symbolic_attempt_id, + step_key="run_backend", + message="Running symbolic transcription backend.", + ) + self.assertIsNotNone(progress) + snapshot = runtime.get_run(created["runId"]) + self.assertEqual( + snapshot["stages"]["symbolicExtraction"]["diagnostics"]["progress"]["stepKey"], + "run_backend", + ) + self.assertEqual( + snapshot["stages"]["symbolicExtraction"]["diagnostics"]["progress"]["seq"], + 1, + ) + + interpretation_attempt = runtime.reserve_next_interpretation_attempt() + self.assertIsNotNone(interpretation_attempt) + interpretation_attempt_id = str(interpretation_attempt["attemptId"]) + progress = runtime.update_interpretation_attempt_progress( + interpretation_attempt_id, + step_key="build_prompt", + message="Building grounded interpretation prompt.", + ) + self.assertIsNotNone(progress) + snapshot = runtime.get_run(created["runId"]) + self.assertEqual( + snapshot["stages"]["interpretation"]["diagnostics"]["progress"]["stepKey"], + "build_prompt", + ) + self.assertEqual( + snapshot["stages"]["interpretation"]["diagnostics"]["progress"]["seq"], + 1, + ) + def test_reserve_next_measurement_run_returns_requested_options(self) -> None: runtime = self._runtime() created = runtime.create_run( diff --git a/apps/backend/tests/test_server.py b/apps/backend/tests/test_server.py index 36d592e5..3ca6c396 100644 --- a/apps/backend/tests/test_server.py +++ b/apps/backend/tests/test_server.py @@ -13,14 +13,67 @@ import server -def _make_timeout_expired() -> subprocess.TimeoutExpired: - error = subprocess.TimeoutExpired( - cmd=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], - timeout=53, - ) - error.stdout = b"partial stdout" - error.stderr = b"partial stderr" - return error +class _FakeStream: + def __init__(self, text: str): + self._lines = text.splitlines(keepends=True) + self._index = 0 + + def readline(self) -> str: + if self._index >= len(self._lines): + return "" + line = self._lines[self._index] + self._index += 1 + return line + + def close(self) -> None: + return None + + +class _FakePopen: + def __init__( + self, + *, + returncode: int, + stdout: str, + stderr: str, + raise_timeout_once: bool = False, + ) -> None: + self.returncode = returncode + self.stdout = _FakeStream(stdout) + self.stderr = _FakeStream(stderr) + self._raise_timeout_once = raise_timeout_once + self._timeout_raised = False + self._killed = False + + def wait(self, timeout: int | float | None = None) -> int: + if self._raise_timeout_once and not self._timeout_raised and not self._killed: + self._timeout_raised = True + raise server.subprocess.TimeoutExpired( + cmd=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + timeout=timeout if timeout is not None else 0, + ) + return self.returncode + + def kill(self) -> None: + self._killed = True + + +def _fake_popen_factory( + *, + returncode: int, + stdout: str, + stderr: str, + raise_timeout_once: bool = False, +): + def _factory(*_args, **_kwargs): + return _FakePopen( + returncode=returncode, + stdout=stdout, + stderr=stderr, + raise_timeout_once=raise_timeout_once, + ) + + return _factory def _timing_points( @@ -162,6 +215,96 @@ def test_resolve_server_port_uses_env_override(self) -> None: with patch.dict(server.os.environ, {"SONIC_ANALYZER_PORT": "8456"}, clear=True): self.assertEqual(server.resolve_server_port(), 8456) + def test_measurement_progress_parser_maps_known_stderr_markers(self) -> None: + self.assertEqual( + server._measurement_progress_from_stderr_line("Loading: /tmp/audio.wav"), + ( + "loading_audio", + "Loading and validating uploaded audio for local analysis.", + ), + ) + self.assertEqual( + server._measurement_progress_from_stderr_line( + "Running source separation (this may take 30-60 seconds)..." + ), + ( + "separating_stems", + "Separating stems with Demucs for downstream symbolic extraction.", + ), + ) + self.assertEqual( + server._measurement_progress_from_stderr_line("Analyzing..."), + ( + "computing_measurements", + "Computing authoritative local DSP measurements.", + ), + ) + self.assertEqual( + server._measurement_progress_from_stderr_line("Done."), + ( + "finalizing_output", + "Finalizing authoritative measurement output.", + ), + ) + self.assertIsNone(server._measurement_progress_from_stderr_line("[warn] ignored")) + + def test_measurement_subprocess_stream_maps_stderr_to_progress_events(self) -> None: + events: list[tuple[str, str]] = [] + with ( + patch.object( + server, + "_build_backend_estimate", + return_value={ + "durationSeconds": 60.0, + "totalLowMs": 10000, + "totalHighMs": 20000, + }, + ), + patch.object( + server.subprocess, + "Popen", + side_effect=_fake_popen_factory( + returncode=0, + stdout=json.dumps({"bpm": 128, "durationSeconds": 60.0}), + stderr=( + "Loading: /tmp/track.wav\n" + "Analyzing...\n" + "Done.\n" + ), + ), + ), + patch("builtins.print"), + ): + execution = server._run_measurement_subprocess( + audio_path="/tmp/track.wav", + file_size_bytes=10, + request_id="req_1", + request_started_at=datetime(2026, 3, 8, 12, 0, 0), + run_separation=False, + run_transcribe=False, + run_fast=False, + on_progress=lambda step_key, message: events.append((step_key, message)), + ) + + self.assertTrue(execution["ok"]) + self.assertEqual( + events, + [ + ( + "loading_audio", + "Loading and validating uploaded audio for local analysis.", + ), + ( + "computing_measurements", + "Computing authoritative local DSP measurements.", + ), + ( + "finalizing_output", + "Finalizing authoritative measurement output.", + ), + ], + ) + def test_analyze_endpoint_openapi_contract_exposes_separate_form_field( self, ) -> None: @@ -339,16 +482,8 @@ def test_estimate_endpoint_combines_separate_and_transcribe_flags( ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=[ - "./venv/bin/python", - "analyze.py", - "track.mp3", - "--yes", - "--separate", - "--transcribe", - ], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout=json.dumps( { @@ -380,7 +515,7 @@ def test_estimate_endpoint_combines_separate_and_transcribe_flags( ), ) def test_analyze_endpoint_combines_separate_and_transcribe_in_subprocess( - self, run_mock, build_estimate_mock, *_mocks + self, popen_mock, build_estimate_mock, *_mocks ) -> None: with ( patch.object( @@ -405,7 +540,7 @@ def test_analyze_endpoint_combines_separate_and_transcribe_in_subprocess( ) self.assertEqual(response.status_code, 200) - command = run_mock.call_args.args[0] + command = popen_mock.call_args.args[0] self.assertEqual( command, [ @@ -417,9 +552,9 @@ def test_analyze_endpoint_combines_separate_and_transcribe_in_subprocess( "--transcribe", ], ) - self.assertEqual(run_mock.call_args.kwargs["timeout"], 218) build_estimate_mock.assert_called_once_with(214.6, True, True) payload = self._decode_json_response(response) + self.assertEqual(payload["diagnostics"]["timeoutSeconds"], 218) self.assertEqual(payload["diagnostics"]["backendDurationMs"], 200.0) self.assertEqual( payload["diagnostics"]["timings"], @@ -461,9 +596,8 @@ def test_analyze_endpoint_openapi_exposes_fast_form_field(self) -> None: ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes", "--fast"], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout=json.dumps({ "bpm": 128, "bpmConfidence": 0.9, "key": "C major", "keyConfidence": 0.8, @@ -477,7 +611,7 @@ def test_analyze_endpoint_openapi_exposes_fast_form_field(self) -> None: ), ) def test_analyze_endpoint_passes_fast_flag_to_subprocess( - self, run_mock, *_mocks + self, popen_mock, *_mocks ) -> None: with ( patch.object(server, "_current_time", side_effect=_timing_points(), create=True), @@ -497,7 +631,7 @@ def test_analyze_endpoint_passes_fast_flag_to_subprocess( ) self.assertEqual(response.status_code, 200) - command = run_mock.call_args.args[0] + command = popen_mock.call_args.args[0] self.assertIn("--fast", command) payload = self._decode_json_response(response) self.assertIn("--fast", payload["diagnostics"]["timings"]["flagsUsed"]) @@ -515,9 +649,8 @@ def test_analyze_endpoint_passes_fast_flag_to_subprocess( ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout=json.dumps({ "bpm": 128, "bpmConfidence": 0.9, "key": "C major", "keyConfidence": 0.8, @@ -531,7 +664,7 @@ def test_analyze_endpoint_passes_fast_flag_to_subprocess( ), ) def test_analyze_endpoint_does_not_pass_fast_when_false( - self, run_mock, *_mocks + self, popen_mock, *_mocks ) -> None: with ( patch.object(server, "_current_time", side_effect=_timing_points(), create=True), @@ -551,7 +684,7 @@ def test_analyze_endpoint_does_not_pass_fast_when_false( ) self.assertEqual(response.status_code, 200) - command = run_mock.call_args.args[0] + command = popen_mock.call_args.args[0] self.assertNotIn("--fast", command) payload = self._decode_json_response(response) self.assertNotIn("--fast", payload["diagnostics"]["timings"]["flagsUsed"]) @@ -612,8 +745,13 @@ def test_estimate_endpoint_returns_preflight_contract(self, *_mocks) -> None: ) @patch.object( server.subprocess, - "run", - side_effect=_make_timeout_expired(), + "Popen", + side_effect=_fake_popen_factory( + returncode=1, + stdout="partial stdout", + stderr="partial stderr", + raise_timeout_once=True, + ), ) def test_timeout_response_uses_structured_json_contract(self, *_mocks) -> None: with ( @@ -682,9 +820,8 @@ def test_timeout_response_uses_structured_json_contract(self, *_mocks) -> None: ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout="{not-json", stderr="broken payload", @@ -752,9 +889,8 @@ def test_invalid_json_response_includes_timing_breakdown(self, *_mocks) -> None: ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout=json.dumps( { @@ -826,7 +962,7 @@ def test_success_response_includes_estimate_diagnostics(self, *_mocks) -> None: ) @patch.object( server.subprocess, - "run", + "Popen", side_effect=RuntimeError("disk full"), ) def test_internal_error_returns_500_with_structured_envelope(self, *_mocks) -> None: @@ -880,9 +1016,8 @@ def test_internal_error_returns_500_with_structured_envelope(self, *_mocks) -> N ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + "Popen", + side_effect=_fake_popen_factory( returncode=1, stdout="partial output", stderr="segfault in essentia", @@ -941,9 +1076,8 @@ def test_nonzero_exit_returns_502_analyzer_failed(self, *_mocks) -> None: ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout="", stderr="analyze finished with no output", @@ -1001,9 +1135,8 @@ def test_empty_stdout_returns_502_analyzer_empty_output(self, *_mocks) -> None: ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=["./venv/bin/python", "analyze.py", "track.mp3", "--yes"], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout="[1, 2, 3]", stderr="", @@ -1461,6 +1594,11 @@ def test_stem_summary_profile_uses_dedicated_prompt_schema_and_hooks(self) -> No patch.dict(server.os.environ, {"GEMINI_API_KEY": "fake-key"}), patch.object(server, "_genai") as mock_genai, patch.object(server, "_genai_types") as mock_genai_types, + patch.object( + runtime, + "update_interpretation_attempt_progress", + wraps=runtime.update_interpretation_attempt_progress, + ) as progress_mock, ): mock_genai.Client.return_value = mock_client mock_genai_types.GenerateContentConfig.return_value = unittest.mock.MagicMock() @@ -1483,6 +1621,19 @@ def test_stem_summary_profile_uses_dedicated_prompt_schema_and_hooks(self) -> No self.assertIn("MEASUREMENT_DERIVED_DESCRIPTOR_HOOKS", prompt) self.assertIn("stableBarGrid", prompt) self.assertIn("pumpingOrModulationDescriptor", prompt) + progress_steps = [ + call.kwargs["step_key"] for call in progress_mock.call_args_list + ] + self.assertEqual( + progress_steps, + [ + "prepare_grounding", + "build_prompt", + "send_request", + "parse_output", + "finalize_output", + ], + ) snapshot = runtime.get_run(run_id) self.assertEqual(snapshot["stages"]["interpretation"]["attemptsSummary"][0]["profileId"], "stem_summary") self.assertEqual(snapshot["stages"]["interpretation"]["result"]["summary"], _valid_stem_summary_result()["summary"]) @@ -1549,9 +1700,8 @@ def _decode(self, response) -> dict: ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=[], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout=_MINIMAL_ANALYZE_PAYLOAD, stderr="", @@ -1602,9 +1752,8 @@ def test_analyze_returns_analysis_run_id_and_persists_measurement(self, *_mocks) ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=[], + "Popen", + side_effect=_fake_popen_factory( returncode=1, stdout="", stderr="error", @@ -1651,9 +1800,8 @@ def test_analyze_marks_measurement_failed_when_subprocess_errors(self, *_mocks) ) @patch.object( server.subprocess, - "run", - return_value=subprocess.CompletedProcess( - args=[], + "Popen", + side_effect=_fake_popen_factory( returncode=0, stdout=json.dumps({ "bpm": 128, @@ -1842,6 +1990,11 @@ def test_symbolic_worker_uses_analyze_transcription_protocol_entry_point(self) - } }, ) as analyze_transcription_mock, + patch.object( + runtime, + "update_symbolic_attempt_progress", + wraps=runtime.update_symbolic_attempt_progress, + ) as progress_mock, ): server._execute_symbolic_attempt( runtime, @@ -1857,6 +2010,18 @@ def test_symbolic_worker_uses_analyze_transcription_protocol_entry_point(self) - kwargs = analyze_transcription_mock.call_args.kwargs self.assertIn("stem_paths", kwargs) self.assertEqual(set(kwargs["stem_paths"].keys()), {"bass", "other"}) + progress_steps = [ + call.kwargs["step_key"] for call in progress_mock.call_args_list + ] + self.assertEqual( + progress_steps, + [ + "prepare_attempt", + "resolve_stems", + "run_backend", + "finalize_output", + ], + ) snapshot = runtime.get_run(created["runId"]) self.assertEqual(snapshot["stages"]["symbolicExtraction"]["status"], "completed") self.assertEqual( diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index a521e582..1244fce8 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -231,6 +231,7 @@ export default function App() { const phase2HelperCopy = getInterpretationHelperCopy(phase2ConfigEnabled, interpretationRequested); const phase2ModelSelectorDisabled = isAnalyzing || !phase2ConfigEnabled || !interpretationRequested; const cpuMeterPercent = useCpuMeter(isAnalyzing); + const audioElementRef = useRef(null); const previousRunRef = useRef(null); const completionRef = useRef<{ measurement: boolean; interpretation: boolean }>({ measurement: false, @@ -855,6 +856,16 @@ export default function App() { } }, [activeRunId, audioFile, interpretationRequested, phase2ConfigEnabled, selectedModel, symbolicExtractionRequested]); + const handleAudioElement = useCallback((el: HTMLAudioElement) => { + audioElementRef.current = el; + }, []); + + const handleSpectrogramSeek = useCallback((timeSeconds: number) => { + if (audioElementRef.current) { + audioElementRef.current.currentTime = timeSeconds; + } + }, []); + const isAnalyzeDisabled = isAnalyzing || estimateWrongService; const hasRetryableRunStage = Boolean( analysisRun && @@ -1048,9 +1059,7 @@ export default function App() {
-

- {isAnalyzing ? 'Local DSP Status' : 'Signal Monitor'} -

+

Signal Monitor

@@ -1059,52 +1068,52 @@ export default function App() { className="flex-grow bg-bg-card border border-border rounded-b-sm p-4 relative flex flex-col" > {audioUrl && audioFile ? ( - shouldShowStatusPanel ? ( - - ) : ( -
- - - {!measurementResult && ( -
-
-
-

Local DSP first

-

Estimated local analysis

-

- {isEstimateLoading - ? 'Calculating estimate...' - : analysisEstimate - ? formatEstimateRange(analysisEstimate) - : 'Unavailable'} -

-
-
- Measurement runs locally first. Symbolic extraction and AI interpretation only start after measurement succeeds. -
-
- {estimateError && ( -

- {estimateWrongService ? estimateError : `Estimate unavailable: ${estimateError}`} +

+ + + {!measurementResult && ( +
+
+
+

Local DSP first

+

Estimated local analysis

+

+ {isEstimateLoading + ? 'Calculating estimate...' + : analysisEstimate + ? formatEstimateRange(analysisEstimate) + : 'Unavailable'}

- )} +
+
+ Measurement runs locally first. Symbolic extraction and AI interpretation only start after measurement succeeds. +
- )} -
- ) + {estimateError && ( +

+ {estimateWrongService ? estimateError : `Estimate unavailable: ${estimateError}`} +

+ )} +
+ )} + + {shouldShowStatusPanel && ( + + )} +
) : (
@@ -1166,6 +1175,9 @@ export default function App() { phase2={phase2Result} phase2StatusMessage={phase2StatusMessage} sourceFileName={audioFile?.name ?? null} + audioFile={audioFile ?? undefined} + audioElementRef={audioElementRef} + onSeek={handleSpectrogramSeek} /> ) : null} diff --git a/apps/ui/src/components/AnalysisResults.tsx b/apps/ui/src/components/AnalysisResults.tsx index 8e9c9ed1..4dbfd60a 100644 --- a/apps/ui/src/components/AnalysisResults.tsx +++ b/apps/ui/src/components/AnalysisResults.tsx @@ -22,8 +22,15 @@ import { INTERPRETATION_LABEL } from '../services/phaseLabels'; import { SessionMusicianPanel } from './SessionMusicianPanel'; import { PhaseSourceBadge } from './PhaseSourceBadge'; import { StickyNav, type StickyNavSection } from './StickyNav'; +import { ChordProgressionPanel } from './ChordProgressionPanel'; +import { SpectralBalanceCurve } from './SpectralBalanceCurve'; +import { SegmentSpectralProfile } from './SegmentSpectralProfile'; +import { ChromaDisplay } from './ChromaDisplay'; +import { DetectorAnalysisGrid } from './DetectorAnalysisGrid'; +import { MelSpectrogram } from './MelSpectrogram'; import { buildArrangementViewModel, + buildChromaViewModel, buildMixChainGroups, buildPatchCards, buildSonicElementCards, @@ -39,6 +46,9 @@ interface AnalysisResultsProps { phase2: Phase2Result | null; phase2StatusMessage?: string | null; sourceFileName?: string | null; + audioFile?: File; + audioElementRef?: React.RefObject; + onSeek?: (timeSeconds: number) => void; } export function toggleOpenKeySet(previous: ReadonlySet, id: string): Set { @@ -162,11 +172,29 @@ function toFiniteNumber(value: unknown): number | null { function getChordStrength(phase1: MeasurementResult): number | null { const chordDetail = phase1.chordDetail; - if (!chordDetail || typeof chordDetail !== 'object' || Array.isArray(chordDetail)) { - return null; - } + if (!chordDetail) return null; + return toFiniteNumber(chordDetail.chordStrength); +} - return toFiniteNumber((chordDetail as Record).chordStrength); +function hasRenderableChordContent(chordDetail: MeasurementResult['chordDetail']): boolean { + if (!chordDetail) return false; + if (Array.isArray(chordDetail.chordSequence) && chordDetail.chordSequence.length > 0) return true; + if (Array.isArray(chordDetail.progression) && chordDetail.progression.length > 0) return true; + return false; +} + +function hasDetectorData(measurement: MeasurementResult): boolean { + return Boolean( + measurement.acidDetail || + measurement.reverbDetail || + measurement.vocalDetail || + measurement.supersawDetail || + measurement.bassDetail || + measurement.kickDetail || + measurement.genreDetail || + measurement.sidechainDetail || + measurement.synthesisCharacter, + ); } export function AnalysisResults({ @@ -175,6 +203,9 @@ export function AnalysisResults({ phase2, phase2StatusMessage = null, sourceFileName = null, + audioFile, + audioElementRef, + onSeek, }: AnalysisResultsProps) { const [openArrangement, setOpenArrangement] = useState>({}); const [openSonic, setOpenSonic] = useState>(new Set()); @@ -257,6 +288,8 @@ export function AnalysisResults({ ? phase2.detectedCharacteristics.slice(0, 4) : []; const danceability = measurement.danceability; + const hasChordSection = hasRenderableChordContent(measurement.chordDetail); + const hasDetectors = hasDetectorData(measurement); const keyIsApproximate = measurement.keyConfidence <= 0.6; const chordStrength = getChordStrength(measurement); const chordsAreApproximate = chordStrength !== null && chordStrength <= 0.7; @@ -267,7 +300,13 @@ export function AnalysisResults({ sonicCards.length > 0 || mixGroups.length > 0 || patchCards.length > 0; + const chromaViewModel = buildChromaViewModel(measurement.spectralDetail?.chroma, measurement.key); + const hasChroma = chromaViewModel !== null; const navSections: StickyNavSection[] = [ + hasChordSection ? { id: 'section-chords', label: 'Chords' } : null, + hasDetectors ? { id: 'section-detectors', label: 'Detectors' } : null, + { id: 'section-spectral-balance', label: 'Spectral' }, + hasChroma ? { id: 'section-spectral', label: 'Chroma' } : null, arrangement ? { id: 'section-arrangement', label: 'Arrangement' } : null, { id: 'section-session', label: 'Session' }, sonicCards.length > 0 ? { id: 'section-sonic-elements', label: 'Sonic' } : null, @@ -406,6 +445,17 @@ export function AnalysisResults({
+ {hasChordSection && ( +
+ +
+ )} + {danceability && (
@@ -435,206 +485,36 @@ export function AnalysisResults({
)} - {(() => { - const { acidDetail, reverbDetail, vocalDetail, supersawDetail, bassDetail, kickDetail, genreDetail, sidechainDetail, synthesisCharacter } = measurement; - const hasAny = acidDetail || reverbDetail || vocalDetail || supersawDetail || bassDetail || kickDetail || genreDetail || sidechainDetail || synthesisCharacter; - if (!hasAny) return null; - return ( -
-
-

- - Detector Analysis -

- - PHASE 1 - -
-
- {acidDetail && ( -
-

Acid Bass

-

{acidDetail.isAcid ? 'DETECTED' : 'NOT DETECTED'}

-

Confidence: {(acidDetail.confidence * 100).toFixed(0)}%

- {acidDetail.isAcid && ( -

Resonance: {acidDetail.resonanceLevel.toFixed(2)}

- )} -
- )} - {reverbDetail && ( -
-

Reverb

-

{reverbDetail.isWet ? 'WET' : 'DRY'}

- {reverbDetail.measured && reverbDetail.rt60 !== null ? ( -

RT60: {reverbDetail.rt60.toFixed(2)}s

- ) : ( -

RT60: N/A

- )} -
- )} - {vocalDetail && ( -
-

Vocals

-

{vocalDetail.hasVocals ? 'PRESENT' : 'ABSENT'}

-

Confidence: {(vocalDetail.confidence * 100).toFixed(0)}%

-
- )} - {supersawDetail && ( -
-

Supersaw

-

{supersawDetail.isSupersaw ? 'DETECTED' : 'NOT DETECTED'}

-

Confidence: {(supersawDetail.confidence * 100).toFixed(0)}%

- {supersawDetail.isSupersaw && ( -

Voices: {supersawDetail.voiceCount}

- )} -
- )} - {bassDetail && ( -
-

Bass Character

-

{bassDetail.type}

-

Decay: {bassDetail.averageDecayMs.toFixed(0)}ms

-

Groove: {bassDetail.grooveType}

-
- )} - {kickDetail && ( -
-

Kick

-

{kickDetail.kickCount} HITS

-

Fundamental: {kickDetail.fundamentalHz.toFixed(0)}Hz

- {kickDetail.isDistorted && ( -

DISTORTED

- )} -
- )} - {genreDetail && ( -
-

Genre Classification

-

- {genreDetail.genre.replace(/-/g, ' ').toUpperCase()} -

-

- Confidence: {(genreDetail.confidence * 100).toFixed(0)}% - {genreDetail.confidence < 0.5 && (uncertain)} -

-

Family: {genreDetail.genreFamily}

- {genreDetail.secondaryGenre && ( -

- Secondary: {genreDetail.secondaryGenre.replace(/-/g, ' ')} -

- )} -
- )} - {sidechainDetail && (() => { - const sc = sidechainDetail as Record; - const strength = typeof sc.pumpingStrength === 'number' ? sc.pumpingStrength : null; - const rate = typeof sc.pumpingRate === 'string' ? sc.pumpingRate : null; - const confidence = typeof sc.pumpingConfidence === 'number' ? sc.pumpingConfidence : null; - if (strength === null) return null; - return ( -
-

Sidechain

-

- {(strength * 100).toFixed(0)}% PUMP -

- {rate && ( -

Rate: {rate}

- )} - {confidence !== null && ( -

- Confidence: {(confidence * 100).toFixed(0)}% -

- )} -
- ); - })()} - {synthesisCharacter && (() => { - const sc = synthesisCharacter as Record; - const inharmonicity = typeof sc.inharmonicity === 'number' ? sc.inharmonicity : null; - const oddToEven = typeof sc.oddToEvenRatio === 'number' ? sc.oddToEvenRatio : null; - if (inharmonicity === null && oddToEven === null) return null; - const synthLabel = inharmonicity !== null - ? inharmonicity > 0.25 ? 'WAVETABLE / NOISE' - : inharmonicity >= 0.10 ? 'FM / ACID' - : 'CLEAN SUBTRACTIVE' - : null; - const waveLabel = oddToEven !== null - ? oddToEven > 1.5 ? 'Saw / Square' - : oddToEven < 0.8 ? 'Sine / Triangle' - : 'Mixed Harmonics' - : null; - return ( -
-

Synthesis Character

- {synthLabel && ( -

{synthLabel}

- )} - {inharmonicity !== null && ( -

- Inharmonicity: {inharmonicity.toFixed(3)} -

- )} - {waveLabel && ( -

Waveform: {waveLabel}

- )} - {oddToEven !== null && ( -

- Odd/Even Ratio: {oddToEven.toFixed(2)} -

- )} -
- ); - })()} -
-
- ); - })()} - - {(() => { - const { spectralBalance } = measurement; - if (!spectralBalance) return null; - const bands = [ - { label: 'Sub Bass', value: spectralBalance.subBass }, - { label: 'Low Bass', value: spectralBalance.lowBass }, - { label: 'Mids', value: spectralBalance.mids }, - { label: 'Upper Mids', value: spectralBalance.upperMids }, - { label: 'Highs', value: spectralBalance.highs }, - { label: 'Brilliance', value: spectralBalance.brilliance }, - ]; - const maxAbs = Math.max(...bands.map(b => Math.abs(b.value)), 1); - return ( -
-
-

- - Spectral Balance -

- - PHASE 1 - -
-
- {bands.map(band => { - const pct = (Math.abs(band.value) / maxAbs) * 100; - return ( -
- {band.label} -
-
-
- - {band.value > 0 ? '+' : ''}{band.value.toFixed(1)} dB - -
- ); - })} -
-
- ); - })()} + {hasDetectors && ( +
+ +
+ )} + +
+ + {audioFile && audioElementRef && ( + + )} + +
+ + {hasChroma && chromaViewModel && ( +
+ +
+ )} {profiles.length > 0 && ( ): string | null { + const progress = stage.diagnostics?.progress; + if (!progress || typeof progress.message !== 'string') { + return null; + } + const message = progress.message.trim(); + return message.length > 0 ? message : null; +} + +export function resolveStageSummary( + run: AnalysisRunSnapshot | null, + stageKey: StageKey, +): StageSummaryDescriptor { if (!run) { - return 'Awaiting run state.'; + return { message: 'Awaiting run state.', typewriterSeed: null }; } - const stage = - stageKey === 'measurement' - ? run.stages.measurement - : stageKey === 'symbolicExtraction' - ? run.stages.symbolicExtraction - : run.stages.interpretation; + const stage = getStageSnapshot(run, stageKey); if (stage.error?.message) { - return stage.error.message; + return { message: stage.error.message, typewriterSeed: null }; } switch (stage.status) { case 'queued': - return 'Queued locally.'; - case 'running': - return 'Currently processing.'; + return { message: 'Queued locally.', typewriterSeed: null }; + case 'running': { + const progressMessage = getRunningProgressMessage(stage); + if (progressMessage) { + return { + message: progressMessage, + typewriterSeed: progressMessage, + }; + } + return { message: 'Currently processing.', typewriterSeed: null }; + } case 'blocked': - return 'Waiting for measurement to finish.'; + return { message: 'Waiting for measurement to finish.', typewriterSeed: null }; case 'ready': - return 'Ready for retry.'; + return { message: 'Ready for retry.', typewriterSeed: null }; case 'completed': - return stageKey === 'measurement' - ? 'Authoritative local measurement complete.' - : stageKey === 'symbolicExtraction' - ? 'Best-effort symbolic output available.' - : 'Grounded musical interpretation available.'; + return { + message: stageKey === 'measurement' + ? 'Authoritative local measurement complete.' + : stageKey === 'symbolicExtraction' + ? 'Best-effort symbolic output available.' + : 'Grounded musical interpretation available.', + typewriterSeed: null, + }; case 'failed': - return 'Stage failed.'; + return { message: 'Stage failed.', typewriterSeed: null }; case 'interrupted': - return 'Stage was interrupted and can be retried.'; + return { message: 'Stage was interrupted and can be retried.', typewriterSeed: null }; case 'not_requested': - return 'Not requested for this run.'; + return { message: 'Not requested for this run.', typewriterSeed: null }; default: - return 'Awaiting stage state.'; + return { message: 'Awaiting stage state.', typewriterSeed: null }; } } +function TypewriterLine({ text }: { text: string }) { + const [visibleText, setVisibleText] = React.useState(text); + + React.useEffect(() => { + if (typeof window === 'undefined') { + setVisibleText(text); + return; + } + if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) { + setVisibleText(text); + return; + } + + setVisibleText(''); + let cursor = 0; + const intervalId = window.setInterval(() => { + cursor += 1; + setVisibleText(text.slice(0, cursor)); + if (cursor >= text.length) { + window.clearInterval(intervalId); + } + }, 14); + + return () => window.clearInterval(intervalId); + }, [text]); + + return {visibleText}; +} + export function AnalysisStatusPanel({ run, elapsedMs, @@ -240,7 +305,17 @@ export function AnalysisStatusPanel({ {stageStatusLabel(card.status)}
-

{stageSummary(run, card.key)}

+ {(() => { + const summary = resolveStageSummary(run, card.key); + if (summary.typewriterSeed) { + return ( +

+ +

+ ); + } + return

{summary.message}

; + })()} {card.onRetry && (card.status === 'failed' || card.status === 'interrupted' || card.status === 'ready') && (