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,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require_relative "support/osc_bridge"
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class QuerySonicPi < Primitive
|
|
9
|
+
RESPONSE_PORT = 4561
|
|
10
|
+
TIMEOUT = 2 # seconds to wait for response
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
"query_sonic_pi"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
17
|
+
"Query Sonic Pi for its current live state — which voices are active, their synths, patterns, and BPM. " \
|
|
18
|
+
"Returns the actual state from Sonic Pi, not what we think we sent. " \
|
|
19
|
+
"Returns an error if Sonic Pi is not running or the boot script isn't loaded."
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def parameters
|
|
23
|
+
{
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {},
|
|
26
|
+
required: []
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def receive(message, context:)
|
|
31
|
+
# Open a UDP socket to receive the response
|
|
32
|
+
socket = UDPSocket.new
|
|
33
|
+
socket.bind("127.0.0.1", RESPONSE_PORT)
|
|
34
|
+
|
|
35
|
+
# Send the query to Sonic Pi, telling it which port to respond on
|
|
36
|
+
OscBridge.send_message("/prompt_objects/query", RESPONSE_PORT)
|
|
37
|
+
|
|
38
|
+
# Wait for response with timeout
|
|
39
|
+
ready = IO.select([socket], nil, nil, TIMEOUT)
|
|
40
|
+
|
|
41
|
+
unless ready
|
|
42
|
+
socket.close
|
|
43
|
+
return "Sonic Pi is not responding. Make sure:\n" \
|
|
44
|
+
"1. Sonic Pi is open and running\n" \
|
|
45
|
+
"2. The boot script from extras/sonic_pi_boot.rb is loaded and running\n" \
|
|
46
|
+
"3. The script includes the /prompt_objects/query handler"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
data, = socket.recvfrom(4096)
|
|
50
|
+
socket.close
|
|
51
|
+
|
|
52
|
+
# Parse OSC response — extract the string argument
|
|
53
|
+
state_str = parse_osc_string(data)
|
|
54
|
+
return "Received empty response from Sonic Pi" unless state_str && !state_str.empty?
|
|
55
|
+
|
|
56
|
+
# Parse the state string into structured data
|
|
57
|
+
parse_state(state_str)
|
|
58
|
+
|
|
59
|
+
rescue Errno::EADDRINUSE
|
|
60
|
+
"Port #{RESPONSE_PORT} is already in use — another query may be in progress. Try again in a moment."
|
|
61
|
+
rescue => e
|
|
62
|
+
socket&.close
|
|
63
|
+
"Error querying Sonic Pi: #{e.message}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Parse the raw OSC packet to extract the string argument.
|
|
69
|
+
# OSC format: address (null-padded to 4-byte boundary), type tag string, arguments
|
|
70
|
+
def parse_osc_string(data)
|
|
71
|
+
return nil if data.nil? || data.empty?
|
|
72
|
+
|
|
73
|
+
# Find the address string (starts at byte 0, null-terminated)
|
|
74
|
+
addr_end = data.index("\x00")
|
|
75
|
+
return nil unless addr_end
|
|
76
|
+
|
|
77
|
+
# Skip address + padding to 4-byte boundary
|
|
78
|
+
pos = addr_end + 1
|
|
79
|
+
pos += 1 while pos % 4 != 0
|
|
80
|
+
|
|
81
|
+
# Type tag string starts with ','
|
|
82
|
+
return nil unless data[pos] == ","
|
|
83
|
+
type_tag_end = data.index("\x00", pos)
|
|
84
|
+
return nil unless type_tag_end
|
|
85
|
+
|
|
86
|
+
type_tags = data[(pos + 1)...type_tag_end]
|
|
87
|
+
|
|
88
|
+
# Skip type tag + padding
|
|
89
|
+
pos = type_tag_end + 1
|
|
90
|
+
pos += 1 while pos % 4 != 0
|
|
91
|
+
|
|
92
|
+
# First argument should be a string (type 's')
|
|
93
|
+
if type_tags[0] == "s"
|
|
94
|
+
str_end = data.index("\x00", pos)
|
|
95
|
+
return nil unless str_end
|
|
96
|
+
data[pos...str_end]
|
|
97
|
+
else
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_state(state_str)
|
|
103
|
+
# Format: "bpm|slots_used|slots_free||voice|active|synth|notes|durs||..."
|
|
104
|
+
parts = state_str.split("||")
|
|
105
|
+
return "Unexpected state format: #{state_str}" if parts.empty?
|
|
106
|
+
|
|
107
|
+
# Header: bpm|slots_used|slots_free
|
|
108
|
+
header = parts[0].split("|")
|
|
109
|
+
bpm = header[0].to_i
|
|
110
|
+
slots_used = header[1].to_i
|
|
111
|
+
slots_free = header[2].to_i
|
|
112
|
+
|
|
113
|
+
voices = {}
|
|
114
|
+
parts[1..].each do |part|
|
|
115
|
+
fields = part.split("|")
|
|
116
|
+
next if fields.length < 3
|
|
117
|
+
|
|
118
|
+
voice_name = fields[0]
|
|
119
|
+
voices[voice_name] = {
|
|
120
|
+
"active" => fields[1] == "true",
|
|
121
|
+
"synth" => fields[2],
|
|
122
|
+
"notes" => fields[3] && !fields[3].empty? ? fields[3] : nil,
|
|
123
|
+
"durations" => fields[4] && !fields[4].empty? ? fields[4] : nil
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
state = {
|
|
128
|
+
"connected" => true,
|
|
129
|
+
"bpm" => bpm,
|
|
130
|
+
"slots_used" => slots_used,
|
|
131
|
+
"slots_free" => slots_free,
|
|
132
|
+
"voices" => voices
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
JSON.pretty_generate(state)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class SetSynth < Primitive
|
|
9
|
+
# Pre-tuned sound-pack presets installed by the boot script. Each picks a
|
|
10
|
+
# warm synth and shapes the envelope, filter, and level so notes never
|
|
11
|
+
# click or sound harsh. Prefer these over raw synth names.
|
|
12
|
+
PRESETS = %w[warm_lead soft_pad round_bass warm_bell soft_kick soft_hat].freeze
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
"set_synth"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def description
|
|
19
|
+
"Set a voice's sound to one of the warm sound-pack presets: #{PRESETS.join(', ')}. " \
|
|
20
|
+
"Use warm_lead for melodies, soft_pad for sustained chords, round_bass for bass, " \
|
|
21
|
+
"warm_bell for bells/plucks, soft_kick for kicks, soft_hat for hi-hats. " \
|
|
22
|
+
"The engine handles envelopes, filtering, and reverb — you just pick the preset."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parameters
|
|
26
|
+
{
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
voice: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Which voice to change (e.g. lead, bass, perc, pad, or any voice name)"
|
|
32
|
+
},
|
|
33
|
+
synth: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Sound-pack preset: #{PRESETS.join(', ')}. (An advanced raw Sonic Pi synth name also works but won't be as warm.)"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: %w[voice synth]
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def receive(message, context:)
|
|
43
|
+
voice = message[:voice] || message["voice"]
|
|
44
|
+
synth = (message[:synth] || message["synth"]).to_s.strip
|
|
45
|
+
|
|
46
|
+
OscBridge.send_message("/chiptune/synth", voice, synth)
|
|
47
|
+
"Set #{voice} sound to #{synth}"
|
|
48
|
+
rescue => e
|
|
49
|
+
"Error setting synth: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class SetTempo < Primitive
|
|
9
|
+
def name
|
|
10
|
+
"set_tempo"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"Set the global tempo (BPM) for the whole ensemble. There is ONE shared tempo — " \
|
|
15
|
+
"every voice locks to it via the master clock, so all voices stay in time. " \
|
|
16
|
+
"Conductor-only: voices should not change the tempo. For background music, 70-90 BPM."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parameters
|
|
20
|
+
{
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
bpm: {
|
|
24
|
+
type: "integer",
|
|
25
|
+
description: "Tempo in beats per minute (e.g. 84). Typical range 60-160."
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: %w[bpm]
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def receive(message, context:)
|
|
33
|
+
bpm = (message[:bpm] || message["bpm"]).to_i
|
|
34
|
+
return "Error: bpm must be a positive integer" unless bpm > 0
|
|
35
|
+
|
|
36
|
+
OscBridge.send_message("/chiptune/tempo", bpm)
|
|
37
|
+
"Set the shared tempo to #{bpm} BPM. All voices will lock to it on the next bar."
|
|
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,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/osc_bridge"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
class StopVoice < Primitive
|
|
9
|
+
def name
|
|
10
|
+
"stop_voice"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"Stop a voice's live loop in Sonic Pi"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parameters
|
|
18
|
+
{
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
voice: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Which voice to stop (e.g. lead, bass, perc, pad, or any voice name), or 'all' to stop everything"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: %w[voice]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def receive(message, context:)
|
|
31
|
+
voice = message[:voice] || message["voice"]
|
|
32
|
+
|
|
33
|
+
OscBridge.send_message("/chiptune/stop", voice)
|
|
34
|
+
if voice == "all"
|
|
35
|
+
"Stopped all voices"
|
|
36
|
+
else
|
|
37
|
+
"Stopped #{voice} voice"
|
|
38
|
+
end
|
|
39
|
+
rescue => e
|
|
40
|
+
"Error stopping voice: #{e.message}. Is Sonic Pi running with the boot script loaded?"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "osc-ruby"
|
|
4
|
+
|
|
5
|
+
module PromptObjects
|
|
6
|
+
module Primitives
|
|
7
|
+
# Shared OSC client for communicating with Sonic Pi.
|
|
8
|
+
# Thread-safe via Mutex. Used by music primitives and the heartbeat service.
|
|
9
|
+
module OscBridge
|
|
10
|
+
SONIC_PI_HOST = ENV.fetch("SONIC_PI_HOST", "127.0.0.1")
|
|
11
|
+
SONIC_PI_PORT = ENV.fetch("SONIC_PI_PORT", "4560").to_i
|
|
12
|
+
|
|
13
|
+
@client = nil
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
|
|
16
|
+
def self.client
|
|
17
|
+
@mutex.synchronize do
|
|
18
|
+
@client ||= OSC::Client.new(SONIC_PI_HOST, SONIC_PI_PORT)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.send_message(path, *args)
|
|
23
|
+
client.send(OSC::Message.new(path, *args))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.connected?
|
|
27
|
+
!@client.nil?
|
|
28
|
+
rescue
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2009 Colin Harris
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
@@ -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
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module OSC
|
|
2
|
+
class Bundle
|
|
3
|
+
attr_accessor :timetag
|
|
4
|
+
|
|
5
|
+
def initialize(timetag=nil, *args)
|
|
6
|
+
@timetag = timetag
|
|
7
|
+
@args = args
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def encode()
|
|
11
|
+
s = OSCString.new('#bundle').encode
|
|
12
|
+
s << encode_timetag(@timetag)
|
|
13
|
+
s << @args.collect do |x|
|
|
14
|
+
x2 = x.encode; [x2.size].pack('N') + x2
|
|
15
|
+
end.join
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_a
|
|
19
|
+
@args.collect{|x| x.to_a}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def encode_timetag(t)
|
|
25
|
+
case t
|
|
26
|
+
when nil # immediately
|
|
27
|
+
t1 = 0
|
|
28
|
+
t2 = 1
|
|
29
|
+
when Numeric
|
|
30
|
+
t1, t2 = construct_timetag(t)
|
|
31
|
+
when Time
|
|
32
|
+
t1, t2 = construct_timetag(t.to_ntp)
|
|
33
|
+
else
|
|
34
|
+
raise(ArgumentError, 'invalid time')
|
|
35
|
+
end
|
|
36
|
+
[t1, t2].pack('N2')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def construct_timetag(time)
|
|
40
|
+
t1, fr = time.divmod(1)
|
|
41
|
+
t2 = (fr * (2**32)).to_i
|
|
42
|
+
|
|
43
|
+
[t1, t2]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'eventmachine'
|
|
2
|
+
|
|
3
|
+
module OSC
|
|
4
|
+
Channel = EM::Channel.new
|
|
5
|
+
|
|
6
|
+
class Connection < EventMachine::Connection
|
|
7
|
+
def receive_data(data)
|
|
8
|
+
ip_info = get_peername[2,6].unpack("nC4")
|
|
9
|
+
Channel << OSC::OSCPacket.messages_from_network(data, ip_info)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class EMServer
|
|
14
|
+
|
|
15
|
+
def initialize(port = 3333)
|
|
16
|
+
@port = port
|
|
17
|
+
setup_dispatcher
|
|
18
|
+
@tuples = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
EM.error_handler{ |e|
|
|
23
|
+
Thread.main.raise e
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
EM.run do
|
|
27
|
+
EM::open_datagram_socket "0.0.0.0", @port, Connection
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_method(address_pattern, &proc)
|
|
32
|
+
matcher = AddressPattern.new(address_pattern)
|
|
33
|
+
|
|
34
|
+
@tuples << [matcher, proc]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
def setup_dispatcher
|
|
39
|
+
Channel.subscribe do |messages|
|
|
40
|
+
messages.each do |message|
|
|
41
|
+
diff = (message.time || 0) - Time.now.to_ntp
|
|
42
|
+
|
|
43
|
+
if diff <= 0
|
|
44
|
+
sendmesg(message)
|
|
45
|
+
else
|
|
46
|
+
EM.defer do
|
|
47
|
+
sleep(diff)
|
|
48
|
+
sendmesg(message)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def sendmesg(mesg)
|
|
56
|
+
@tuples.each do |matcher, obj|
|
|
57
|
+
if matcher.match?(mesg.address)
|
|
58
|
+
obj.call(mesg)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module OSC
|
|
2
|
+
class Message
|
|
3
|
+
attr_accessor :address
|
|
4
|
+
attr_accessor :time
|
|
5
|
+
attr_accessor :ip_address
|
|
6
|
+
attr_accessor :ip_port
|
|
7
|
+
|
|
8
|
+
def self.new_with_time(address, time, tags=nil, *args)
|
|
9
|
+
message = new(address, tags, *args)
|
|
10
|
+
message.time = time
|
|
11
|
+
message
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(address, *args)
|
|
15
|
+
@address = address
|
|
16
|
+
@args = []
|
|
17
|
+
|
|
18
|
+
args.each do |arg|
|
|
19
|
+
case arg
|
|
20
|
+
when Integer; @args << OSCInt32.new(arg)
|
|
21
|
+
when Float; @args << OSCFloat32.new(arg)
|
|
22
|
+
when String; @args << OSCString.new(arg)
|
|
23
|
+
when OSCArgument; @args << arg
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tags
|
|
29
|
+
@args.collect{|x| x.tag}.join
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def encode
|
|
33
|
+
s = OSCString.new(@address).encode
|
|
34
|
+
s << OSCString.new(',' + tags).encode
|
|
35
|
+
s << @args.collect{|x| x.encode}.join
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_a
|
|
39
|
+
@args.collect{|x| x.val}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ==(other)
|
|
43
|
+
@address == other.address && to_a == other.to_a
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module OSC
|
|
2
|
+
class NetworkPacket
|
|
3
|
+
def initialize(str)
|
|
4
|
+
@str = str.force_encoding("BINARY")
|
|
5
|
+
@index = 0
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_s
|
|
9
|
+
@str
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def rem
|
|
13
|
+
@str.length - @index
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def eof?
|
|
17
|
+
rem <= 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def skip(n)
|
|
21
|
+
@index += n
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def skip_padding
|
|
25
|
+
skip((4 - (@index % 4)) % 4)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def getn(n)
|
|
29
|
+
raise(EOFError) if rem < n
|
|
30
|
+
s = @str[@index, n]
|
|
31
|
+
skip(n)
|
|
32
|
+
s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def getc
|
|
36
|
+
raise(EOFError) if rem < 1
|
|
37
|
+
c = @str[@index]
|
|
38
|
+
skip(1)
|
|
39
|
+
c
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module OSC
|
|
2
|
+
class OSCArgument
|
|
3
|
+
def initialize(val)
|
|
4
|
+
@val = val
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
attr_accessor :val
|
|
8
|
+
|
|
9
|
+
def to_i()
|
|
10
|
+
@val.to_i
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_f()
|
|
14
|
+
@val.to_f
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_s()
|
|
18
|
+
@val.to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def padding(s)
|
|
23
|
+
s + ("\000" * ((4 - (s.size % 4)) % 4))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|