prompt_objects 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/README.md +93 -22
  4. data/exe/poop +5 -0
  5. data/exe/prompt_objects +1 -1108
  6. data/exe/prompt_objects_mcp +5 -3
  7. data/lib/prompt_objects/capability.rb +33 -0
  8. data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
  9. data/lib/prompt_objects/cli/env_command.rb +435 -0
  10. data/lib/prompt_objects/cli/events_command.rb +145 -0
  11. data/lib/prompt_objects/cli/explore_command.rb +155 -0
  12. data/lib/prompt_objects/cli/helpers.rb +91 -0
  13. data/lib/prompt_objects/cli/message_command.rb +216 -0
  14. data/lib/prompt_objects/cli/repl_command.rb +366 -0
  15. data/lib/prompt_objects/cli/serve_command.rb +162 -0
  16. data/lib/prompt_objects/cli.rb +64 -422
  17. data/lib/prompt_objects/connectors/base.rb +16 -29
  18. data/lib/prompt_objects/connectors/mcp.rb +82 -98
  19. data/lib/prompt_objects/domain/records.rb +45 -0
  20. data/lib/prompt_objects/environment/exporter.rb +5 -4
  21. data/lib/prompt_objects/environment/git.rb +21 -1
  22. data/lib/prompt_objects/environment/importer.rb +5 -4
  23. data/lib/prompt_objects/environment/manager.rb +44 -6
  24. data/lib/prompt_objects/environment/manifest.rb +26 -4
  25. data/lib/prompt_objects/environment.rb +1337 -20
  26. data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
  27. data/lib/prompt_objects/execution/context.rb +137 -0
  28. data/lib/prompt_objects/execution/contracts.rb +59 -0
  29. data/lib/prompt_objects/execution/permit_pool.rb +49 -0
  30. data/lib/prompt_objects/execution/policy.rb +47 -0
  31. data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
  32. data/lib/prompt_objects/execution/runner.rb +498 -0
  33. data/lib/prompt_objects/execution/scheduler.rb +325 -0
  34. data/lib/prompt_objects/llm/factory.rb +4 -4
  35. data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
  36. data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
  37. data/lib/prompt_objects/llm/pricing.rb +9 -2
  38. data/lib/prompt_objects/llm/response.rb +9 -4
  39. data/lib/prompt_objects/loader.rb +28 -1
  40. data/lib/prompt_objects/mcp/server.rb +6 -3
  41. data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
  42. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
  43. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
  44. data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
  45. data/lib/prompt_objects/operations/diagnostics.rb +115 -0
  46. data/lib/prompt_objects/operations/retention.rb +75 -0
  47. data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
  48. data/lib/prompt_objects/primitives/http_get.rb +2 -0
  49. data/lib/prompt_objects/primitives/list_files.rb +2 -0
  50. data/lib/prompt_objects/primitives/read_file.rb +2 -0
  51. data/lib/prompt_objects/primitives/think.rb +36 -0
  52. data/lib/prompt_objects/primitives/write_file.rb +9 -0
  53. data/lib/prompt_objects/prompt_object.rb +171 -519
  54. data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
  55. data/lib/prompt_objects/repositories/base_repository.rb +37 -0
  56. data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
  57. data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
  58. data/lib/prompt_objects/repositories/event_repository.rb +105 -0
  59. data/lib/prompt_objects/repositories/message_repository.rb +73 -0
  60. data/lib/prompt_objects/repositories/run_repository.rb +254 -0
  61. data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
  62. data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
  63. data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
  64. data/lib/prompt_objects/server/api/routes.rb +549 -53
  65. data/lib/prompt_objects/server/app.rb +133 -14
  66. data/lib/prompt_objects/server/file_watcher.rb +9 -0
  67. data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
  68. data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
  69. data/lib/prompt_objects/server/public/index.html +2 -2
  70. data/lib/prompt_objects/server/websocket_handler.rb +202 -227
  71. data/lib/prompt_objects/server.rb +170 -1
  72. data/lib/prompt_objects/service.rb +129 -0
  73. data/lib/prompt_objects/session/store.rb +600 -13
  74. data/lib/prompt_objects/universal/add_capability.rb +1 -1
  75. data/lib/prompt_objects/universal/create_capability.rb +4 -2
  76. data/lib/prompt_objects/universal/delete_env_data.rb +23 -15
  77. data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
  78. data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
  79. data/lib/prompt_objects/universal/get_env_data.rb +8 -14
  80. data/lib/prompt_objects/universal/get_run_status.rb +30 -0
  81. data/lib/prompt_objects/universal/join_runs.rb +35 -0
  82. data/lib/prompt_objects/universal/list_env_data.rb +10 -14
  83. data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
  84. data/lib/prompt_objects/universal/render_artifact.rb +91 -0
  85. data/lib/prompt_objects/universal/request_primitive.rb +1 -1
  86. data/lib/prompt_objects/universal/{store_env_data.rb → set_env_data.rb} +34 -20
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +33 -8
  89. data/objects/coordinator.md +8 -4
  90. data/templates/arc-agi-1/objects/data_manager.md +2 -2
  91. data/templates/arc-agi-1/objects/observer.md +2 -2
  92. data/templates/arc-agi-1/objects/solver.md +6 -5
  93. data/templates/arc-agi-1/objects/verifier.md +2 -2
  94. data/templates/arc-agi-3/manifest.yml +19 -0
  95. data/templates/arc-agi-3/objects/analyst.md +79 -0
  96. data/templates/arc-agi-3/objects/player.md +76 -0
  97. data/templates/arc-agi-3/objects/strategist.md +97 -0
  98. data/templates/arc-agi-3/primitives/action_log.rb +133 -0
  99. data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
  100. data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
  101. data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
  102. data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
  103. data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
  104. data/templates/basic/objects/basic.md +11 -3
  105. data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
  106. data/templates/chiptune/manifest.yml +20 -0
  107. data/templates/chiptune/objects/conductor.md +184 -0
  108. data/templates/chiptune/objects/sonic_pi.md +65 -0
  109. data/templates/chiptune/objects/voice_bass.md +44 -0
  110. data/templates/chiptune/objects/voice_lead.md +44 -0
  111. data/templates/chiptune/objects/voice_pad.md +43 -0
  112. data/templates/chiptune/objects/voice_perc.md +45 -0
  113. data/templates/chiptune/primitives/play_note.rb +59 -0
  114. data/templates/chiptune/primitives/play_sequence.rb +55 -0
  115. data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
  116. data/templates/chiptune/primitives/set_synth.rb +57 -0
  117. data/templates/chiptune/primitives/set_tempo.rb +45 -0
  118. data/templates/chiptune/primitives/stop_voice.rb +46 -0
  119. data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
  120. data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  121. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  122. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  123. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  124. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  125. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  126. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  127. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  128. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  129. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  130. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  131. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  132. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  133. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  134. data/templates/developer/objects/code_reviewer.md +1 -1
  135. data/templates/developer/objects/coordinator.md +16 -3
  136. data/templates/developer/objects/debugger.md +3 -3
  137. data/templates/empty/objects/assistant.md +10 -5
  138. data/templates/minimal/objects/assistant.md +10 -5
  139. data/templates/music/extras/sonic_pi_boot.rb +220 -0
  140. data/templates/music/manifest.yml +16 -0
  141. data/templates/music/objects/conductor.md +154 -0
  142. data/templates/music/objects/sonic_pi.md +65 -0
  143. data/templates/music/primitives/play_note.rb +55 -0
  144. data/templates/music/primitives/play_sequence.rb +51 -0
  145. data/templates/music/primitives/query_sonic_pi.rb +139 -0
  146. data/templates/music/primitives/set_synth.rb +53 -0
  147. data/templates/music/primitives/set_tempo.rb +43 -0
  148. data/templates/music/primitives/stop_voice.rb +44 -0
  149. data/templates/music/primitives/support/osc_bridge.rb +33 -0
  150. data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  151. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  152. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  153. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  154. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  155. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  156. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  157. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  158. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  159. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  160. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  161. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  162. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  163. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  164. data/templates/music-beginner/README.md +100 -0
  165. data/templates/music-beginner/extras/README.md +27 -0
  166. data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
  167. data/templates/music-beginner/manifest.yml +25 -0
  168. data/templates/music-beginner/objects/conductor.md +222 -0
  169. data/templates/music-beginner/objects/engine_setup.md +54 -0
  170. data/templates/music-beginner/objects/voice_bass.md +43 -0
  171. data/templates/music-beginner/objects/voice_keys.md +48 -0
  172. data/templates/music-beginner/objects/voice_pad.md +46 -0
  173. data/templates/music-beginner/objects/voice_percussion.md +45 -0
  174. data/templates/music-beginner/objects/voice_texture.md +37 -0
  175. data/templates/music-beginner/primitives/play_pattern.rb +118 -0
  176. data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
  177. data/templates/music-beginner/primitives/set_instrument.rb +47 -0
  178. data/templates/music-beginner/primitives/set_tempo.rb +43 -0
  179. data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
  180. data/templates/music-beginner/primitives/stop_voice.rb +37 -0
  181. data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
  182. data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  183. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  184. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  185. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  186. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  187. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  188. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  189. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  190. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  191. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  192. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  193. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  194. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  195. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  196. data/templates/music-pro/extras/AUDITION.md +100 -0
  197. data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
  198. data/templates/music-pro/manifest.yml +19 -0
  199. data/templates/music-pro/objects/conductor.md +126 -0
  200. data/templates/music-pro/objects/sonic_pi.md +35 -0
  201. data/templates/music-pro/objects/voice_harmony.md +48 -0
  202. data/templates/music-pro/objects/voice_rhythm.md +43 -0
  203. data/templates/music-pro/objects/voice_texture.md +42 -0
  204. data/templates/music-pro/primitives/define_patch.rb +46 -0
  205. data/templates/music-pro/primitives/query_music_engine.rb +69 -0
  206. data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
  207. data/templates/music-pro/primitives/set_tempo.rb +31 -0
  208. data/templates/music-pro/primitives/stop_voice.rb +31 -0
  209. data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
  210. data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
  211. data/templates/music-pro/primitives/trigger_event.rb +44 -0
  212. data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  213. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  214. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  215. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  216. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  217. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  218. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  219. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  220. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  221. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  222. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  223. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  224. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  225. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  226. data/templates/writer/objects/coordinator.md +14 -3
  227. data/templates/writer/objects/editor.md +1 -1
  228. data/templates/writer/objects/researcher.md +2 -2
  229. data/tools/thread-explorer.html +1 -1
  230. metadata +172 -65
  231. data/CLAUDE.md +0 -177
  232. data/Gemfile +0 -10
  233. data/Rakefile +0 -27
  234. data/frontend/.gitignore +0 -3
  235. data/frontend/index.html +0 -17
  236. data/frontend/package-lock.json +0 -4540
  237. data/frontend/package.json +0 -36
  238. data/frontend/postcss.config.js +0 -6
  239. data/frontend/src/App.tsx +0 -104
  240. data/frontend/src/canvas/CanvasView.tsx +0 -113
  241. data/frontend/src/canvas/ForceLayout.ts +0 -115
  242. data/frontend/src/canvas/SceneManager.ts +0 -587
  243. data/frontend/src/canvas/canvasStore.ts +0 -47
  244. data/frontend/src/canvas/constants.ts +0 -95
  245. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  246. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  247. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  248. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  249. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  250. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  251. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  252. data/frontend/src/canvas/types.ts +0 -24
  253. data/frontend/src/components/ContextMenu.tsx +0 -68
  254. data/frontend/src/components/EnvDataPane.tsx +0 -69
  255. data/frontend/src/components/Inspector.tsx +0 -263
  256. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  257. data/frontend/src/components/MethodList.tsx +0 -90
  258. data/frontend/src/components/ModelSelector.tsx +0 -115
  259. data/frontend/src/components/NotificationPanel.tsx +0 -130
  260. data/frontend/src/components/ObjectList.tsx +0 -78
  261. data/frontend/src/components/PaneSlot.tsx +0 -30
  262. data/frontend/src/components/SourcePane.tsx +0 -202
  263. data/frontend/src/components/SystemBar.tsx +0 -74
  264. data/frontend/src/components/Transcript.tsx +0 -76
  265. data/frontend/src/components/UsagePanel.tsx +0 -105
  266. data/frontend/src/components/Workspace.tsx +0 -260
  267. data/frontend/src/components/index.ts +0 -12
  268. data/frontend/src/hooks/useResize.ts +0 -55
  269. data/frontend/src/hooks/useWebSocket.ts +0 -559
  270. data/frontend/src/index.css +0 -102
  271. data/frontend/src/main.tsx +0 -10
  272. data/frontend/src/store/index.ts +0 -311
  273. data/frontend/src/types/index.ts +0 -176
  274. data/frontend/tailwind.config.js +0 -44
  275. data/frontend/tsconfig.json +0 -30
  276. data/frontend/vite.config.ts +0 -29
  277. data/lib/prompt_objects/server/public/assets/index-DEPawnfZ.js +0 -4345
  278. data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +0 -1
  279. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  280. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  281. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  282. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  283. data/lib/prompt_objects/universal/think.rb +0 -41
  284. data/lib/prompt_objects/universal/update_env_data.rb +0 -88
  285. data/prompt_objects.gemspec +0 -50
  286. data/templates/basic/.gitignore +0 -2
  287. data/templates/developer/.gitignore +0 -5
  288. data/templates/empty/.gitignore +0 -5
  289. data/templates/empty/objects/.gitkeep +0 -0
  290. data/templates/minimal/.gitignore +0 -5
  291. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: sonic_pi
3
+ description: Connection engineer — starts, queries, and troubleshoots the external Sonic Pi live engine
4
+ tools:
5
+ - query_music_engine
6
+ - read_file
7
+ - list_files
8
+ ---
9
+
10
+ # Sonic Pi Connection Engineer
11
+
12
+ You manage the sound-engine connection, not the composition.
13
+
14
+ When asked to connect, call `query_music_engine`. If it does not respond, tell the human to:
15
+
16
+ 1. Open Sonic Pi.
17
+ 2. Enable incoming OSC if Sonic Pi asks for it.
18
+ 3. In an empty buffer run this one line, substituting the environment's absolute path:
19
+
20
+ `run_file "/absolute/path/to/the/environment/extras/sonic_pi_boot.rb"`
21
+
22
+ 4. Press Run, then ask you to check again.
23
+
24
+ The usual installed path is `~/.prompt_objects/environments/<environment-name>/extras/sonic_pi_boot.rb`. Prefer `run_file` to pasting the script because Sonic Pi buffers have size limits.
25
+
26
+ When connected, summarize tempo, active voices, live patterns, patches, transforms, and cycles. A successful query proves that OSC works in both directions. Do not claim that state proves the music sounds good: agents cannot hear Sonic Pi. Invite specific human listening feedback and relay it to the conductor.
27
+
28
+ Connection is not playback. A response with `connected: true` but an empty
29
+ `voices` object means the engine is loaded and silent. Never describe a pattern
30
+ stored in env_data or written in prose as sounding. Only active voice state from
31
+ `query_music_engine` proves that a musician's playback tool reached Sonic Pi.
32
+ Refer a silent-but-connected composition back to `conductor` with instructions
33
+ to require immediate `set_live_pattern` calls and verify each voice.
34
+
35
+ If the engine stops responding, have the human run the same `run_file` line again. Existing live patterns then need to be sent again.
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: voice_harmony
3
+ description: Harmonic musician — owns the voicing, harmonic motion, and sustained tonal field
4
+ tools:
5
+ - set_live_pattern
6
+ - define_patch
7
+ - trigger_event
8
+ - stop_voice
9
+ ---
10
+
11
+ # Voice: Harmony
12
+
13
+ You are an independent harmonic musician. Read `composition` before playing. Own one live voice named `harmony`; only you rewrite it.
14
+
15
+ ## Performance contract
16
+
17
+ When asked to start, join, activate, or revise your part, perform the change in
18
+ that same turn. On first activation you MUST call `set_live_pattern` before
19
+ responding. If you invent a patch, call `define_patch` first and then activate
20
+ that patch with `set_live_pattern`; `define_patch` alone makes no sound. On
21
+ later changes, call every playback tool needed to make the requested change
22
+ real. Do not merely draft notation, ask “shall I activate this?”, or wait for
23
+ confirmation: the conductor's request is authorization. Save state only after
24
+ the playback calls return without an error, and report the calls you actually
25
+ completed.
26
+
27
+ Interpret the contract rather than merely spelling every root-position chord.
28
+ Use voice leading, inversions, common tones, register, rests, and harmonic rhythm
29
+ deliberately. You are the default continuity anchor: cover the contract's full
30
+ harmonic cycle and keep adjacent sustained events connected unless the conductor
31
+ assigns continuity to another voice. A long step is not permission for a short
32
+ sound followed by dead air; default duration-aware gating handles this, and an
33
+ explicit `gate` slightly longer than the step can create deliberate overlap.
34
+ Listen structurally to the other POs through their env_data: leave spectral and
35
+ rhythmic room instead of filling every beat. When directed to evolve, preserve at
36
+ least one audible relationship from your prior pattern.
37
+
38
+ Start with a named patch such as `air_blade` or `soft_rodeo`, or use `define_patch` to invent a timbre. Named patches are examples, not restrictions. Keep initial mix amp around 0.35. Write your exact pattern, patch, intent, register, and relationship to `voice_harmony_state` after each submitted playback change.
39
+
40
+ Useful notation:
41
+
42
+ - `(D3,A3,C4,E4)/8{gate=8.4}` — an eight-beat chord with gentle overlap
43
+ - `~/4` — four beats of explicit silence
44
+ - `[(D3,A3,C4)/2 (G3,B3,E4)/2]/4` — a subdivision stretched to four beats
45
+ - `<(D3,A3,C4) (G3,B3,E4)>/8` — alternate voicings each cycle
46
+ - `{amp=0.7,pan=-0.15,cutoff=68}` — per-event controls
47
+
48
+ Use transforms for longer-form variation, for example `every: {cycles: 4, transform: "reverse"}` or a small transpose/rotation. Avoid rapid dense chords unless the contract explicitly calls for them.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: voice_rhythm
3
+ description: Rhythmic musician — owns pulse, percussion, silence, and the ensemble's changing sense of time
4
+ tools:
5
+ - set_live_pattern
6
+ - define_patch
7
+ - trigger_event
8
+ - stop_voice
9
+ ---
10
+
11
+ # Voice: Rhythm
12
+
13
+ You are an independent rhythm musician. Read `composition` and the other voice states before playing. Own one live voice named `rhythm`; only you rewrite it.
14
+
15
+ ## Performance contract
16
+
17
+ When asked to start, join, activate, or revise your part, perform the change in
18
+ that same turn. On first activation you MUST call `set_live_pattern` before
19
+ responding. If you invent a patch, call `define_patch` first and then activate
20
+ that patch with `set_live_pattern`; `define_patch` alone makes no sound. On
21
+ later changes, call every playback tool needed to make the requested change
22
+ real. Do not merely draft notation, ask “shall I activate this?”, or wait for
23
+ confirmation: the conductor's request is authorization. Save state only after
24
+ the playback calls return without an error, and report the calls you actually
25
+ completed. If the correct musical choice is silence, call `stop_voice` rather
26
+ than merely saying you will remain silent.
27
+
28
+ Rhythm includes silence. Establish a legible pulse or counter-pulse that serves
29
+ the current music, then develop it gradually. If you own the connective layer,
30
+ keep the meter inferable and avoid gaps longer than two beats; quiet events are
31
+ usually better than vanishing. If harmony or texture already carries motion,
32
+ you may answer it more sparsely. Do not default to a constant kick-and-hat loop.
33
+ Patterns may contain built-in samples such as `@bd_haus/0.5` or pitched synth
34
+ events; per-event sample names let one voice combine different percussion
35
+ sounds. Use explicit rests to place them.
36
+
37
+ Example syntax (illustrative, never a mandatory groove):
38
+
39
+ `@bd_haus/0.5{amp=0.35} ~/1.5 @elec_tick/0.25{amp=0.18,pan=0.2} ~/1.75`
40
+
41
+ Create a raw sample patch when you want shared defaults and effects, or use event samples directly. Keep initial mix amp around 0.3. Probability and `every` transformations can create deterministic variation; always seed them and preserve the beat's identity. Write pattern, sources, intent, density, and relationship to `voice_rhythm_state` after every submitted playback rewrite.
42
+
43
+ If the human is speaking or asks for background music, favor low density and unaccented events. If the contract asks for foreground experimental music, you have much more freedom—but still coordinate with the ensemble.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: voice_texture
3
+ description: Texture musician — owns motifs, counterpoint, timbral gestures, and the surprising edge of the composition
4
+ tools:
5
+ - set_live_pattern
6
+ - define_patch
7
+ - trigger_event
8
+ - stop_voice
9
+ ---
10
+
11
+ # Voice: Texture
12
+
13
+ You are an independent texture and motif musician. Read `composition`, `voice_harmony_state`, and `voice_rhythm_state` before playing. Own one live voice named `texture`; only you rewrite it.
14
+
15
+ ## Performance contract
16
+
17
+ When asked to start, join, activate, or revise your part, perform the change in
18
+ that same turn. On first activation you MUST call `set_live_pattern` before
19
+ responding. If you invent a patch, call `define_patch` first and then activate
20
+ that patch with `set_live_pattern`; `define_patch` alone makes no sound. On
21
+ later changes, call every playback tool needed to make the requested change
22
+ real. Do not merely draft notation, ask “shall I activate this?”, or wait for
23
+ confirmation: the conductor's request is authorization. Save state only after
24
+ the playback calls return without an error, and report the calls you actually
25
+ completed.
26
+
27
+ Find a small identity—a two- or three-note contour, a sample gesture, an unusual
28
+ articulation, or a timbral motion—and develop it through recurrence and
29
+ transformation. When you are a foreground motif, build a connected 8- or
30
+ 16-beat phrase from recurring cells rather than one isolated note followed by a
31
+ giant rest. When you are color around another connective voice, sparse gestures
32
+ are welcome. Your purpose is meaningful surprise, not constant decoration. Use
33
+ rests deliberately and avoid occupying the harmony's register continuously.
34
+
35
+ The surface is open-ended. Start from `tiny_kalimba`, `chip_raw`, or a runtime patch, and change raw synth/effect parameters when that serves the idea. Examples:
36
+
37
+ - `C5/0.5 E5/0.5 ~/3` — a tiny motif with space
38
+ - `<C5 D5 G4>/0.5 ~/3.5` — one alternating note per cycle
39
+ - `[C5/1 E5/1 G5/1]/1 ~/3` — compressed subdivision
40
+ - `@ambi_glass_hum/1{amp=0.2,pan=0.4} ~/7` — a sparse sample gesture
41
+
42
+ Keep initial mix amp around 0.25. Use a deterministic seed for probability. After a submitted playback rewrite, store the pattern, patch, motif identity, register, and reason for the change in `voice_texture_state`.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "support/osc_bridge"
5
+ require_relative "support/music_protocol"
6
+
7
+ module PromptObjects
8
+ module Primitives
9
+ class DefinePatch < Primitive
10
+ execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
11
+ ["music:patch:#{arguments[:name] || arguments['name']}"]
12
+ }
13
+
14
+ def name
15
+ "define_patch"
16
+ end
17
+
18
+ def description
19
+ "Define or replace a reusable Sonic Pi patch at runtime. A patch can name any Sonic Pi synth or built-in sample, set raw parameters, and add up to three raw effects."
20
+ end
21
+
22
+ def parameters
23
+ {
24
+ type: "object",
25
+ properties: {
26
+ name: { type: "string", description: "Patch name used by set_live_pattern" },
27
+ patch: {
28
+ type: "object",
29
+ description: "{kind: 'synth', synth: 'blade', params: {...}, effects: [{name: 'reverb', params: {...}}]} or a sample equivalent"
30
+ }
31
+ },
32
+ required: %w[name patch]
33
+ }
34
+ end
35
+
36
+ def receive(message, context:)
37
+ name = MusicProtocol.patch_name!(message[:name] || message["name"])
38
+ patch = MusicProtocol.patch!(message[:patch] || message["patch"])
39
+ OscBridge.send_message("/prompt_objects/music/patch", name, JSON.generate(patch))
40
+ "Defined live patch #{name} (#{patch['kind'] || 'synth'}: #{patch['synth'] || patch['sample']})"
41
+ rescue => e
42
+ "Error defining patch: #{e.message}. Is Sonic Pi running the music-pro boot script?"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "socket"
5
+ require_relative "support/osc_bridge"
6
+
7
+ module PromptObjects
8
+ module Primitives
9
+ class QueryMusicEngine < Primitive
10
+ execution_policy parallel_safe: true, side_effect: :read
11
+
12
+ TIMEOUT = 2
13
+
14
+ def name = "query_music_engine"
15
+
16
+ def description
17
+ "Read the live state reported by Sonic Pi: tempo, safety limits, available patch names, and every voice's active pattern, patch summary, transforms, and cycle count."
18
+ end
19
+
20
+ def parameters
21
+ { type: "object", properties: {}, required: [] }
22
+ end
23
+
24
+ def receive(message, context:)
25
+ socket = UDPSocket.new
26
+ socket.bind("127.0.0.1", 0)
27
+ response_port = socket.addr[1]
28
+ OscBridge.send_message("/prompt_objects/music/query", response_port)
29
+
30
+ unless IO.select([socket], nil, nil, TIMEOUT)
31
+ return "Sonic Pi is not responding. Run extras/sonic_pi_boot.rb in Sonic Pi and try again."
32
+ end
33
+
34
+ data, = socket.recvfrom(65_535)
35
+ payload = parse_osc_string(data)
36
+ state = JSON.parse(payload)
37
+ JSON.pretty_generate(state)
38
+ rescue JSON::ParserError => e
39
+ "Sonic Pi returned invalid state: #{e.message}"
40
+ rescue => e
41
+ "Error querying music engine: #{e.message}"
42
+ ensure
43
+ socket&.close
44
+ end
45
+
46
+ private
47
+
48
+ def parse_osc_string(data)
49
+ raise "empty OSC response" if data.nil? || data.empty?
50
+
51
+ address_end = data.index("\x00") or raise "malformed OSC address"
52
+ pos = padded_position(address_end + 1)
53
+ raise "OSC response has no string argument" unless data[pos] == ","
54
+
55
+ type_end = data.index("\x00", pos) or raise "malformed OSC type tag"
56
+ raise "OSC response is not a string" unless data[(pos + 1)...type_end].start_with?("s")
57
+
58
+ pos = padded_position(type_end + 1)
59
+ string_end = data.index("\x00", pos) or raise "malformed OSC string"
60
+ data[pos...string_end]
61
+ end
62
+
63
+ def padded_position(pos)
64
+ pos += 1 while (pos % 4) != 0
65
+ pos
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "support/osc_bridge"
5
+ require_relative "support/music_protocol"
6
+
7
+ module PromptObjects
8
+ module Primitives
9
+ class SetLivePattern < Primitive
10
+ execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
11
+ ["music:voice:#{arguments[:voice] || arguments['voice']}"]
12
+ }
13
+
14
+ def name
15
+ "set_live_pattern"
16
+ end
17
+
18
+ def description
19
+ "Create or atomically rewrite a named live voice pattern. Supports notes, chords, rests, samples, subdivisions, alternation, repetition, per-event controls, and cycle transforms. The new definition starts on the next bar."
20
+ end
21
+
22
+ def parameters
23
+ {
24
+ type: "object",
25
+ properties: {
26
+ voice: { type: "string", description: "Stable voice name owned by this Prompt Object" },
27
+ pattern: {
28
+ type: "string",
29
+ description: "Pattern notation: C4/1, (C4,E4,G4)/2 chord, ~/1 rest, @bd_haus/0.5 sample, [C4/0.5 E4/0.5]/1 subdivision, <C4 E4 G4>/1 cycle alternation, and *3 repetition. Duration sets the next onset and defaults synth gate near that length; use {gate=1.2} for overlap or articulation, alongside amp, pan, cutoff, or probability."
30
+ },
31
+ patch: {
32
+ description: "Named patch, or inline patch object with kind, synth/sample, params, and up to three effects",
33
+ oneOf: [{ type: "string" }, { type: "object" }]
34
+ },
35
+ transforms: {
36
+ type: "object",
37
+ description: "Optional transpose, stretch, rotate, reverse, probability, seed, or every-cycle transform"
38
+ },
39
+ mix: {
40
+ type: "object",
41
+ description: "Voice amp and pan. Final amplitude is stage-clamped by the engine."
42
+ }
43
+ },
44
+ required: %w[voice pattern]
45
+ }
46
+ end
47
+
48
+ def receive(message, context:)
49
+ voice = MusicProtocol.voice!(message[:voice] || message["voice"])
50
+ pattern = MusicProtocol.pattern!(message[:pattern] || message["pattern"])
51
+ patch = message[:patch] || message["patch"] || "soft_rodeo"
52
+ patch = patch.is_a?(Hash) ? MusicProtocol.patch!(patch) : MusicProtocol.patch_name!(patch)
53
+ transforms = MusicProtocol.transforms!(message[:transforms] || message["transforms"])
54
+ mix = MusicProtocol.mix!(message[:mix] || message["mix"])
55
+
56
+ definition = {
57
+ voice: voice,
58
+ pattern: pattern,
59
+ patch: patch,
60
+ transforms: transforms,
61
+ mix: mix
62
+ }
63
+ OscBridge.send_message("/prompt_objects/music/pattern", JSON.generate(definition))
64
+ "Queued #{voice} for the next bar: #{pattern}"
65
+ rescue => e
66
+ "Error setting live pattern: #{e.message}. Is Sonic Pi running the music-pro boot script?"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "support/osc_bridge"
4
+
5
+ module PromptObjects
6
+ module Primitives
7
+ class SetTempo < Primitive
8
+ execution_policy parallel_safe: false, side_effect: :exclusive
9
+
10
+ def name = "set_tempo"
11
+
12
+ def description
13
+ "Set the shared live-engine tempo. Values are stage-bounded to 30-240 BPM."
14
+ end
15
+
16
+ def parameters
17
+ { type: "object", properties: { bpm: { type: "number", description: "Tempo from 30 through 240 BPM" } }, required: ["bpm"] }
18
+ end
19
+
20
+ def receive(message, context:)
21
+ bpm = (message[:bpm] || message["bpm"]).to_f
22
+ return "Error: bpm must be between 30 and 240" unless bpm.between?(30, 240)
23
+
24
+ OscBridge.send_message("/prompt_objects/music/tempo", bpm)
25
+ "Tempo queued at #{bpm} BPM"
26
+ rescue => e
27
+ "Error setting tempo: #{e.message}. Is Sonic Pi running the music-pro boot script?"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "support/osc_bridge"
4
+ require_relative "support/music_protocol"
5
+
6
+ module PromptObjects
7
+ module Primitives
8
+ class StopVoice < Primitive
9
+ execution_policy parallel_safe: false, side_effect: :exclusive
10
+
11
+ def name = "stop_voice"
12
+
13
+ def description
14
+ "Mute a named live voice on the next bar, or use all for the emergency master stop."
15
+ end
16
+
17
+ def parameters
18
+ { type: "object", properties: { voice: { type: "string", description: "Voice name or all" } }, required: ["voice"] }
19
+ end
20
+
21
+ def receive(message, context:)
22
+ raw = (message[:voice] || message["voice"]).to_s
23
+ voice = raw == "all" ? raw : MusicProtocol.voice!(raw)
24
+ OscBridge.send_message("/prompt_objects/music/stop", voice)
25
+ voice == "all" ? "Emergency stop sent for all voices" : "Queued mute for #{voice}"
26
+ rescue => e
27
+ "Error stopping voice: #{e.message}. Is Sonic Pi running the music-pro boot script?"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module PromptObjects
6
+ module Primitives
7
+ # Validation and serialization shared by the live-music tools. Sonic Pi is
8
+ # deliberately permissive about timbre, while this boundary keeps malformed
9
+ # or accidentally enormous agent messages away from the stage audio engine.
10
+ module MusicProtocol
11
+ MAX_PATTERN_BYTES = 4096
12
+ MAX_PATCH_BYTES = 8192
13
+ MAX_EFFECTS = 3
14
+ MAX_PARAMS = 24
15
+ VOICE_NAME = /\A[a-zA-Z][a-zA-Z0-9_-]{0,39}\z/
16
+ PATCH_NAME = /\A[a-zA-Z][a-zA-Z0-9_-]{0,39}\z/
17
+
18
+ module_function
19
+
20
+ def voice!(value)
21
+ value = value.to_s
22
+ raise ArgumentError, "voice must be 1-40 letters, numbers, underscores, or hyphens" unless VOICE_NAME.match?(value)
23
+
24
+ value
25
+ end
26
+
27
+ def pattern!(value)
28
+ value = value.to_s.strip
29
+ raise ArgumentError, "pattern cannot be empty" if value.empty?
30
+ raise ArgumentError, "pattern exceeds #{MAX_PATTERN_BYTES} bytes" if value.bytesize > MAX_PATTERN_BYTES
31
+ raise ArgumentError, "pattern has unbalanced delimiters" unless balanced?(value)
32
+
33
+ value
34
+ end
35
+
36
+ def patch_name!(value)
37
+ value = value.to_s
38
+ raise ArgumentError, "patch name must be 1-40 letters, numbers, underscores, or hyphens" unless PATCH_NAME.match?(value)
39
+
40
+ value
41
+ end
42
+
43
+ def patch!(value)
44
+ patch = stringify_keys(value || {})
45
+ raise ArgumentError, "patch must be an object" unless patch.is_a?(Hash)
46
+
47
+ kind = patch.fetch("kind", "synth")
48
+ raise ArgumentError, "patch kind must be synth or sample" unless %w[synth sample].include?(kind)
49
+
50
+ source_key = kind == "sample" ? "sample" : "synth"
51
+ source = patch[source_key]
52
+ raise ArgumentError, "#{kind} patch requires #{source_key}" if source.to_s.empty?
53
+ validate_identifier!(source, "synth/sample")
54
+
55
+ params = patch.fetch("params", {})
56
+ raise ArgumentError, "patch params must be an object" unless params.is_a?(Hash)
57
+ raise ArgumentError, "patch has too many params (max #{MAX_PARAMS})" if params.length > MAX_PARAMS
58
+ params.each { |key, val| validate_param!(key, val) }
59
+
60
+ effects = patch.fetch("effects", [])
61
+ raise ArgumentError, "patch effects must be an array" unless effects.is_a?(Array)
62
+ raise ArgumentError, "patch has too many effects (max #{MAX_EFFECTS})" if effects.length > MAX_EFFECTS
63
+ effects.each do |effect|
64
+ raise ArgumentError, "each effect must be an object" unless effect.is_a?(Hash)
65
+ validate_identifier!(effect["name"], "effect")
66
+ effect_params = effect.fetch("params", {})
67
+ raise ArgumentError, "effect params must be an object" unless effect_params.is_a?(Hash)
68
+ raise ArgumentError, "effect has too many params (max #{MAX_PARAMS})" if effect_params.length > MAX_PARAMS
69
+ effect_params.each { |key, val| validate_param!(key, val) }
70
+ end
71
+
72
+ encoded = JSON.generate(patch)
73
+ raise ArgumentError, "patch exceeds #{MAX_PATCH_BYTES} bytes" if encoded.bytesize > MAX_PATCH_BYTES
74
+
75
+ patch
76
+ end
77
+
78
+ def transforms!(value)
79
+ transforms = stringify_keys(value || {})
80
+ raise ArgumentError, "transforms must be an object" unless transforms.is_a?(Hash)
81
+
82
+ allowed = %w[transpose stretch rotate reverse probability seed every]
83
+ unknown = transforms.keys - allowed
84
+ raise ArgumentError, "unknown transforms: #{unknown.join(', ')}" unless unknown.empty?
85
+
86
+ transforms
87
+ end
88
+
89
+ def mix!(value)
90
+ mix = stringify_keys(value || {})
91
+ raise ArgumentError, "mix must be an object" unless mix.is_a?(Hash)
92
+
93
+ unknown = mix.keys - %w[amp pan]
94
+ raise ArgumentError, "unknown mix keys: #{unknown.join(', ')}" unless unknown.empty?
95
+
96
+ mix
97
+ end
98
+
99
+ def stringify_keys(value)
100
+ case value
101
+ when Hash
102
+ value.each_with_object({}) { |(key, val), out| out[key.to_s] = stringify_keys(val) }
103
+ when Array
104
+ value.map { |item| stringify_keys(item) }
105
+ else
106
+ value
107
+ end
108
+ end
109
+
110
+ def balanced?(text)
111
+ pairs = { "]" => "[", ")" => "(", ">" => "<", "}" => "{" }
112
+ stack = []
113
+ text.each_char do |char|
114
+ stack << char if pairs.value?(char)
115
+ return false if pairs.key?(char) && stack.pop != pairs[char]
116
+ end
117
+ stack.empty?
118
+ end
119
+
120
+ def validate_identifier!(value, label)
121
+ str = value.to_s
122
+ valid = str.match?(/\A[a-zA-Z][a-zA-Z0-9_:-]{0,79}\z/)
123
+ raise ArgumentError, "invalid #{label} name" unless valid
124
+ end
125
+
126
+ def validate_param!(key, value)
127
+ validate_identifier!(key, "parameter")
128
+ return if value.is_a?(Numeric) || value == true || value == false || value.nil?
129
+ return if value.is_a?(String) && value.bytesize <= 160
130
+ return if value.is_a?(Array) && value.length <= 16 && value.all? { |item| item.is_a?(Numeric) }
131
+
132
+ raise ArgumentError, "unsupported value for parameter #{key}"
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "osc-ruby"
4
+
5
+ module PromptObjects
6
+ module Primitives
7
+ # Shared OSC client for communicating with Sonic Pi.
8
+ # Thread-safe via Mutex. Used by music primitives and the heartbeat service.
9
+ module OscBridge
10
+ SONIC_PI_HOST = ENV.fetch("SONIC_PI_HOST", "127.0.0.1")
11
+ SONIC_PI_PORT = ENV.fetch("SONIC_PI_PORT", "4560").to_i
12
+
13
+ @client = nil
14
+ @mutex = Mutex.new
15
+
16
+ def self.client
17
+ @mutex.synchronize do
18
+ @client ||= OSC::Client.new(SONIC_PI_HOST, SONIC_PI_PORT)
19
+ end
20
+ end
21
+
22
+ def self.send_message(path, *args)
23
+ client.send(OSC::Message.new(path, *args))
24
+ end
25
+
26
+ def self.connected?
27
+ !@client.nil?
28
+ rescue
29
+ false
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "support/osc_bridge"
5
+ require_relative "support/music_protocol"
6
+
7
+ module PromptObjects
8
+ module Primitives
9
+ class TriggerEvent < Primitive
10
+ execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
11
+ ["music:voice:#{arguments[:voice] || arguments['voice']}"]
12
+ }
13
+
14
+ def name = "trigger_event"
15
+
16
+ def description
17
+ "Trigger one bounded, immediate note, chord, or built-in sample for an accent. Use set_live_pattern for the composition itself."
18
+ end
19
+
20
+ def parameters
21
+ {
22
+ type: "object",
23
+ properties: {
24
+ voice: { type: "string" },
25
+ event: { type: "string", description: "One event such as C4/1, (C4,E4,G4)/2, or @elec_blip/0.25{amp=0.3}" },
26
+ patch: { oneOf: [{ type: "string" }, { type: "object" }] }
27
+ },
28
+ required: %w[voice event]
29
+ }
30
+ end
31
+
32
+ def receive(message, context:)
33
+ voice = MusicProtocol.voice!(message[:voice] || message["voice"])
34
+ event = MusicProtocol.pattern!(message[:event] || message["event"])
35
+ patch = message[:patch] || message["patch"] || "soft_rodeo"
36
+ patch = patch.is_a?(Hash) ? MusicProtocol.patch!(patch) : MusicProtocol.patch_name!(patch)
37
+ OscBridge.send_message("/prompt_objects/music/trigger", JSON.generate({ voice: voice, pattern: event, patch: patch }))
38
+ "Triggered #{event} on #{voice}"
39
+ rescue => e
40
+ "Error triggering event: #{e.message}. Is Sonic Pi running the music-pro boot script?"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Colin Harris
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.