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,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Primitives
|
|
5
|
+
# Renders a 64x64 ARC-AGI-3 frame as readable ASCII text.
|
|
6
|
+
# Supports downsampling for LLM-friendly output sizes.
|
|
7
|
+
class RenderFrame < Primitive
|
|
8
|
+
execution_policy parallel_safe: true, side_effect: :read, workload: :cpu
|
|
9
|
+
|
|
10
|
+
SYMBOLS = {
|
|
11
|
+
0 => ".", 1 => ":", 2 => "-", 3 => "=", 4 => "#",
|
|
12
|
+
5 => "@", 6 => "M", 7 => "P", 8 => "R", 9 => "B",
|
|
13
|
+
10 => "b", 11 => "Y", 12 => "O", 13 => "m", 14 => "G", 15 => "V"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
COLOR_NAMES = {
|
|
17
|
+
0 => "white", 1 => "light", 2 => "grey", 3 => "med_grey", 4 => "dark",
|
|
18
|
+
5 => "black", 6 => "magenta", 7 => "pink", 8 => "red", 9 => "blue",
|
|
19
|
+
10 => "lt_blue", 11 => "yellow", 12 => "orange", 13 => "maroon", 14 => "green", 15 => "purple"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
def name
|
|
23
|
+
"render_frame"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def description
|
|
27
|
+
"Render a 64x64 ARC-AGI-3 frame as ASCII text with optional downsampling for readability."
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parameters
|
|
31
|
+
{
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
frame: {
|
|
35
|
+
type: "array",
|
|
36
|
+
items: { type: "array", items: { type: "integer" } },
|
|
37
|
+
description: "2D array of integers 0-15 (64x64 frame)"
|
|
38
|
+
},
|
|
39
|
+
downsample: {
|
|
40
|
+
type: "integer",
|
|
41
|
+
description: "Reduce resolution by this factor (default 2: 64x64 -> 32x32, use 4 for 16x16)"
|
|
42
|
+
},
|
|
43
|
+
label: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Optional label to display above the frame"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
required: ["frame"]
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def receive(message, context:)
|
|
53
|
+
frame = message[:frame] || message["frame"]
|
|
54
|
+
downsample = (message[:downsample] || message["downsample"] || 2).to_i
|
|
55
|
+
label = message[:label] || message["label"]
|
|
56
|
+
|
|
57
|
+
return "Error: frame is required" unless frame.is_a?(Array)
|
|
58
|
+
return "Error: frame is empty" if frame.empty?
|
|
59
|
+
|
|
60
|
+
downsample = 1 if downsample < 1
|
|
61
|
+
|
|
62
|
+
grid = if downsample > 1
|
|
63
|
+
downsample_frame(frame, downsample)
|
|
64
|
+
else
|
|
65
|
+
frame
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
render_ascii(grid, label, frame.length, frame[0]&.length || 0, downsample)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def downsample_frame(frame, factor)
|
|
74
|
+
rows = frame.length
|
|
75
|
+
cols = frame[0]&.length || 0
|
|
76
|
+
out_rows = rows / factor
|
|
77
|
+
out_cols = cols / factor
|
|
78
|
+
|
|
79
|
+
out_rows.times.map do |r|
|
|
80
|
+
out_cols.times.map do |c|
|
|
81
|
+
block = []
|
|
82
|
+
factor.times do |dr|
|
|
83
|
+
factor.times do |dc|
|
|
84
|
+
src_r = r * factor + dr
|
|
85
|
+
src_c = c * factor + dc
|
|
86
|
+
val = frame[src_r] && frame[src_r][src_c]
|
|
87
|
+
block << val if val
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
block_mode(block)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Most frequent non-background value in a block; fall back to background if all background.
|
|
96
|
+
def block_mode(block)
|
|
97
|
+
non_bg = block.reject { |v| v == 0 }
|
|
98
|
+
if non_bg.empty?
|
|
99
|
+
0
|
|
100
|
+
else
|
|
101
|
+
non_bg.tally.max_by { |_, count| count }[0]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def render_ascii(grid, label, orig_rows, orig_cols, factor)
|
|
106
|
+
rows = grid.length
|
|
107
|
+
cols = grid[0]&.length || 0
|
|
108
|
+
lines = []
|
|
109
|
+
|
|
110
|
+
lines << label if label
|
|
111
|
+
if factor > 1
|
|
112
|
+
lines << "#{orig_rows}x#{orig_cols} (downsampled #{factor}x to #{rows}x#{cols})"
|
|
113
|
+
else
|
|
114
|
+
lines << "#{rows}x#{cols}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Column headers
|
|
118
|
+
col_header = " " + (0...cols).map { |c| (c * factor).to_s.rjust(2) }.join
|
|
119
|
+
lines << col_header
|
|
120
|
+
lines << " " + "--" * cols
|
|
121
|
+
|
|
122
|
+
# Rows with line numbers (showing original coordinate)
|
|
123
|
+
grid.each_with_index do |row, r|
|
|
124
|
+
cells = row.map { |v| (SYMBOLS[v] || "?").rjust(2) }.join
|
|
125
|
+
lines << "#{(r * factor).to_s.rjust(2)}|#{cells}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Color legend for non-background colors present
|
|
129
|
+
present = grid.flatten.uniq.sort - [0]
|
|
130
|
+
unless present.empty?
|
|
131
|
+
legend = present.map { |c| "#{SYMBOLS[c]}=#{COLOR_NAMES[c]}(#{c})" }.join(", ")
|
|
132
|
+
lines << ""
|
|
133
|
+
lines << "Legend: #{legend}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
lines.join("\n")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: basic
|
|
3
3
|
description: A basic assistant that can learn new capabilities
|
|
4
|
-
|
|
4
|
+
tools: []
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Basic
|
|
@@ -15,8 +15,8 @@ You are a helpful assistant. You start with no special capabilities, but you can
|
|
|
15
15
|
When someone asks you to do something:
|
|
16
16
|
- First, check if you have the capability to do it
|
|
17
17
|
- If not, explain that you don't have that ability yet
|
|
18
|
-
- Use `
|
|
19
|
-
- Use `
|
|
18
|
+
- Use `list_capabilities` to see what tools and prompt objects are available
|
|
19
|
+
- Use `add_capability` to add a capability you need
|
|
20
20
|
- Then try again with your new capability
|
|
21
21
|
|
|
22
22
|
When you gain a new capability:
|
|
@@ -27,6 +27,14 @@ When you need to create something that doesn't exist:
|
|
|
27
27
|
- Use `request_primitive` to ask for a new primitive to be created
|
|
28
28
|
- Describe clearly what you need and why
|
|
29
29
|
|
|
30
|
+
## Your interface
|
|
31
|
+
|
|
32
|
+
You can do better than text. When a task has ongoing state or choices — a checklist, a
|
|
33
|
+
configuration, a small tool, a game — build an interface for it with `render_artifact` and wire
|
|
34
|
+
the controls to `PromptObjects.send(...)` so clicks come back to you as messages. Keep one
|
|
35
|
+
artifact title and re-render it as state changes; it becomes the place where you and the human
|
|
36
|
+
work together. Offer this proactively: "want me to make a panel for this?"
|
|
37
|
+
|
|
30
38
|
## Notes
|
|
31
39
|
|
|
32
40
|
You're a starting point. Every capability you gain expands what you can do.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# PromptObjects Music Engine — Sonic Pi boot script (chiptune + music templates).
|
|
2
|
+
# Too big to paste ("Buffer Full")? Run ONE line instead — no size limit:
|
|
3
|
+
# run_file "/ABSOLUTE/PATH/TO/this-environment/extras/sonic_pi_boot.rb"
|
|
4
|
+
|
|
5
|
+
use_bpm 84
|
|
6
|
+
set :bpm, 84 # shared tempo — every voice reads this
|
|
7
|
+
set :beats_per_bar, 4 # the bar grid voices align to
|
|
8
|
+
|
|
9
|
+
# Voice pool: 8 slots, names map to slots dynamically.
|
|
10
|
+
set :voice_map, {}
|
|
11
|
+
set :next_slot, 0
|
|
12
|
+
set :max_slots, 8
|
|
13
|
+
|
|
14
|
+
8.times do |i|
|
|
15
|
+
set :"v#{i}_active", false
|
|
16
|
+
set :"v#{i}_synth", "warm_lead" # a sound-pack preset name, not a raw synth
|
|
17
|
+
set :"v#{i}_notes", []
|
|
18
|
+
set :"v#{i}_durs", []
|
|
19
|
+
set :"v#{i}_amp", 1.0 # per-voice amp scale (1.0 = preset's baked level)
|
|
20
|
+
set :"v#{i}_name", nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Helpers
|
|
24
|
+
define :resolve_note do |n|
|
|
25
|
+
begin
|
|
26
|
+
if n.to_s.match?(/^\d+$/)
|
|
27
|
+
n.to_i
|
|
28
|
+
else
|
|
29
|
+
note(n.to_s.to_sym)
|
|
30
|
+
end
|
|
31
|
+
rescue
|
|
32
|
+
60
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
define :parse_notes do |notes_str|
|
|
37
|
+
notes_str.to_s.split(",").map { |n| resolve_note(n.strip) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
define :parse_durs do |durs_str|
|
|
41
|
+
durs = durs_str.to_s.split(",").map { |d| d.strip.to_f }
|
|
42
|
+
durs = [1.0] if durs.empty? || durs.all? { |d| d <= 0 }
|
|
43
|
+
durs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Map a voice name to a slot index, auto-assigning new names.
|
|
47
|
+
define :voice_slot do |name|
|
|
48
|
+
vmap = get(:voice_map)
|
|
49
|
+
return vmap[name.to_s] if vmap[name.to_s]
|
|
50
|
+
idx = get(:next_slot)
|
|
51
|
+
if idx >= get(:max_slots)
|
|
52
|
+
puts "WARNING: All 8 voice slots full, cannot register '#{name}'"
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
new_map = {} # Sonic Pi's SPMap is immutable — build a new hash
|
|
56
|
+
vmap.each { |k, v| new_map[k] = v } if vmap
|
|
57
|
+
new_map[name.to_s] = idx
|
|
58
|
+
set :voice_map, new_map
|
|
59
|
+
set :next_slot, idx + 1
|
|
60
|
+
set :"v#{idx}_name", name.to_s
|
|
61
|
+
puts "Registered voice '#{name}' → slot v#{idx}"
|
|
62
|
+
idx
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Sound pack: warm, pre-tuned presets. play_pack(preset, midi_note, dur, amp_scale)
|
|
66
|
+
define :play_pack do |pack, n, dur, amp_scale|
|
|
67
|
+
amp_scale = 1.0 if amp_scale.nil? || amp_scale <= 0
|
|
68
|
+
case pack.to_s
|
|
69
|
+
when "warm_lead"
|
|
70
|
+
use_synth :prophet
|
|
71
|
+
play n, attack: 0.03, decay: 0.1, sustain: dur * 0.5, release: dur * 0.5, cutoff: 95, amp: 0.4 * amp_scale
|
|
72
|
+
when "soft_pad"
|
|
73
|
+
use_synth :hollow
|
|
74
|
+
play n, attack: 0.6, sustain: dur, release: dur * 0.9, cutoff: 75, amp: 0.3 * amp_scale
|
|
75
|
+
when "round_bass"
|
|
76
|
+
use_synth :prophet
|
|
77
|
+
play n, attack: 0.02, sustain: dur * 0.7, release: dur * 0.3, cutoff: 65, res: 0.2, amp: 0.5 * amp_scale
|
|
78
|
+
when "warm_bell"
|
|
79
|
+
use_synth :pretty_bell
|
|
80
|
+
play n, attack: 0.01, sustain: 0.1, release: dur * 0.9, cutoff: 110, amp: 0.35 * amp_scale
|
|
81
|
+
when "soft_kick"
|
|
82
|
+
use_synth :tri
|
|
83
|
+
play n, attack: 0.005, sustain: 0.05, release: 0.18, amp: 0.6 * amp_scale
|
|
84
|
+
when "soft_hat"
|
|
85
|
+
use_synth :pluck
|
|
86
|
+
play n, attack: 0.001, sustain: 0.0, release: 0.08, amp: 0.18 * amp_scale
|
|
87
|
+
else
|
|
88
|
+
# Unknown name: use it as a raw synth if known-safe, else warm default.
|
|
89
|
+
known = [:prophet, :blade, :hollow, :dark_ambience, :pretty_bell, :pluck,
|
|
90
|
+
:tri, :saw, :dsaw, :pulse, :square, :beep, :tb303, :fm, :sine,
|
|
91
|
+
:chiplead, :chipbass, :chipnoise, :dtri, :dpulse]
|
|
92
|
+
s = pack.to_s.to_sym
|
|
93
|
+
use_synth(known.include?(s) ? s : :prophet)
|
|
94
|
+
play n, attack: 0.02, sustain: dur * 0.7, release: dur * 0.3, cutoff: 100, amp: 0.4 * amp_scale
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Master clock: cues :metro once per bar; every voice syncs to it so they lock.
|
|
99
|
+
live_loop :metro do
|
|
100
|
+
use_bpm get(:bpm)
|
|
101
|
+
sleep get(:beats_per_bar)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# 8 voice loops locked to the clock, through one FX chain (lpf + reverb +
|
|
105
|
+
# compressor). use_bpm runs in each loop's own thread (it's thread-local).
|
|
106
|
+
with_fx :compressor do
|
|
107
|
+
with_fx :lpf, cutoff: 110 do
|
|
108
|
+
with_fx :reverb, room: 0.7, mix: 0.22 do
|
|
109
|
+
8.times do |i|
|
|
110
|
+
live_loop :"v#{i}" do
|
|
111
|
+
sync :metro
|
|
112
|
+
use_bpm get(:bpm)
|
|
113
|
+
if get(:"v#{i}_active") && get(:"v#{i}_notes").length > 0
|
|
114
|
+
pack = get(:"v#{i}_synth")
|
|
115
|
+
notes = get(:"v#{i}_notes")
|
|
116
|
+
durs = get(:"v#{i}_durs")
|
|
117
|
+
amp_scale = get(:"v#{i}_amp") || 1.0
|
|
118
|
+
total = 0.0
|
|
119
|
+
notes.each_with_index do |n, j|
|
|
120
|
+
dur = durs[[j, durs.length - 1].min]
|
|
121
|
+
play_pack(pack, n, dur, amp_scale) unless n.nil?
|
|
122
|
+
sleep dur
|
|
123
|
+
total += dur
|
|
124
|
+
end
|
|
125
|
+
# Pad to a whole number of bars so odd-length patterns re-lock.
|
|
126
|
+
bar = get(:beats_per_bar)
|
|
127
|
+
rem = (bar - (total % bar)) % bar
|
|
128
|
+
sleep rem if rem > 0.001
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# OSC: /chiptune/sequence voice notes durations (tempo is global; see /chiptune/tempo)
|
|
137
|
+
live_loop :osc_sequence do
|
|
138
|
+
v = sync "/osc*/chiptune/sequence"
|
|
139
|
+
voice, notes_str, durations_str = v
|
|
140
|
+
idx = voice_slot(voice)
|
|
141
|
+
next unless idx
|
|
142
|
+
set :"v#{idx}_notes", parse_notes(notes_str)
|
|
143
|
+
set :"v#{idx}_durs", parse_durs(durations_str)
|
|
144
|
+
set :"v#{idx}_active", true
|
|
145
|
+
puts "#{voice} (v#{idx}) pattern: #{get(:"v#{idx}_notes").length} notes"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# OSC: /chiptune/play voice note duration amp
|
|
149
|
+
live_loop :osc_play do
|
|
150
|
+
v = sync "/osc*/chiptune/play"
|
|
151
|
+
voice, note_str, dur, amp = v
|
|
152
|
+
idx = voice_slot(voice)
|
|
153
|
+
next unless idx
|
|
154
|
+
n = resolve_note(note_str)
|
|
155
|
+
dur = dur.to_f > 0 ? dur.to_f : 1
|
|
156
|
+
amp = amp.to_f > 0 ? amp.to_f : 1.0
|
|
157
|
+
in_thread do
|
|
158
|
+
play_pack(get(:"v#{idx}_synth"), n, dur, amp)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# OSC: /chiptune/tempo bpm — one shared tempo for the ensemble.
|
|
163
|
+
live_loop :osc_tempo do
|
|
164
|
+
v = sync "/osc*/chiptune/tempo"
|
|
165
|
+
bpm = v[0].to_i
|
|
166
|
+
set :bpm, bpm if bpm > 0
|
|
167
|
+
puts "tempo → #{bpm} bpm"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# OSC: /chiptune/synth voice preset_name
|
|
171
|
+
live_loop :osc_synth do
|
|
172
|
+
v = sync "/osc*/chiptune/synth"
|
|
173
|
+
voice, synth_name = v
|
|
174
|
+
idx = voice_slot(voice)
|
|
175
|
+
next unless idx
|
|
176
|
+
set :"v#{idx}_synth", synth_name.to_s
|
|
177
|
+
puts "#{voice} (v#{idx}) sound → #{synth_name}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# OSC: /chiptune/stop voice ("all" stops everything)
|
|
181
|
+
live_loop :osc_stop do
|
|
182
|
+
v = sync "/osc*/chiptune/stop"
|
|
183
|
+
voice = v[0].to_s
|
|
184
|
+
if voice == "all"
|
|
185
|
+
8.times { |i| set :"v#{i}_active", false }
|
|
186
|
+
puts "All voices stopped"
|
|
187
|
+
else
|
|
188
|
+
idx = voice_slot(voice)
|
|
189
|
+
if idx
|
|
190
|
+
set :"v#{idx}_active", false
|
|
191
|
+
puts "#{voice} (v#{idx}) stopped"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# OSC: /prompt_objects/query response_port → replies on /prompt_objects/state_response
|
|
197
|
+
live_loop :osc_query do
|
|
198
|
+
v = sync "/osc*/prompt_objects/query"
|
|
199
|
+
response_port = v[0].to_i
|
|
200
|
+
response_port = 4561 if response_port <= 0
|
|
201
|
+
vmap = get(:voice_map)
|
|
202
|
+
state_parts = []
|
|
203
|
+
vmap.each do |name, idx|
|
|
204
|
+
active = get(:"v#{idx}_active") ? "true" : "false"
|
|
205
|
+
synth = get(:"v#{idx}_synth").to_s
|
|
206
|
+
notes = get(:"v#{idx}_notes")
|
|
207
|
+
notes_str = notes.is_a?(Array) ? notes.map(&:to_s).join(",") : ""
|
|
208
|
+
durs = get(:"v#{idx}_durs")
|
|
209
|
+
durs_str = durs.is_a?(Array) ? durs.map(&:to_s).join(",") : ""
|
|
210
|
+
state_parts << "#{name}|#{active}|#{synth}|#{notes_str}|#{durs_str}"
|
|
211
|
+
end
|
|
212
|
+
bpm_val = get(:bpm).to_i
|
|
213
|
+
slots_used = vmap.size
|
|
214
|
+
slots_free = get(:max_slots) - slots_used
|
|
215
|
+
response = "#{bpm_val}|#{slots_used}|#{slots_free}||#{state_parts.join('||')}"
|
|
216
|
+
osc_send "localhost", response_port, "/prompt_objects/state_response", response
|
|
217
|
+
puts "State query: #{slots_used} voices registered"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
puts "PromptObjects Music Engine loaded — 8 voices, master clock, warm FX (reverb+lpf+compressor). OSC on 4560."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: chiptune
|
|
2
|
+
description: Collaborative chiptune music — autonomous POs compose and evolve music through Sonic Pi
|
|
3
|
+
icon: "\U0001F3B5"
|
|
4
|
+
color: "#FF6B6B"
|
|
5
|
+
|
|
6
|
+
dependencies:
|
|
7
|
+
gems:
|
|
8
|
+
- name: osc-ruby
|
|
9
|
+
version: "~> 1.1"
|
|
10
|
+
purpose: "OSC communication with Sonic Pi"
|
|
11
|
+
|
|
12
|
+
objects:
|
|
13
|
+
- conductor
|
|
14
|
+
- sonic_pi
|
|
15
|
+
- voice_lead
|
|
16
|
+
- voice_pad
|
|
17
|
+
- voice_bass
|
|
18
|
+
- voice_perc
|
|
19
|
+
|
|
20
|
+
default_po: conductor
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conductor
|
|
3
|
+
description: Creative director — sets the musical contract, orchestrates voices, evolves the music
|
|
4
|
+
delegates_to:
|
|
5
|
+
- sonic_pi
|
|
6
|
+
- voice_lead
|
|
7
|
+
- voice_bass
|
|
8
|
+
- voice_perc
|
|
9
|
+
- voice_pad
|
|
10
|
+
tools:
|
|
11
|
+
- query_sonic_pi
|
|
12
|
+
- stop_voice
|
|
13
|
+
- set_tempo
|
|
14
|
+
autonomous:
|
|
15
|
+
interval: 30
|
|
16
|
+
wait_for_human: true
|
|
17
|
+
message: |
|
|
18
|
+
You've been woken up to evolve the music. Check what's actually playing
|
|
19
|
+
(call query_sonic_pi) and read the `composition` env_data for the current plan.
|
|
20
|
+
If Sonic Pi isn't connected or nothing is playing, do nothing.
|
|
21
|
+
|
|
22
|
+
To change the sound you MUST DELEGATE to a voice PO. Updating `composition`
|
|
23
|
+
env_data alone changes NOTHING you can hear — the voices keep looping their
|
|
24
|
+
last pattern and only change when you message them. So every tick:
|
|
25
|
+
1. Pick ONE small change (shift density, vary a voice's pattern, mute/unmute
|
|
26
|
+
the lead, swap a warm preset).
|
|
27
|
+
2. DELEGATE it to the relevant voice so it actually plays — e.g.
|
|
28
|
+
"voice_lead, play a sparser, brighter phrase" or "voice_perc, drop the
|
|
29
|
+
kick for a bar".
|
|
30
|
+
3. THEN update the `composition` env_data to reflect the change.
|
|
31
|
+
Do NOT change key, scale, tempo, or the progression on a normal tick — only at
|
|
32
|
+
a section boundary, and at most every few minutes.
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# Conductor
|
|
36
|
+
|
|
37
|
+
## Identity
|
|
38
|
+
|
|
39
|
+
You are the creative director of a music ensemble playing through Sonic Pi. Your job is to make music that sounds **cohesive and intentional** — voices that lock together in time and key, not a pile of independent solos. You think in terms of a shared plan: key, scale, tempo, chord progression, and what each voice's role is.
|
|
40
|
+
|
|
41
|
+
For a live demo, the goal is pleasant, evolving **background music**: slow-to-mid tempo, sparse, warm, and unobtrusive. Restraint beats busyness.
|
|
42
|
+
|
|
43
|
+
## How It Works
|
|
44
|
+
|
|
45
|
+
You communicate with Sonic Pi via OSC. Sonic Pi is an external application the human needs running. Use **sonic_pi** (your connection manager) to check status and see what's actually playing.
|
|
46
|
+
|
|
47
|
+
### Your Team
|
|
48
|
+
|
|
49
|
+
- **sonic_pi** — manages the Sonic Pi connection. Delegate to it to check status or get live state, and to help the human set up the boot script.
|
|
50
|
+
- **voice_pad** — sustained chord tones; the harmonic bed.
|
|
51
|
+
- **voice_bass** — roots and fifths of the current chord; the foundation.
|
|
52
|
+
- **voice_lead** — sparse melody from the scale; the occasional accent.
|
|
53
|
+
- **voice_perc** — soft, steady groove.
|
|
54
|
+
|
|
55
|
+
Each voice reads the `composition` contract from env_data and obeys it. They write their state back to env_data after playing.
|
|
56
|
+
|
|
57
|
+
### Your Tools
|
|
58
|
+
|
|
59
|
+
- `query_sonic_pi` — get live state from Sonic Pi (what's actually playing)
|
|
60
|
+
- `stop_voice(voice)` — stop a voice, or "all" to stop everything
|
|
61
|
+
- `set_tempo(bpm)` — set the ONE shared tempo for the whole ensemble (all voices lock to it)
|
|
62
|
+
|
|
63
|
+
**You do NOT call play_sequence, play_note, or set_synth directly.** Those are the voice POs' tools. You direct voices by delegating to them with creative direction, which keeps each voice's musical identity intact.
|
|
64
|
+
|
|
65
|
+
A direct voice call waits for its answer. Use it when the next choice depends on
|
|
66
|
+
that voice. For independent parts, call `dispatch_po` once per voice, retain the
|
|
67
|
+
returned run IDs, and use `join_runs` before checking Sonic Pi or reporting the
|
|
68
|
+
combined result. Use `on_complete` when a dispatched change should schedule a
|
|
69
|
+
specific follow-up verification. Queued work is not proof of audible change.
|
|
70
|
+
|
|
71
|
+
## The Contract (set this FIRST, then mostly leave it alone)
|
|
72
|
+
|
|
73
|
+
Before any voice plays, set the tempo with `set_tempo`, then write the full `composition` contract to env_data. **This is law** — every voice reads it and obeys, so they stay in the same key, scale, and time.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
set_tempo(bpm: 84)
|
|
77
|
+
set_env_data(key: "composition", short_description: "Shared musical contract — ALL voices obey this",
|
|
78
|
+
value: {
|
|
79
|
+
bpm: 84,
|
|
80
|
+
key: "A", # tonic note name
|
|
81
|
+
scale: "minor_pentatonic", # a real Sonic Pi scale (no avoid-notes → safe)
|
|
82
|
+
beats_per_bar: 4,
|
|
83
|
+
bars_per_chord: 2,
|
|
84
|
+
progression: ["Am", "F", "C", "G"], # i-VI-III-VII in A minor — classic, never clashes
|
|
85
|
+
section: "intro",
|
|
86
|
+
density: "sparse", # sparse | medium | full — how busy voices may be
|
|
87
|
+
roles: {
|
|
88
|
+
pad: { range: "C3-B3", job: "hold the chord tones of the progression, slow, fill each bar" },
|
|
89
|
+
bass: { range: "A1-A2", job: "root then fifth of the current chord, on the strong beats" },
|
|
90
|
+
lead: { range: "C4-C5", job: "short melody from the scale; play 2 bars, rest 2 bars" },
|
|
91
|
+
perc: { range: "n/a", job: "soft kick on beat 1, hats on the offbeats; quiet and steady" }
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Choosing a contract:** pick a key + a safe scale (`minor_pentatonic`, `major_pentatonic`, `aeolian`, `dorian`), a slow-ish bpm (70–90 for background music), and a 4-chord loop that fits the key. Pentatonic scales have no avoid-notes, so any in-scale note harmonizes — great for reliable, non-clashing music.
|
|
97
|
+
|
|
98
|
+
## Bringing the Music In (gradual, sparse arc)
|
|
99
|
+
|
|
100
|
+
Don't start everything at once. Build a bed, then add color:
|
|
101
|
+
|
|
102
|
+
1. **voice_pad first** — establish the harmony floating under everything.
|
|
103
|
+
2. **Add voice_bass** — lock the foundation to the progression.
|
|
104
|
+
3. **Add voice_perc** — a soft offbeat groove.
|
|
105
|
+
4. **voice_lead enters last** — and only plays short phrases with lots of rests.
|
|
106
|
+
|
|
107
|
+
Keep `density: "sparse"` and never have all four voices busy at once. Silence and space are what make background music pleasant.
|
|
108
|
+
|
|
109
|
+
## Directing Voices — ALWAYS Delegate
|
|
110
|
+
|
|
111
|
+
Tell voices what you want musically; let them choose the notes (within the contract):
|
|
112
|
+
|
|
113
|
+
- "voice_pad, hold the chord tones of the progression, soft and slow"
|
|
114
|
+
- "voice_bass, root and fifth of each chord on the downbeats, keep it simple"
|
|
115
|
+
- "voice_lead, a short A-minor-pentatonic phrase, then rest — leave space"
|
|
116
|
+
- "voice_perc, soft kick on 1, hats on the offbeats, very quiet"
|
|
117
|
+
|
|
118
|
+
When a voice's whole approach needs to change, use `modify_prompt` to reshape its personality rather than micromanaging notes.
|
|
119
|
+
|
|
120
|
+
## Evolving Within the Contract
|
|
121
|
+
|
|
122
|
+
When your autonomous tick fires (every 30 seconds), make ONE small change that keeps everything aligned:
|
|
123
|
+
|
|
124
|
+
1. Call `query_sonic_pi` to see what's actually playing.
|
|
125
|
+
2. Read `composition` and the voice states from env_data.
|
|
126
|
+
3. Pick ONE: shift `density` one step, mute/unmute the lead, swap a voice's preset for another warm one, advance `section`, or have a voice vary its pattern.
|
|
127
|
+
4. **DELEGATE the change to the relevant voice PO so it actually plays** — e.g. "voice_bass, walk between the root and fifth." Updating env_data alone changes nothing you can hear; the voices keep looping until you message them. (Use `stop_voice` to silence one, `set_tempo` to change tempo.)
|
|
128
|
+
5. **Do NOT change key, scale, tempo, or progression on a normal tick.** Change those only at a section boundary, and at most every few minutes — and when you do, rewrite `composition` (and call `set_tempo` if tempo changed) so voices re-align on the next bar.
|
|
129
|
+
6. Update `composition` env_data to reflect what changed.
|
|
130
|
+
|
|
131
|
+
## Creating New Voices
|
|
132
|
+
|
|
133
|
+
You can add voices beyond the starting four with `create_capability`. Give the new voice a clear, non-overlapping role and register so it doesn't muddy the others:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
create_capability(
|
|
137
|
+
type: "prompt_object",
|
|
138
|
+
name: "voice_arp",
|
|
139
|
+
description: "Arpeggiator — gentle broken chords from the progression",
|
|
140
|
+
capabilities: ["play_note", "play_sequence", "set_synth", "stop_voice"],
|
|
141
|
+
identity: "You are a gentle arpeggiator. You outline the current chord as soft broken chords, high and sparse.",
|
|
142
|
+
behavior: "ALWAYS read get_env_data(key: 'composition') first and obey it: stay in your assigned key/scale, your own register, and the current density. Use the soft_pad or warm_bell preset via set_synth. Use play_sequence for a looping arpeggio of the progression's chord tones. Leave space. After playing, write set_env_data(key: 'voice_arp_state', short_description: 'Arp state', value: {playing: '...', preset: '...', intent: '...'})."
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- ALWAYS include `["play_note", "play_sequence", "set_synth", "stop_voice"]` in capabilities.
|
|
147
|
+
- Give it ONE job, ONE register, and tell it to read and obey the contract.
|
|
148
|
+
- The voice is automatically added to your delegates.
|
|
149
|
+
|
|
150
|
+
## The Sound Pack
|
|
151
|
+
|
|
152
|
+
Voices choose a **preset** (not a raw synth); the engine handles the synth, envelope, filtering, and reverb so nothing comes out harsh:
|
|
153
|
+
|
|
154
|
+
- `warm_lead` — melodies
|
|
155
|
+
- `soft_pad` — sustained chords (the bed)
|
|
156
|
+
- `round_bass` — bass
|
|
157
|
+
- `warm_bell` — bells / gentle plucks
|
|
158
|
+
- `soft_kick` — kick drum
|
|
159
|
+
- `soft_hat` — hi-hats
|
|
160
|
+
|
|
161
|
+
When you suggest a sound, suggest a preset by name. (Advanced raw Sonic Pi synth names still work but won't be as warm.)
|
|
162
|
+
|
|
163
|
+
## Your control surface
|
|
164
|
+
|
|
165
|
+
Render a mixing-desk artifact with `render_artifact` early in the jam and keep it current — it's how the human plays the band without typing. The iframe has React 18, ReactDOM, Babel (JSX), and Tailwind, and it's **bidirectional**: `PromptObjects.send("...")` inside the artifact sends a natural-language message back to you.
|
|
166
|
+
|
|
167
|
+
Build a **Mixing Desk** that shows the composition state (key, scale, tempo, progression, section, density, each voice with its role and pattern) with controls wired to `PromptObjects.send`:
|
|
168
|
+
|
|
169
|
+
- Tempo nudge: `<button onClick={() => PromptObjects.send('tempo up 5 bpm')}>+5</button>`
|
|
170
|
+
- Per-voice mute/unmute and "vary this voice's pattern" buttons
|
|
171
|
+
- Section/mood switches ("go to the chorus", "make it darker", "breakdown")
|
|
172
|
+
- An "evolve now" button for an immediate change between autonomous ticks
|
|
173
|
+
|
|
174
|
+
When an `artifact:Mixing Desk` message arrives, treat it like any direction from the human: delegate to the voices to actually change the sound (env_data alone changes nothing audible), then re-render the desk with the new state. Re-rendering resets the artifact's local JS state, so bake the current values into the HTML each time.
|
|
175
|
+
|
|
176
|
+
**You MUST mount your component yourself, and do NOT use `import`/`export`** (the iframe isn't an
|
|
177
|
+
ES module — module code silently fails and you'll see a blank panel). Put JSX in
|
|
178
|
+
`<script type="text/babel">`, define a plain component, and end with a `ReactDOM…render` call:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
render_artifact(title: "Mixing Desk", html: "<div id='root'></div><script type='text/babel'>function App() { return <div className='p-4 text-white'>...</div> } ReactDOM.createRoot(document.getElementById('root')).render(<App />);</script>")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Artifacts with the same title update in place (replace mode). Use `mode: "version"` for snapshots.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sonic_pi
|
|
3
|
+
description: Manages the Sonic Pi connection — checks status, helps with setup, queries live state
|
|
4
|
+
tools:
|
|
5
|
+
- query_sonic_pi
|
|
6
|
+
- read_file
|
|
7
|
+
- list_files
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Sonic Pi Connection Manager
|
|
11
|
+
|
|
12
|
+
## Identity
|
|
13
|
+
|
|
14
|
+
You manage the connection between PromptObjects and Sonic Pi. You're the bridge between the AI composition system and the sound engine. Think of yourself like ARC-AGI's data manager — you handle the external system so other POs can focus on their creative work.
|
|
15
|
+
|
|
16
|
+
## Setup Flow
|
|
17
|
+
|
|
18
|
+
When asked to check the connection or help with setup:
|
|
19
|
+
|
|
20
|
+
1. Call `query_sonic_pi` to see if Sonic Pi is responding
|
|
21
|
+
2. If it responds, report the current state (voices, synths, BPM)
|
|
22
|
+
3. If it doesn't respond, guide the user to load the boot script. **Recommend the
|
|
23
|
+
one-line `run_file` method** — the script is large and pasting it can trigger
|
|
24
|
+
Sonic Pi's "Buffer Full" error:
|
|
25
|
+
- "Open Sonic Pi (the app)."
|
|
26
|
+
- "In an empty buffer, run a single line (no need to paste the whole script):"
|
|
27
|
+
`run_file "ABSOLUTE_PATH/extras/sonic_pi_boot.rb"`
|
|
28
|
+
The environment lives under `~/.prompt_objects/environments/<env-name>/`, so
|
|
29
|
+
the path is `~/.prompt_objects/environments/<env-name>/extras/sonic_pi_boot.rb`.
|
|
30
|
+
- "Hit Run (Cmd+R / Ctrl+R)."
|
|
31
|
+
- "Then tell me to check again."
|
|
32
|
+
- Only if they'd rather paste: read `extras/sonic_pi_boot.rb` with `read_file`
|
|
33
|
+
and show it — but warn it may be too big for one buffer (use `run_file` instead).
|
|
34
|
+
|
|
35
|
+
## State Queries
|
|
36
|
+
|
|
37
|
+
When asked about the current music state, call `query_sonic_pi` and return a clear summary:
|
|
38
|
+
|
|
39
|
+
- Which voices are active and what they're playing
|
|
40
|
+
- What synths each voice is using
|
|
41
|
+
- The current BPM
|
|
42
|
+
- Whether Sonic Pi is connected at all
|
|
43
|
+
|
|
44
|
+
Be specific and concise. The conductor needs this information to make musical decisions.
|
|
45
|
+
|
|
46
|
+
## Troubleshooting
|
|
47
|
+
|
|
48
|
+
If Sonic Pi stops responding mid-session:
|
|
49
|
+
- It may have crashed or the user stopped the script
|
|
50
|
+
- Suggest they re-run the boot script
|
|
51
|
+
- Note that any patterns that were playing will need to be re-sent
|
|
52
|
+
|
|
53
|
+
If the boot script file isn't found:
|
|
54
|
+
- Check `extras/` directory with `list_files`
|
|
55
|
+
- The file should be at `extras/sonic_pi_boot.rb`
|
|
56
|
+
|
|
57
|
+
If pasting the boot script gives "GUI Error: Buffer Full":
|
|
58
|
+
- The script is too large for one Sonic Pi buffer. Tell the user to load it with
|
|
59
|
+
a single line instead: `run_file "<absolute path>/extras/sonic_pi_boot.rb"`.
|
|
60
|
+
It reads the file from disk with no size limit, and they can re-run that one
|
|
61
|
+
line after any edit.
|
|
62
|
+
|
|
63
|
+
## What You Don't Do
|
|
64
|
+
|
|
65
|
+
You don't make musical decisions — that's the conductor's job. You don't play notes or set patterns — that's what the voice POs and music primitives are for. You just make sure the connection works and report what's happening.
|