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,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_pad
|
|
3
|
+
description: Sustained harmony specialist who creates slow, connected chord beds
|
|
4
|
+
tools:
|
|
5
|
+
- set_instrument
|
|
6
|
+
- set_voice_level
|
|
7
|
+
- play_pattern
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Pad voice
|
|
12
|
+
|
|
13
|
+
You create the harmonic bed. Always read `composition` first.
|
|
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 call `play_pattern` once with `instrument:
|
|
19
|
+
air_pad`, the chosen `level`, and the complete pattern so setup and playback are
|
|
20
|
+
one operation. On later changes, call every playback tool needed to make the
|
|
21
|
+
requested change real. Do not merely
|
|
22
|
+
draft notation, ask “shall I activate this?”, or wait for confirmation: the
|
|
23
|
+
conductor's request is authorization. A pattern written only in prose makes no
|
|
24
|
+
sound. Save state only after the playback calls return without an error, and
|
|
25
|
+
report the calls you actually completed.
|
|
26
|
+
|
|
27
|
+
Use voice name `pad`, instrument `air_pad`, and level 0.42-0.62. Write one chord
|
|
28
|
+
event for every chord in the progression, normally one chord every four beats.
|
|
29
|
+
Join simultaneous notes with `+`; do not arpeggiate a chord that should be held.
|
|
30
|
+
Keep voicings between C3 and E4, use three or four notes, and favor inversions
|
|
31
|
+
that move each note only a short distance. Use the optional fourth event field
|
|
32
|
+
as a gate slightly longer than the four-beat step so adjacent chords overlap
|
|
33
|
+
gently instead of exposing a gap.
|
|
34
|
+
|
|
35
|
+
For Cmaj7 / Am7 / Fmaj7 / G6 at one bar per chord, the structural shape is:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
C3+G3+B3+E4@4@0.58@4.5;C3+E3+G3+A3@4@0.55@4.5;C3+F3+A3+E4@4@0.54@4.5;D3+G3+B3+E4@4@0.56@4.5
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Compose your own voicings from the actual contract. Change them sparingly. You
|
|
42
|
+
may insert a full-progression rest only for a deliberate breakdown; normally
|
|
43
|
+
you are the quiet continuity underneath the other voices.
|
|
44
|
+
|
|
45
|
+
After every submitted playback change, save `voice_pad_state` with instrument,
|
|
46
|
+
level, pattern, voicing intent, and which contract you followed.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_percussion
|
|
3
|
+
description: Sample-based percussionist who adds a barely-there pulse only when useful
|
|
4
|
+
tools:
|
|
5
|
+
- set_instrument
|
|
6
|
+
- set_voice_level
|
|
7
|
+
- play_pattern
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Percussion voice
|
|
12
|
+
|
|
13
|
+
You add pulse, not spectacle. Always read `composition` first.
|
|
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 call `play_pattern` once with `instrument:
|
|
19
|
+
gentle_kit`, the chosen `level`, and the complete pattern so setup and playback
|
|
20
|
+
are one operation. On later changes, call every playback tool needed to make
|
|
21
|
+
the requested change real. Do not merely
|
|
22
|
+
draft notation, ask “shall I activate this?”, or wait for confirmation: the
|
|
23
|
+
conductor's request is authorization. A pattern written only in prose makes no
|
|
24
|
+
sound. Save state only after the playback calls return without an error, and
|
|
25
|
+
report the calls you actually completed. If the correct musical choice is
|
|
26
|
+
silence, call `stop_voice` rather than merely saying you will remain silent.
|
|
27
|
+
|
|
28
|
+
Use voice name `percussion`, instrument `gentle_kit`, and level 0.22-0.42. Your
|
|
29
|
+
tokens trigger real Sonic Pi samples: `kick`, `hat`, `snare`, and `tick`. Every
|
|
30
|
+
silent subdivision must be an explicit `R` event.
|
|
31
|
+
|
|
32
|
+
For talk-safe background music, provide a quiet four-beat pulse like:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
kick@0.5@0.45;R@0.5;hat@0.5@0.28;R@0.5;tick@0.5@0.25;R@0.5;hat@0.5@0.26;R@0.5
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use at most one kick per bar. Hats or ticks may quietly mark alternating beats
|
|
39
|
+
or offbeats; snare is optional and rare. Fills are inappropriate unless the
|
|
40
|
+
conductor explicitly calls for a transition. If keys already carry a clear
|
|
41
|
+
pulse, simplify and lower your level instead of automatically disappearing.
|
|
42
|
+
|
|
43
|
+
After every submitted playback change, save `voice_percussion_state` with level,
|
|
44
|
+
pattern, density, and why percussion is helping. Stopping yourself is a valid
|
|
45
|
+
musical choice.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voice_texture
|
|
3
|
+
description: Minimal colorist who places rare, quiet tones around the ensemble
|
|
4
|
+
tools:
|
|
5
|
+
- set_instrument
|
|
6
|
+
- set_voice_level
|
|
7
|
+
- play_pattern
|
|
8
|
+
- stop_voice
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Texture voice
|
|
12
|
+
|
|
13
|
+
You provide one glint of color, then disappear. Always read `composition` first.
|
|
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 call `play_pattern` once with the chosen
|
|
19
|
+
`instrument`, `level`, and complete pattern so setup and playback are one
|
|
20
|
+
operation. On later changes, call every playback tool needed to make the
|
|
21
|
+
requested change real. Do not merely
|
|
22
|
+
draft notation, ask “shall I activate this?”, or wait for confirmation: the
|
|
23
|
+
conductor's request is authorization. A pattern written only in prose makes no
|
|
24
|
+
sound. Save state only after the playback calls return without an error, and
|
|
25
|
+
report the calls you actually completed.
|
|
26
|
+
|
|
27
|
+
Use voice name `texture`. Choose `felt_pluck`, `sine_glow`, or—very
|
|
28
|
+
sparingly—`glass_bell`, at level 0.3-0.55. Stay around G4-E5. Build a 16- or
|
|
29
|
+
32-beat pattern with one to three attacks and long explicit rests. Use chord
|
|
30
|
+
tones or shared tones between adjacent chords so the accent does not clash.
|
|
31
|
+
|
|
32
|
+
A shape such as `G4@1@0.4;R@15` demonstrates the right amount of space. Compose
|
|
33
|
+
the actual note and placement from the contract. Never double the keys motif
|
|
34
|
+
continuously and never use rapid bells.
|
|
35
|
+
|
|
36
|
+
After every submitted playback change, save `voice_texture_state` with
|
|
37
|
+
instrument, level, pattern, and the coloristic purpose of each attack.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
module PromptObjects
|
|
6
|
+
module Primitives
|
|
7
|
+
class PlayPattern < Primitive
|
|
8
|
+
execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
|
|
9
|
+
["music:voice:#{arguments[:voice] || arguments['voice']}"]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
MAX_EVENTS = 32
|
|
13
|
+
MAX_BEATS = 64.0
|
|
14
|
+
NOTE = /\A(?:\d{1,3}|[A-Ga-g](?:s|b|#)?-?\d)\z/
|
|
15
|
+
DRUMS = %w[kick hat snare tick].freeze
|
|
16
|
+
PRESETS = %w[soft_keys air_pad warm_bass felt_pluck glass_bell sine_glow gentle_kit].freeze
|
|
17
|
+
|
|
18
|
+
def name
|
|
19
|
+
"play_pattern"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def description
|
|
23
|
+
"Activate or replace a voice's repeating pattern at a synchronized boundary. Syntax: semicolon-separated " \
|
|
24
|
+
"sound@step_beats@amp@gate_beats events; amp and gate are optional. Gate controls how long pitched sound " \
|
|
25
|
+
"rings independently of the time to the next event. Optional instrument and level make first activation " \
|
|
26
|
+
"atomic. Sounds are notes (C4), chords (C3+E3+G3), R rests, or gentle_kit tokens."
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parameters
|
|
30
|
+
{
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
voice: { type: "string", description: "Voice that owns this loop" },
|
|
34
|
+
pattern: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Events sound@step_beats@amp@gate_beats separated by semicolons; amp and gate are optional. Up to 32 events / 64 step beats."
|
|
37
|
+
},
|
|
38
|
+
instrument: { type: "string", enum: PRESETS, description: "Optional curated instrument to apply with this pattern" },
|
|
39
|
+
level: { type: "number", minimum: 0.0, maximum: 1.0, description: "Optional voice level to apply with this pattern" }
|
|
40
|
+
},
|
|
41
|
+
required: %w[voice pattern]
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def receive(message, context:)
|
|
46
|
+
voice = (message[:voice] || message["voice"]).to_s.strip
|
|
47
|
+
pattern = (message[:pattern] || message["pattern"]).to_s.strip
|
|
48
|
+
instrument = message[:instrument] || message["instrument"]
|
|
49
|
+
level = message.key?(:level) ? message[:level] : message["level"]
|
|
50
|
+
return "Error: voice cannot be empty" if voice.empty?
|
|
51
|
+
|
|
52
|
+
events, error = self.class.validate(pattern)
|
|
53
|
+
return "Error: #{error}" if error
|
|
54
|
+
if instrument && !PRESETS.include?(instrument.to_s)
|
|
55
|
+
return "Error: unknown instrument '#{instrument}'. Choose: #{PRESETS.join(', ')}"
|
|
56
|
+
end
|
|
57
|
+
if !level.nil? && (!level.is_a?(Numeric) || !level.to_f.between?(0.0, 1.0))
|
|
58
|
+
return "Error: level must be a number from 0.0 to 1.0"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
OscBridge.send_message("/music/instrument", voice, instrument.to_s) if instrument
|
|
62
|
+
OscBridge.send_message("/music/level", voice, level.to_f) unless level.nil?
|
|
63
|
+
OscBridge.send_message("/music/pattern", voice, pattern)
|
|
64
|
+
beats = events.sum { |event| event[:duration] }
|
|
65
|
+
setup = []
|
|
66
|
+
setup << instrument.to_s if instrument
|
|
67
|
+
setup << "level #{level.to_f.round(2)}" unless level.nil?
|
|
68
|
+
suffix = setup.empty? ? "" : " with #{setup.join(', ')}"
|
|
69
|
+
"Queued #{events.length}-event / #{beats.round(2)}-beat pattern for #{voice}#{suffix}. It will replace the loop at a synchronized boundary."
|
|
70
|
+
rescue => e
|
|
71
|
+
"Error setting pattern: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.validate(pattern)
|
|
75
|
+
return [nil, "pattern cannot be empty"] if pattern.empty?
|
|
76
|
+
|
|
77
|
+
raw_events = pattern.split(";", -1).map(&:strip)
|
|
78
|
+
return [nil, "pattern contains an empty event"] if raw_events.any?(&:empty?)
|
|
79
|
+
return [nil, "pattern has more than #{MAX_EVENTS} events"] if raw_events.length > MAX_EVENTS
|
|
80
|
+
|
|
81
|
+
events = raw_events.map.with_index do |raw, index|
|
|
82
|
+
parts = raw.split("@", -1).map(&:strip)
|
|
83
|
+
return [nil, "event #{index + 1} must be sound@step_beats, sound@step_beats@amp, or sound@step_beats@amp@gate_beats"] unless parts.length.between?(2, 4)
|
|
84
|
+
|
|
85
|
+
sound = parts[0]
|
|
86
|
+
return [nil, "event #{index + 1} has an invalid sound '#{sound}'"] unless valid_sound?(sound)
|
|
87
|
+
|
|
88
|
+
duration = Float(parts[1])
|
|
89
|
+
return [nil, "event #{index + 1} duration must be 0.125..16 beats"] unless duration.between?(0.125, 16.0)
|
|
90
|
+
|
|
91
|
+
amp = parts.length == 3 ? Float(parts[2]) : 0.7
|
|
92
|
+
amp = Float(parts[2]) if parts.length == 4
|
|
93
|
+
return [nil, "event #{index + 1} amplitude must be 0.0..1.0"] unless amp.between?(0.0, 1.0)
|
|
94
|
+
|
|
95
|
+
gate = parts.length == 4 ? Float(parts[3]) : duration
|
|
96
|
+
return [nil, "event #{index + 1} gate must be 0.05..16 beats"] unless gate.between?(0.05, 16.0)
|
|
97
|
+
|
|
98
|
+
{ sound: sound, duration: duration, amp: amp, gate: gate }
|
|
99
|
+
rescue ArgumentError, TypeError
|
|
100
|
+
return [nil, "event #{index + 1} step, amplitude, and gate must be numbers"]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
total = events.sum { |event| event[:duration] }
|
|
104
|
+
return [nil, "pattern is longer than #{MAX_BEATS.to_i} beats"] if total > MAX_BEATS
|
|
105
|
+
|
|
106
|
+
[events, nil]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.valid_sound?(sound)
|
|
110
|
+
return true if sound.casecmp("R").zero? || DRUMS.include?(sound.downcase)
|
|
111
|
+
|
|
112
|
+
notes = sound.split("+")
|
|
113
|
+
notes.length.between?(1, 6) && notes.all? { |candidate| NOTE.match?(candidate) }
|
|
114
|
+
end
|
|
115
|
+
private_class_method :valid_sound?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
RESPONSE_PORT = 4561
|
|
13
|
+
TIMEOUT = 2
|
|
14
|
+
|
|
15
|
+
def name
|
|
16
|
+
"query_music_engine"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def description
|
|
20
|
+
"Query the actual Sonic Pi engine state: tempo, voice instruments, levels, active flags, and patterns."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parameters
|
|
24
|
+
{ type: "object", properties: {}, required: [] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def receive(message, context:)
|
|
28
|
+
socket = UDPSocket.new
|
|
29
|
+
socket.bind("127.0.0.1", RESPONSE_PORT)
|
|
30
|
+
OscBridge.send_message("/music/query", RESPONSE_PORT)
|
|
31
|
+
ready = IO.select([socket], nil, nil, TIMEOUT)
|
|
32
|
+
return no_response(socket) unless ready
|
|
33
|
+
|
|
34
|
+
data, = socket.recvfrom(8192)
|
|
35
|
+
socket.close
|
|
36
|
+
state = parse_osc_string(data)
|
|
37
|
+
return "Received an empty response from Sonic Pi." if state.nil? || state.empty?
|
|
38
|
+
|
|
39
|
+
format_state(state)
|
|
40
|
+
rescue Errno::EADDRINUSE
|
|
41
|
+
"Port #{RESPONSE_PORT} is busy; another query may be in progress. Try again shortly."
|
|
42
|
+
rescue => e
|
|
43
|
+
socket&.close
|
|
44
|
+
"Error querying Sonic Pi: #{e.message}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def no_response(socket)
|
|
50
|
+
socket.close
|
|
51
|
+
"Sonic Pi is not responding. Open Sonic Pi and run extras/sonic_pi_boot.rb with run_file."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_osc_string(data)
|
|
55
|
+
address_end = data.index("\x00")
|
|
56
|
+
return unless address_end
|
|
57
|
+
|
|
58
|
+
position = address_end + 1
|
|
59
|
+
position += 1 while position % 4 != 0
|
|
60
|
+
return unless data[position] == ","
|
|
61
|
+
|
|
62
|
+
tag_end = data.index("\x00", position)
|
|
63
|
+
return unless tag_end && data[position + 1] == "s"
|
|
64
|
+
|
|
65
|
+
position = tag_end + 1
|
|
66
|
+
position += 1 while position % 4 != 0
|
|
67
|
+
string_end = data.index("\x00", position)
|
|
68
|
+
data[position...string_end] if string_end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def format_state(state)
|
|
72
|
+
parts = state.split("||")
|
|
73
|
+
header = parts.shift.to_s.split("|", -1)
|
|
74
|
+
voices = parts.each_with_object({}) do |part, result|
|
|
75
|
+
name, active, instrument, level, pattern = part.split("|", 5)
|
|
76
|
+
result[name] = {
|
|
77
|
+
"active" => active == "true",
|
|
78
|
+
"instrument" => instrument,
|
|
79
|
+
"level" => level.to_f,
|
|
80
|
+
"pattern" => pattern
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
JSON.pretty_generate(
|
|
85
|
+
"connected" => true,
|
|
86
|
+
"bpm" => header[0].to_i,
|
|
87
|
+
"slots_used" => header[1].to_i,
|
|
88
|
+
"slots_free" => header[2].to_i,
|
|
89
|
+
"voices" => voices
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
module PromptObjects
|
|
6
|
+
module Primitives
|
|
7
|
+
class SetInstrument < Primitive
|
|
8
|
+
execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
|
|
9
|
+
["music:voice:#{arguments[:voice] || arguments['voice']}"]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
PRESETS = %w[soft_keys air_pad warm_bass felt_pluck glass_bell sine_glow gentle_kit].freeze
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
"set_instrument"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def description
|
|
19
|
+
"Choose a curated, level-matched instrument: #{PRESETS.join(', ')}. " \
|
|
20
|
+
"gentle_kit accepts kick, hat, snare, and tick pattern tokens."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parameters
|
|
24
|
+
{
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
voice: { type: "string", description: "Stable voice name, such as keys, pad, bass, percussion, or texture" },
|
|
28
|
+
instrument: { type: "string", enum: PRESETS, description: "A curated instrument preset" }
|
|
29
|
+
},
|
|
30
|
+
required: %w[voice instrument]
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def receive(message, context:)
|
|
35
|
+
voice = (message[:voice] || message["voice"]).to_s.strip
|
|
36
|
+
instrument = (message[:instrument] || message["instrument"]).to_s.strip
|
|
37
|
+
return "Error: voice cannot be empty" if voice.empty?
|
|
38
|
+
return "Error: unknown instrument '#{instrument}'. Choose: #{PRESETS.join(', ')}" unless PRESETS.include?(instrument)
|
|
39
|
+
|
|
40
|
+
OscBridge.send_message("/music/instrument", voice, instrument)
|
|
41
|
+
"Set #{voice} to #{instrument}; the change takes effect at its next synchronized pattern boundary."
|
|
42
|
+
rescue => e
|
|
43
|
+
"Error setting instrument: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
MIN_BPM = 50
|
|
11
|
+
MAX_BPM = 120
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
"set_tempo"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def description
|
|
18
|
+
"Set the ensemble's shared tempo to 50-120 BPM. Every voice stays on the same bar clock."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parameters
|
|
22
|
+
{
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
bpm: { type: "integer", minimum: MIN_BPM, maximum: MAX_BPM,
|
|
26
|
+
description: "Shared tempo. For background music, prefer 65-82 BPM." }
|
|
27
|
+
},
|
|
28
|
+
required: ["bpm"]
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def receive(message, context:)
|
|
33
|
+
bpm = (message[:bpm] || message["bpm"]).to_i
|
|
34
|
+
return "Error: bpm must be between #{MIN_BPM} and #{MAX_BPM}" unless bpm.between?(MIN_BPM, MAX_BPM)
|
|
35
|
+
|
|
36
|
+
OscBridge.send_message("/music/tempo", bpm)
|
|
37
|
+
"Tempo will change to #{bpm} BPM on the shared clock."
|
|
38
|
+
rescue => e
|
|
39
|
+
"Error setting tempo: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
module PromptObjects
|
|
6
|
+
module Primitives
|
|
7
|
+
class SetVoiceLevel < Primitive
|
|
8
|
+
execution_policy parallel_safe: true, side_effect: :write, resource_keys: ->(arguments, _context, _capability) {
|
|
9
|
+
["music:voice:#{arguments[:voice] || arguments['voice']}"]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
"set_voice_level"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
17
|
+
"Set a voice's mix level from 0.0 (silent) to 1.0 (preset maximum). Prefer 0.35-0.75."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parameters
|
|
21
|
+
{
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
voice: { type: "string", description: "Voice to mix" },
|
|
25
|
+
level: { type: "number", minimum: 0.0, maximum: 1.0, description: "Safe scale within the preset's baked-in level" }
|
|
26
|
+
},
|
|
27
|
+
required: %w[voice level]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def receive(message, context:)
|
|
32
|
+
voice = (message[:voice] || message["voice"]).to_s.strip
|
|
33
|
+
level = Float(message[:level] || message["level"])
|
|
34
|
+
return "Error: voice cannot be empty" if voice.empty?
|
|
35
|
+
return "Error: level must be between 0.0 and 1.0" unless level.between?(0.0, 1.0)
|
|
36
|
+
|
|
37
|
+
OscBridge.send_message("/music/level", voice, level)
|
|
38
|
+
"Set #{voice} level to #{level.round(2)}."
|
|
39
|
+
rescue ArgumentError, TypeError
|
|
40
|
+
"Error: level must be a number between 0.0 and 1.0"
|
|
41
|
+
rescue => e
|
|
42
|
+
"Error setting voice level: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
module PromptObjects
|
|
6
|
+
module Primitives
|
|
7
|
+
class StopVoice < Primitive
|
|
8
|
+
execution_policy parallel_safe: false, side_effect: :exclusive
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
"stop_voice"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def description
|
|
15
|
+
"Stop one voice at its next boundary, or use voice 'all' for immediate ensemble silence."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parameters
|
|
19
|
+
{
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: { voice: { type: "string", description: "Voice name, or all" } },
|
|
22
|
+
required: ["voice"]
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def receive(message, context:)
|
|
27
|
+
voice = (message[:voice] || message["voice"]).to_s.strip
|
|
28
|
+
return "Error: voice cannot be empty" if voice.empty?
|
|
29
|
+
|
|
30
|
+
OscBridge.send_message("/music/stop", voice)
|
|
31
|
+
voice == "all" ? "Stopped all music." : "Queued stop for #{voice}."
|
|
32
|
+
rescue => e
|
|
33
|
+
"Error stopping voice: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "osc-ruby"
|
|
4
|
+
|
|
5
|
+
module PromptObjects
|
|
6
|
+
module Primitives
|
|
7
|
+
module OscBridge
|
|
8
|
+
SONIC_PI_HOST = ENV.fetch("SONIC_PI_HOST", "127.0.0.1")
|
|
9
|
+
SONIC_PI_PORT = ENV.fetch("SONIC_PI_PORT", "4560").to_i
|
|
10
|
+
|
|
11
|
+
@client = nil
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
|
|
14
|
+
def self.client
|
|
15
|
+
@mutex.synchronize do
|
|
16
|
+
@client ||= OSC::Client.new(SONIC_PI_HOST, SONIC_PI_PORT)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.send_message(path, *args)
|
|
21
|
+
client.send(OSC::Message.new(path, *args))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
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.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module OSC
|
|
2
|
+
class AddressPattern
|
|
3
|
+
def initialize(pattern)
|
|
4
|
+
@pattern = pattern
|
|
5
|
+
|
|
6
|
+
generate_regex_from_pattern
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def match?(address)
|
|
10
|
+
!!(@re.nil? || @re.match(address))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def generate_regex_from_pattern
|
|
16
|
+
case @pattern
|
|
17
|
+
when nil
|
|
18
|
+
@re = @pattern
|
|
19
|
+
when Regexp
|
|
20
|
+
@re = @pattern
|
|
21
|
+
when String
|
|
22
|
+
# i'm unsure what this does
|
|
23
|
+
# @pattern.gsub!(/[.^(|)]/, '\\1')
|
|
24
|
+
|
|
25
|
+
# handles osc single char wildcard matching
|
|
26
|
+
@pattern.gsub!(/\?/, '[^/]')
|
|
27
|
+
|
|
28
|
+
# handles ** - used in creating osc routers
|
|
29
|
+
# TODO: turns out the OSC 1.1 spec says that we shoudl use "//"
|
|
30
|
+
# this will be implemented in the next major release of this gem
|
|
31
|
+
#@pattern.gsub!(/\*\*/, '.*' )
|
|
32
|
+
|
|
33
|
+
# handles osc * - 0 or more matching
|
|
34
|
+
@pattern.gsub!(/\*[^\*]/, '[^/]*')
|
|
35
|
+
|
|
36
|
+
# handles [!] matching
|
|
37
|
+
@pattern.gsub!(/\[!/, '[^')
|
|
38
|
+
|
|
39
|
+
# handles {} matching
|
|
40
|
+
@pattern.gsub!(/\{/, '(')
|
|
41
|
+
@pattern.gsub!(/,/, '|')
|
|
42
|
+
@pattern.gsub!(/\}/, ')')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# keeps from matching before the begining of the pattern
|
|
46
|
+
@pattern.gsub!(/\A/, '\A')
|
|
47
|
+
|
|
48
|
+
# keeps from matching beyond the end,
|
|
49
|
+
# eg. pattern /hi does not match /hidden
|
|
50
|
+
@pattern.gsub!(/\z/, '\z')
|
|
51
|
+
|
|
52
|
+
@re = Regexp.new(@pattern)
|
|
53
|
+
else
|
|
54
|
+
raise(ArgumentError, 'invalid pattern')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'ipaddr'
|
|
2
|
+
|
|
3
|
+
module OSC
|
|
4
|
+
class BroadcastClient
|
|
5
|
+
|
|
6
|
+
BROADCAST_ADDRESS = '<broadcast>'
|
|
7
|
+
|
|
8
|
+
attr_reader :port
|
|
9
|
+
|
|
10
|
+
def initialize(port, local_ip = nil)
|
|
11
|
+
@port = port
|
|
12
|
+
@so = UDPSocket.new
|
|
13
|
+
@so.setsockopt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true
|
|
14
|
+
if local_ip
|
|
15
|
+
@so.setsockopt Socket::IPPROTO_IP, Socket::IP_MULTICAST_IF, IPAddr.new(local_ip).hton
|
|
16
|
+
@so.bind(local_ip, 0)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def send(mesg)
|
|
21
|
+
@so.send(mesg.encode, 0, BROADCAST_ADDRESS, @port)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|