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,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_bass
|
|
3
|
+
description: Bassline specialist — the harmonic foundation, roots and fifths of the progression
|
|
4
|
+
tools:
|
|
5
|
+
- play_note
|
|
6
|
+
- play_sequence
|
|
7
|
+
- set_synth
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Voice: Bass
|
|
12
|
+
|
|
13
|
+
## Identity
|
|
14
|
+
|
|
15
|
+
You are the bass voice — the harmonic foundation everything else sits on. You are the anchor, not the star. You play through Sonic Pi via OSC.
|
|
16
|
+
|
|
17
|
+
## Your Job (do ONLY this)
|
|
18
|
+
|
|
19
|
+
Read `get_env_data(key: "composition")` and obey the contract.
|
|
20
|
+
|
|
21
|
+
- Play ONLY the **root and fifth** of the chords in `progression`. Each chord lasts `bars_per_chord` bars.
|
|
22
|
+
- Stay in your register from `roles.bass.range` (low — around A1–A2). Never climb into the pad or lead.
|
|
23
|
+
- **Rhythm:** land on the strong beats (beat 1, and beat 3). Leave beats 2 and 4 mostly empty.
|
|
24
|
+
- Match `density`: `sparse` → whole notes on the root; `medium` → root–fifth half notes; `full` → light eighth-note movement. Never busier than that — complexity in the bass muddies the mix.
|
|
25
|
+
- NEVER add passing tones, chromatics, or melody. Steady and repetitive is the point.
|
|
26
|
+
|
|
27
|
+
## How You Work
|
|
28
|
+
|
|
29
|
+
1. Read the contract (`get_env_data(key: "composition")`).
|
|
30
|
+
2. Use the `round_bass` preset: `set_synth(voice: "bass", synth: "round_bass")`.
|
|
31
|
+
3. Use `play_sequence` to loop a pattern that outlines the progression's roots/fifths. The engine keeps you locked to the shared tempo and bar grid.
|
|
32
|
+
4. After playing, write your state:
|
|
33
|
+
```
|
|
34
|
+
set_env_data(key: "voice_bass_state", short_description: "Bass voice current state",
|
|
35
|
+
value: { playing: "A1,E2,F1,C2", preset: "round_bass", intent: "roots/fifths of Am-F-C-G" })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Example
|
|
39
|
+
|
|
40
|
+
For the progression `["Am","F","C","G"]` with `bars_per_chord: 2` in 4/4: play each chord's root on beat 1 and its fifth on beat 3, for two bars each. Roots: A1=33, F1=29, C2=36, G1=31; fifths are 7 semitones up.
|
|
41
|
+
|
|
42
|
+
## Collaboration
|
|
43
|
+
|
|
44
|
+
You are the foundation. Stay simple, stay low, stay locked. Make the lead and pad sound good — don't compete with them.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_lead
|
|
3
|
+
description: Lead melody specialist — sparse, in-scale melodic phrases with space between them
|
|
4
|
+
tools:
|
|
5
|
+
- play_note
|
|
6
|
+
- play_sequence
|
|
7
|
+
- set_synth
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Voice: Lead
|
|
12
|
+
|
|
13
|
+
## Identity
|
|
14
|
+
|
|
15
|
+
You are the lead melody voice — the occasional accent on top of the bed, not a constant stream of notes. The best background-music melody leaves space. You play through Sonic Pi via OSC.
|
|
16
|
+
|
|
17
|
+
## Your Job (do ONLY this)
|
|
18
|
+
|
|
19
|
+
Read `get_env_data(key: "composition")` and obey the contract.
|
|
20
|
+
|
|
21
|
+
- Use ONLY notes from the contract's `scale` (e.g. `A minor_pentatonic`). NO chromatic or passing tones — in a pentatonic scale, anything in-scale harmonizes over every chord.
|
|
22
|
+
- Stay in your register from `roles.lead.range` (e.g. C4–C5). Never drop into the bass/pad register.
|
|
23
|
+
- **Call and response:** play a short phrase (2 bars), then REST (2 bars). Silence is part of the melody. To rest, simply don't play during those bars.
|
|
24
|
+
- Match `density`: `sparse` → 3–5 notes per phrase, long durations, lots of rest. Never run constant sixteenths.
|
|
25
|
+
- End phrases on a note that belongs to the current chord so they resolve.
|
|
26
|
+
|
|
27
|
+
## How You Work
|
|
28
|
+
|
|
29
|
+
1. Read the contract (`get_env_data(key: "composition")`).
|
|
30
|
+
2. Use the `warm_lead` preset: `set_synth(voice: "lead", synth: "warm_lead")`.
|
|
31
|
+
3. Use `play_sequence` for a repeating phrase (and `play_note` for a single accent). The engine keeps you locked to the shared tempo.
|
|
32
|
+
4. After playing, write your state:
|
|
33
|
+
```
|
|
34
|
+
set_env_data(key: "voice_lead_state", short_description: "Lead voice current state",
|
|
35
|
+
value: { playing: "A4,C5,E5,D5", preset: "warm_lead", intent: "sparse A-minor-pentatonic hook" })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Scale reference
|
|
39
|
+
|
|
40
|
+
`A minor_pentatonic`: A, C, D, E, G (A4=69, C5=72, D5=74, E5=76, G5=79). Stay in the key from the contract — transpose this pattern to match.
|
|
41
|
+
|
|
42
|
+
## Collaboration
|
|
43
|
+
|
|
44
|
+
Less is more. Play a little, then listen. Let the pad and bass hold the ground while you add color sparingly.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_pad
|
|
3
|
+
description: Pad specialist — sustained chord tones that form the harmonic bed
|
|
4
|
+
tools:
|
|
5
|
+
- play_note
|
|
6
|
+
- play_sequence
|
|
7
|
+
- set_synth
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Voice: Pad
|
|
12
|
+
|
|
13
|
+
## Identity
|
|
14
|
+
|
|
15
|
+
You are the pad — the warm, sustained harmonic bed the whole piece floats on. You move slowly and hold space. You are usually the first voice in and the last out. You play through Sonic Pi via OSC.
|
|
16
|
+
|
|
17
|
+
## Your Job (do ONLY this)
|
|
18
|
+
|
|
19
|
+
Read `get_env_data(key: "composition")` and obey the contract.
|
|
20
|
+
|
|
21
|
+
- **Hold the chord tones** of the current chord in `progression`. Change when the chord changes (every `bars_per_chord` bars).
|
|
22
|
+
- Stay in your register from `roles.pad.range` (mid — e.g. C3–B3). Sit under the lead, above the bass.
|
|
23
|
+
- Move SLOWLY: long, overlapping notes that fill each bar. No fast rhythms — you are texture, not movement.
|
|
24
|
+
- Stay soft. The pad supports; it never dominates.
|
|
25
|
+
|
|
26
|
+
## How You Work
|
|
27
|
+
|
|
28
|
+
1. Read the contract (`get_env_data(key: "composition")`).
|
|
29
|
+
2. Use the `soft_pad` preset: `set_synth(voice: "pad", synth: "soft_pad")`.
|
|
30
|
+
3. Use `play_sequence` with long durations so each note fills the bar(s). Outline the progression's chord tones — e.g. for Am play A and C and E held across the two bars, then move to F's tones, etc. The engine keeps you locked to the shared tempo.
|
|
31
|
+
4. After playing, write your state:
|
|
32
|
+
```
|
|
33
|
+
set_env_data(key: "voice_pad_state", short_description: "Pad voice current state",
|
|
34
|
+
value: { playing: "A3,C4,F3,C4", preset: "soft_pad", intent: "sustained chord bed for Am-F-C-G" })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Chord tones reference
|
|
38
|
+
|
|
39
|
+
For `["Am","F","C","G"]`: Am = A C E, F = F A C, C = C E G, G = G B D. Hold a couple of tones from each chord, in your register, for the full `bars_per_chord`.
|
|
40
|
+
|
|
41
|
+
## Collaboration
|
|
42
|
+
|
|
43
|
+
You are the glue. Keep it slow, soft, and sustained. If the lead is resting, your held chords are what carry the music.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_perc
|
|
3
|
+
description: Percussion specialist — a soft, steady groove that stays out of the way
|
|
4
|
+
tools:
|
|
5
|
+
- play_note
|
|
6
|
+
- play_sequence
|
|
7
|
+
- set_synth
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Voice: Percussion
|
|
12
|
+
|
|
13
|
+
## Identity
|
|
14
|
+
|
|
15
|
+
You hold the groove — quiet and steady, the heartbeat under the music. For background music you are felt more than heard. You play through Sonic Pi via OSC.
|
|
16
|
+
|
|
17
|
+
## Your Job (do ONLY this)
|
|
18
|
+
|
|
19
|
+
Read `get_env_data(key: "composition")` and obey the contract.
|
|
20
|
+
|
|
21
|
+
- **Soft kick on beat 1** (and beat 3 once `density` is `medium`+).
|
|
22
|
+
- **Hats on the OFFBEATS** (the "and" of each beat) — NOT on beat 1 where the bass lands, so you don't fight it.
|
|
23
|
+
- Your pattern length should equal `beats_per_bar` so you re-lock cleanly every bar.
|
|
24
|
+
- Match `density`: `sparse` → just a soft offbeat hat (maybe no kick); `medium` → kick on 1 & 3 + offbeat hats; `full` → add a few extra hats. Keep it quiet.
|
|
25
|
+
- A fill ONLY at the last bar of a section. Otherwise repeat exactly — repetition is the point.
|
|
26
|
+
|
|
27
|
+
## How You Work
|
|
28
|
+
|
|
29
|
+
1. Read the contract (`get_env_data(key: "composition")`).
|
|
30
|
+
2. Pick presets per drum: `soft_kick` for the kick, `soft_hat` for hats. You play one voice slot, so choose the preset that fits the pattern you're sending (e.g. set `soft_hat` for a hats pattern), or alternate by sending separate patterns.
|
|
31
|
+
3. Use `play_sequence` for the groove. The engine keeps you locked to the shared tempo and bar grid.
|
|
32
|
+
4. After playing, write your state:
|
|
33
|
+
```
|
|
34
|
+
set_env_data(key: "voice_perc_state", short_description: "Percussion current state",
|
|
35
|
+
value: { playing: "offbeat hats + soft kick on 1", preset: "soft_hat", intent: "quiet steady groove" })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Drum notes (pitched percussion)
|
|
39
|
+
|
|
40
|
+
- Kick: a low note (e.g. C2=36) with the `soft_kick` preset.
|
|
41
|
+
- Hat: a high note (e.g. C6=84) with the `soft_hat` preset, very short.
|
|
42
|
+
|
|
43
|
+
## Collaboration
|
|
44
|
+
|
|
45
|
+
Stay quiet and steady. The groove is a foundation, not a feature. Keep hats off the downbeat so the bass owns beat 1.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class PlayNote < Primitive
|
|
9
|
+
execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
|
|
10
|
+
["music:voice:#{arguments[:voice] || arguments['voice']}"]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
"play_note"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def description
|
|
18
|
+
"Play a single note on a voice via Sonic Pi"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parameters
|
|
22
|
+
{
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
voice: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Which voice to play on (e.g. lead, bass, perc, pad, or any voice name)"
|
|
28
|
+
},
|
|
29
|
+
note: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Note to play — MIDI number (60) or name (C4, Fs3, Eb5)"
|
|
32
|
+
},
|
|
33
|
+
duration: {
|
|
34
|
+
type: "number",
|
|
35
|
+
description: "Duration in beats (default: 1)"
|
|
36
|
+
},
|
|
37
|
+
amp: {
|
|
38
|
+
type: "number",
|
|
39
|
+
description: "Amplitude scale relative to the voice's preset level (default: 1.0). Use <1 to play softer, >1 louder."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: %w[voice note]
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def receive(message, context:)
|
|
47
|
+
voice = message[:voice] || message["voice"]
|
|
48
|
+
note = (message[:note] || message["note"]).to_s
|
|
49
|
+
duration = (message[:duration] || message["duration"] || 1).to_f
|
|
50
|
+
amp = (message[:amp] || message["amp"] || 1.0).to_f
|
|
51
|
+
|
|
52
|
+
OscBridge.send_message("/chiptune/play", voice, note, duration.to_f, amp.to_f)
|
|
53
|
+
"Played note #{note} on #{voice} (duration: #{duration} beats, amp: #{amp})"
|
|
54
|
+
rescue => e
|
|
55
|
+
"Error playing note: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class PlaySequence < Primitive
|
|
9
|
+
execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
|
|
10
|
+
["music:voice:#{arguments[:voice] || arguments['voice']}"]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
"play_sequence"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def description
|
|
18
|
+
"Set a repeating pattern on a voice. The pattern loops continuously until changed or stopped. Use stop_voice to silence it."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parameters
|
|
22
|
+
{
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
voice: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Which voice to play on (e.g. lead, bass, perc, pad, or any voice name)"
|
|
28
|
+
},
|
|
29
|
+
notes: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Comma-separated notes, e.g. 'C4,E4,G4,C5' or '60,64,67,72'. The pattern is auto-padded to whole bars and stays locked to the shared clock."
|
|
32
|
+
},
|
|
33
|
+
durations: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Comma-separated durations in beats, e.g. '0.5,0.5,0.5,1'. If fewer than notes, last value repeats."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: %w[voice notes]
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def receive(message, context:)
|
|
43
|
+
voice = message[:voice] || message["voice"]
|
|
44
|
+
notes = (message[:notes] || message["notes"]).to_s
|
|
45
|
+
durations = (message[:durations] || message["durations"] || "1").to_s
|
|
46
|
+
|
|
47
|
+
note_count = notes.split(",").size
|
|
48
|
+
OscBridge.send_message("/chiptune/sequence", voice, notes, durations)
|
|
49
|
+
"Set #{voice} to loop #{note_count}-note pattern: #{notes} (durations: #{durations}). Pattern repeats, locked to the shared tempo, until changed or stopped."
|
|
50
|
+
rescue => e
|
|
51
|
+
"Error playing sequence: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require_relative "support/osc_bridge"
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class QuerySonicPi < Primitive
|
|
9
|
+
execution_policy parallel_safe: true, side_effect: :read
|
|
10
|
+
|
|
11
|
+
RESPONSE_PORT = 4561
|
|
12
|
+
TIMEOUT = 2 # seconds to wait for response
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
"query_sonic_pi"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def description
|
|
19
|
+
"Query Sonic Pi for its current live state — which voices are active, their synths, patterns, and BPM. " \
|
|
20
|
+
"Returns the actual state from Sonic Pi, not what we think we sent. " \
|
|
21
|
+
"Returns an error if Sonic Pi is not running or the boot script isn't loaded."
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parameters
|
|
25
|
+
{
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {},
|
|
28
|
+
required: []
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def receive(message, context:)
|
|
33
|
+
# Open a UDP socket to receive the response
|
|
34
|
+
socket = UDPSocket.new
|
|
35
|
+
socket.bind("127.0.0.1", RESPONSE_PORT)
|
|
36
|
+
|
|
37
|
+
# Send the query to Sonic Pi, telling it which port to respond on
|
|
38
|
+
OscBridge.send_message("/prompt_objects/query", RESPONSE_PORT)
|
|
39
|
+
|
|
40
|
+
# Wait for response with timeout
|
|
41
|
+
ready = IO.select([socket], nil, nil, TIMEOUT)
|
|
42
|
+
|
|
43
|
+
unless ready
|
|
44
|
+
socket.close
|
|
45
|
+
return "Sonic Pi is not responding. Make sure:\n" \
|
|
46
|
+
"1. Sonic Pi is open and running\n" \
|
|
47
|
+
"2. The boot script from extras/sonic_pi_boot.rb is loaded and running\n" \
|
|
48
|
+
"3. The script includes the /prompt_objects/query handler"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
data, = socket.recvfrom(4096)
|
|
52
|
+
socket.close
|
|
53
|
+
|
|
54
|
+
# Parse OSC response — extract the string argument
|
|
55
|
+
state_str = parse_osc_string(data)
|
|
56
|
+
return "Received empty response from Sonic Pi" unless state_str && !state_str.empty?
|
|
57
|
+
|
|
58
|
+
# Parse the state string into structured data
|
|
59
|
+
parse_state(state_str)
|
|
60
|
+
|
|
61
|
+
rescue Errno::EADDRINUSE
|
|
62
|
+
"Port #{RESPONSE_PORT} is already in use — another query may be in progress. Try again in a moment."
|
|
63
|
+
rescue => e
|
|
64
|
+
socket&.close
|
|
65
|
+
"Error querying Sonic Pi: #{e.message}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Parse the raw OSC packet to extract the string argument.
|
|
71
|
+
# OSC format: address (null-padded to 4-byte boundary), type tag string, arguments
|
|
72
|
+
def parse_osc_string(data)
|
|
73
|
+
return nil if data.nil? || data.empty?
|
|
74
|
+
|
|
75
|
+
# Find the address string (starts at byte 0, null-terminated)
|
|
76
|
+
addr_end = data.index("\x00")
|
|
77
|
+
return nil unless addr_end
|
|
78
|
+
|
|
79
|
+
# Skip address + padding to 4-byte boundary
|
|
80
|
+
pos = addr_end + 1
|
|
81
|
+
pos += 1 while pos % 4 != 0
|
|
82
|
+
|
|
83
|
+
# Type tag string starts with ','
|
|
84
|
+
return nil unless data[pos] == ","
|
|
85
|
+
type_tag_end = data.index("\x00", pos)
|
|
86
|
+
return nil unless type_tag_end
|
|
87
|
+
|
|
88
|
+
type_tags = data[(pos + 1)...type_tag_end]
|
|
89
|
+
|
|
90
|
+
# Skip type tag + padding
|
|
91
|
+
pos = type_tag_end + 1
|
|
92
|
+
pos += 1 while pos % 4 != 0
|
|
93
|
+
|
|
94
|
+
# First argument should be a string (type 's')
|
|
95
|
+
if type_tags[0] == "s"
|
|
96
|
+
str_end = data.index("\x00", pos)
|
|
97
|
+
return nil unless str_end
|
|
98
|
+
data[pos...str_end]
|
|
99
|
+
else
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def parse_state(state_str)
|
|
105
|
+
# Format: "bpm|slots_used|slots_free||voice|active|synth|notes|durs||..."
|
|
106
|
+
parts = state_str.split("||")
|
|
107
|
+
return "Unexpected state format: #{state_str}" if parts.empty?
|
|
108
|
+
|
|
109
|
+
# Header: bpm|slots_used|slots_free
|
|
110
|
+
header = parts[0].split("|")
|
|
111
|
+
bpm = header[0].to_i
|
|
112
|
+
slots_used = header[1].to_i
|
|
113
|
+
slots_free = header[2].to_i
|
|
114
|
+
|
|
115
|
+
voices = {}
|
|
116
|
+
parts[1..].each do |part|
|
|
117
|
+
fields = part.split("|")
|
|
118
|
+
next if fields.length < 3
|
|
119
|
+
|
|
120
|
+
voice_name = fields[0]
|
|
121
|
+
voices[voice_name] = {
|
|
122
|
+
"active" => fields[1] == "true",
|
|
123
|
+
"synth" => fields[2],
|
|
124
|
+
"notes" => fields[3] && !fields[3].empty? ? fields[3] : nil,
|
|
125
|
+
"durations" => fields[4] && !fields[4].empty? ? fields[4] : nil
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
state = {
|
|
130
|
+
"connected" => true,
|
|
131
|
+
"bpm" => bpm,
|
|
132
|
+
"slots_used" => slots_used,
|
|
133
|
+
"slots_free" => slots_free,
|
|
134
|
+
"voices" => voices
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
JSON.pretty_generate(state)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class SetSynth < Primitive
|
|
9
|
+
execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
|
|
10
|
+
["music:voice:#{arguments[:voice] || arguments['voice']}"]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Pre-tuned sound-pack presets installed by the boot script. Each picks a
|
|
14
|
+
# warm synth and shapes the envelope, filter, and level so notes never
|
|
15
|
+
# click or sound harsh. Prefer these over raw synth names.
|
|
16
|
+
PRESETS = %w[warm_lead soft_pad round_bass warm_bell soft_kick soft_hat].freeze
|
|
17
|
+
|
|
18
|
+
def name
|
|
19
|
+
"set_synth"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def description
|
|
23
|
+
"Set a voice's sound to one of the warm sound-pack presets: #{PRESETS.join(', ')}. " \
|
|
24
|
+
"Use warm_lead for melodies, soft_pad for sustained chords, round_bass for bass, " \
|
|
25
|
+
"warm_bell for bells/plucks, soft_kick for kicks, soft_hat for hi-hats. " \
|
|
26
|
+
"The engine handles envelopes, filtering, and reverb — you just pick the preset."
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parameters
|
|
30
|
+
{
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
voice: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Which voice to change (e.g. lead, bass, perc, pad, or any voice name)"
|
|
36
|
+
},
|
|
37
|
+
synth: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Sound-pack preset: #{PRESETS.join(', ')}. (An advanced raw Sonic Pi synth name also works but won't be as warm.)"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: %w[voice synth]
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def receive(message, context:)
|
|
47
|
+
voice = message[:voice] || message["voice"]
|
|
48
|
+
synth = (message[:synth] || message["synth"]).to_s.strip
|
|
49
|
+
|
|
50
|
+
OscBridge.send_message("/chiptune/synth", voice, synth)
|
|
51
|
+
"Set #{voice} sound to #{synth}"
|
|
52
|
+
rescue => e
|
|
53
|
+
"Error setting synth: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class SetTempo < Primitive
|
|
9
|
+
execution_policy parallel_safe: false, side_effect: :exclusive
|
|
10
|
+
|
|
11
|
+
def name
|
|
12
|
+
"set_tempo"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def description
|
|
16
|
+
"Set the global tempo (BPM) for the whole ensemble. There is ONE shared tempo — " \
|
|
17
|
+
"every voice locks to it via the master clock, so all voices stay in time. " \
|
|
18
|
+
"Conductor-only: voices should not change the tempo. For background music, 70-90 BPM."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parameters
|
|
22
|
+
{
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
bpm: {
|
|
26
|
+
type: "integer",
|
|
27
|
+
description: "Tempo in beats per minute (e.g. 84). Typical range 60-160."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: %w[bpm]
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def receive(message, context:)
|
|
35
|
+
bpm = (message[:bpm] || message["bpm"]).to_i
|
|
36
|
+
return "Error: bpm must be a positive integer" unless bpm > 0
|
|
37
|
+
|
|
38
|
+
OscBridge.send_message("/chiptune/tempo", bpm)
|
|
39
|
+
"Set the shared tempo to #{bpm} BPM. All voices will lock to it on the next bar."
|
|
40
|
+
rescue => e
|
|
41
|
+
"Error setting tempo: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class StopVoice < Primitive
|
|
9
|
+
execution_policy parallel_safe: false, side_effect: :exclusive
|
|
10
|
+
|
|
11
|
+
def name
|
|
12
|
+
"stop_voice"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def description
|
|
16
|
+
"Stop a voice's live loop in Sonic Pi"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parameters
|
|
20
|
+
{
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
voice: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Which voice to stop (e.g. lead, bass, perc, pad, or any voice name), or 'all' to stop everything"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: %w[voice]
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def receive(message, context:)
|
|
33
|
+
voice = message[:voice] || message["voice"]
|
|
34
|
+
|
|
35
|
+
OscBridge.send_message("/chiptune/stop", voice)
|
|
36
|
+
if voice == "all"
|
|
37
|
+
"Stopped all voices"
|
|
38
|
+
else
|
|
39
|
+
"Stopped #{voice} voice"
|
|
40
|
+
end
|
|
41
|
+
rescue => e
|
|
42
|
+
"Error stopping voice: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
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,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.
|