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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +126 -0
- data/README.md +93 -22
- data/exe/poop +5 -0
- data/exe/prompt_objects +1 -1108
- data/exe/prompt_objects_mcp +5 -3
- data/lib/prompt_objects/capability.rb +33 -0
- data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
- data/lib/prompt_objects/cli/env_command.rb +435 -0
- data/lib/prompt_objects/cli/events_command.rb +145 -0
- data/lib/prompt_objects/cli/explore_command.rb +155 -0
- data/lib/prompt_objects/cli/helpers.rb +91 -0
- data/lib/prompt_objects/cli/message_command.rb +216 -0
- data/lib/prompt_objects/cli/repl_command.rb +366 -0
- data/lib/prompt_objects/cli/serve_command.rb +162 -0
- data/lib/prompt_objects/cli.rb +64 -422
- data/lib/prompt_objects/connectors/base.rb +16 -29
- data/lib/prompt_objects/connectors/mcp.rb +82 -98
- data/lib/prompt_objects/domain/records.rb +45 -0
- data/lib/prompt_objects/environment/exporter.rb +5 -4
- data/lib/prompt_objects/environment/git.rb +21 -1
- data/lib/prompt_objects/environment/importer.rb +5 -4
- data/lib/prompt_objects/environment/manager.rb +44 -6
- data/lib/prompt_objects/environment/manifest.rb +26 -4
- data/lib/prompt_objects/environment.rb +1337 -20
- data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
- data/lib/prompt_objects/execution/context.rb +137 -0
- data/lib/prompt_objects/execution/contracts.rb +59 -0
- data/lib/prompt_objects/execution/permit_pool.rb +49 -0
- data/lib/prompt_objects/execution/policy.rb +47 -0
- data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
- data/lib/prompt_objects/execution/runner.rb +498 -0
- data/lib/prompt_objects/execution/scheduler.rb +325 -0
- data/lib/prompt_objects/llm/factory.rb +4 -4
- data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
- data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
- data/lib/prompt_objects/llm/pricing.rb +9 -2
- data/lib/prompt_objects/llm/response.rb +9 -4
- data/lib/prompt_objects/loader.rb +28 -1
- data/lib/prompt_objects/mcp/server.rb +6 -3
- data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
- data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
- data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
- data/lib/prompt_objects/operations/diagnostics.rb +115 -0
- data/lib/prompt_objects/operations/retention.rb +75 -0
- data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
- data/lib/prompt_objects/primitives/http_get.rb +2 -0
- data/lib/prompt_objects/primitives/list_files.rb +2 -0
- data/lib/prompt_objects/primitives/read_file.rb +2 -0
- data/lib/prompt_objects/primitives/think.rb +36 -0
- data/lib/prompt_objects/primitives/write_file.rb +9 -0
- data/lib/prompt_objects/prompt_object.rb +171 -519
- data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
- data/lib/prompt_objects/repositories/base_repository.rb +37 -0
- data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
- data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
- data/lib/prompt_objects/repositories/event_repository.rb +105 -0
- data/lib/prompt_objects/repositories/message_repository.rb +73 -0
- data/lib/prompt_objects/repositories/run_repository.rb +254 -0
- data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
- data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
- data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
- data/lib/prompt_objects/server/api/routes.rb +549 -53
- data/lib/prompt_objects/server/app.rb +133 -14
- data/lib/prompt_objects/server/file_watcher.rb +9 -0
- data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
- data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
- data/lib/prompt_objects/server/public/index.html +2 -2
- data/lib/prompt_objects/server/websocket_handler.rb +202 -227
- data/lib/prompt_objects/server.rb +170 -1
- data/lib/prompt_objects/service.rb +129 -0
- data/lib/prompt_objects/session/store.rb +600 -13
- data/lib/prompt_objects/universal/add_capability.rb +1 -1
- data/lib/prompt_objects/universal/create_capability.rb +4 -2
- data/lib/prompt_objects/universal/delete_env_data.rb +23 -15
- data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
- data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
- data/lib/prompt_objects/universal/get_env_data.rb +8 -14
- data/lib/prompt_objects/universal/get_run_status.rb +30 -0
- data/lib/prompt_objects/universal/join_runs.rb +35 -0
- data/lib/prompt_objects/universal/list_env_data.rb +10 -14
- data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
- data/lib/prompt_objects/universal/render_artifact.rb +91 -0
- data/lib/prompt_objects/universal/request_primitive.rb +1 -1
- data/lib/prompt_objects/universal/{store_env_data.rb → set_env_data.rb} +34 -20
- data/lib/prompt_objects/version.rb +5 -0
- data/lib/prompt_objects.rb +33 -8
- data/objects/coordinator.md +8 -4
- data/templates/arc-agi-1/objects/data_manager.md +2 -2
- data/templates/arc-agi-1/objects/observer.md +2 -2
- data/templates/arc-agi-1/objects/solver.md +6 -5
- data/templates/arc-agi-1/objects/verifier.md +2 -2
- data/templates/arc-agi-3/manifest.yml +19 -0
- data/templates/arc-agi-3/objects/analyst.md +79 -0
- data/templates/arc-agi-3/objects/player.md +76 -0
- data/templates/arc-agi-3/objects/strategist.md +97 -0
- data/templates/arc-agi-3/primitives/action_log.rb +133 -0
- data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
- data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
- data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
- data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
- data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
- data/templates/basic/objects/basic.md +11 -3
- data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
- data/templates/chiptune/manifest.yml +20 -0
- data/templates/chiptune/objects/conductor.md +184 -0
- data/templates/chiptune/objects/sonic_pi.md +65 -0
- data/templates/chiptune/objects/voice_bass.md +44 -0
- data/templates/chiptune/objects/voice_lead.md +44 -0
- data/templates/chiptune/objects/voice_pad.md +43 -0
- data/templates/chiptune/objects/voice_perc.md +45 -0
- data/templates/chiptune/primitives/play_note.rb +59 -0
- data/templates/chiptune/primitives/play_sequence.rb +55 -0
- data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
- data/templates/chiptune/primitives/set_synth.rb +57 -0
- data/templates/chiptune/primitives/set_tempo.rb +45 -0
- data/templates/chiptune/primitives/stop_voice.rb +46 -0
- data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/developer/objects/code_reviewer.md +1 -1
- data/templates/developer/objects/coordinator.md +16 -3
- data/templates/developer/objects/debugger.md +3 -3
- data/templates/empty/objects/assistant.md +10 -5
- data/templates/minimal/objects/assistant.md +10 -5
- data/templates/music/extras/sonic_pi_boot.rb +220 -0
- data/templates/music/manifest.yml +16 -0
- data/templates/music/objects/conductor.md +154 -0
- data/templates/music/objects/sonic_pi.md +65 -0
- data/templates/music/primitives/play_note.rb +55 -0
- data/templates/music/primitives/play_sequence.rb +51 -0
- data/templates/music/primitives/query_sonic_pi.rb +139 -0
- data/templates/music/primitives/set_synth.rb +53 -0
- data/templates/music/primitives/set_tempo.rb +43 -0
- data/templates/music/primitives/stop_voice.rb +44 -0
- data/templates/music/primitives/support/osc_bridge.rb +33 -0
- data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/music-beginner/README.md +100 -0
- data/templates/music-beginner/extras/README.md +27 -0
- data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
- data/templates/music-beginner/manifest.yml +25 -0
- data/templates/music-beginner/objects/conductor.md +222 -0
- data/templates/music-beginner/objects/engine_setup.md +54 -0
- data/templates/music-beginner/objects/voice_bass.md +43 -0
- data/templates/music-beginner/objects/voice_keys.md +48 -0
- data/templates/music-beginner/objects/voice_pad.md +46 -0
- data/templates/music-beginner/objects/voice_percussion.md +45 -0
- data/templates/music-beginner/objects/voice_texture.md +37 -0
- data/templates/music-beginner/primitives/play_pattern.rb +118 -0
- data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
- data/templates/music-beginner/primitives/set_instrument.rb +47 -0
- data/templates/music-beginner/primitives/set_tempo.rb +43 -0
- data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
- data/templates/music-beginner/primitives/stop_voice.rb +37 -0
- data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/music-pro/extras/AUDITION.md +100 -0
- data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
- data/templates/music-pro/manifest.yml +19 -0
- data/templates/music-pro/objects/conductor.md +126 -0
- data/templates/music-pro/objects/sonic_pi.md +35 -0
- data/templates/music-pro/objects/voice_harmony.md +48 -0
- data/templates/music-pro/objects/voice_rhythm.md +43 -0
- data/templates/music-pro/objects/voice_texture.md +42 -0
- data/templates/music-pro/primitives/define_patch.rb +46 -0
- data/templates/music-pro/primitives/query_music_engine.rb +69 -0
- data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
- data/templates/music-pro/primitives/set_tempo.rb +31 -0
- data/templates/music-pro/primitives/stop_voice.rb +31 -0
- data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
- data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
- data/templates/music-pro/primitives/trigger_event.rb +44 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/writer/objects/coordinator.md +14 -3
- data/templates/writer/objects/editor.md +1 -1
- data/templates/writer/objects/researcher.md +2 -2
- data/tools/thread-explorer.html +1 -1
- metadata +172 -65
- data/CLAUDE.md +0 -177
- data/Gemfile +0 -10
- data/Rakefile +0 -27
- data/frontend/.gitignore +0 -3
- data/frontend/index.html +0 -17
- data/frontend/package-lock.json +0 -4540
- data/frontend/package.json +0 -36
- data/frontend/postcss.config.js +0 -6
- data/frontend/src/App.tsx +0 -104
- data/frontend/src/canvas/CanvasView.tsx +0 -113
- data/frontend/src/canvas/ForceLayout.ts +0 -115
- data/frontend/src/canvas/SceneManager.ts +0 -587
- data/frontend/src/canvas/canvasStore.ts +0 -47
- data/frontend/src/canvas/constants.ts +0 -95
- data/frontend/src/canvas/controls/CameraControls.ts +0 -98
- data/frontend/src/canvas/edges/MessageArc.ts +0 -149
- data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
- data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
- data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
- data/frontend/src/canvas/nodes/PONode.ts +0 -249
- data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
- data/frontend/src/canvas/types.ts +0 -24
- data/frontend/src/components/ContextMenu.tsx +0 -68
- data/frontend/src/components/EnvDataPane.tsx +0 -69
- data/frontend/src/components/Inspector.tsx +0 -263
- data/frontend/src/components/MarkdownMessage.tsx +0 -155
- data/frontend/src/components/MethodList.tsx +0 -90
- data/frontend/src/components/ModelSelector.tsx +0 -115
- data/frontend/src/components/NotificationPanel.tsx +0 -130
- data/frontend/src/components/ObjectList.tsx +0 -78
- data/frontend/src/components/PaneSlot.tsx +0 -30
- data/frontend/src/components/SourcePane.tsx +0 -202
- data/frontend/src/components/SystemBar.tsx +0 -74
- data/frontend/src/components/Transcript.tsx +0 -76
- data/frontend/src/components/UsagePanel.tsx +0 -105
- data/frontend/src/components/Workspace.tsx +0 -260
- data/frontend/src/components/index.ts +0 -12
- data/frontend/src/hooks/useResize.ts +0 -55
- data/frontend/src/hooks/useWebSocket.ts +0 -559
- data/frontend/src/index.css +0 -102
- data/frontend/src/main.tsx +0 -10
- data/frontend/src/store/index.ts +0 -311
- data/frontend/src/types/index.ts +0 -176
- data/frontend/tailwind.config.js +0 -44
- data/frontend/tsconfig.json +0 -30
- data/frontend/vite.config.ts +0 -29
- data/lib/prompt_objects/server/public/assets/index-DEPawnfZ.js +0 -4345
- data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +0 -1
- data/lib/prompt_objects/universal/add_primitive.rb +0 -118
- data/lib/prompt_objects/universal/ask_human.rb +0 -109
- data/lib/prompt_objects/universal/create_primitive.rb +0 -174
- data/lib/prompt_objects/universal/list_primitives.rb +0 -145
- data/lib/prompt_objects/universal/think.rb +0 -41
- data/lib/prompt_objects/universal/update_env_data.rb +0 -88
- data/prompt_objects.gemspec +0 -50
- data/templates/basic/.gitignore +0 -2
- data/templates/developer/.gitignore +0 -5
- data/templates/empty/.gitignore +0 -5
- data/templates/empty/objects/.gitkeep +0 -0
- data/templates/minimal/.gitignore +0 -5
- 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.
|