feat: basic support for OSC sending

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
Christopher Arndt 2023-11-04 16:06:33 +01:00
parent 4f0bb2fa93
commit b7ef9bc01b
10 changed files with 193 additions and 63 deletions

35
UI.gd
View File

@ -1,35 +0,0 @@
extends Control
func _ready():
var scene = preload("res://osc_receiver.tscn")
var osc_server = scene.instantiate()
add_child(osc_server)
# configure all sensors
for i in range(4):
var nodename = "VSlider%d" % (i + 1)
var osc_addr = "/slider/%d/set" % i
var slider_node = find_child(nodename)
osc_server.register_callback(osc_addr, "f", slider_node.recv_osc)
for i in range(4):
var nodename = "Button%d" % (i + 1)
var osc_addr = "/button/%d/set" % i
var button_node = find_child(nodename)
osc_server.register_callback(osc_addr, "i", button_node.recv_osc)
osc_server.register_callback("/string", "s", recv_osc)
osc_server.register_callback("/stringint", "si", recv_osc)
osc_server.register_callback("/blob", "b", recv_osc)
osc_server.register_callback("/blobint", "bi", recv_osc)
# start listening for osc messages
osc_server.start_server()
func recv_osc(msg_info, values):
print("Sender IP: %s" % msg_info["ip"])
print("Sender Port: %d" % msg_info["port"])
print("Address: %s" % msg_info["address"])
print("Types: %s" % msg_info["types"])
print("Values:", values)

24
UI.tscn
View File

@ -1,8 +1,8 @@
[gd_scene load_steps=8 format=3 uid="uid://8xyprd3yldfg"] [gd_scene load_steps=8 format=3 uid="uid://8xyprd3yldfg"]
[ext_resource type="Script" path="res://UI.gd" id="1_ft657"] [ext_resource type="Script" path="res://ui.gd" id="1_28t32"]
[ext_resource type="Script" path="res://Slider.gd" id="2_310mb"] [ext_resource type="Script" path="res://slider.gd" id="2_7hpi4"]
[ext_resource type="Script" path="res://ToggleButton.gd" id="3_ct66k"] [ext_resource type="Script" path="res://toggle_button.gd" id="3_ch7hh"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y0b18"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y0b18"]
bg_color = Color(1, 0, 0, 1) bg_color = Color(1, 0, 0, 1)
@ -23,7 +23,7 @@ anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("1_ft657") script = ExtResource("1_28t32")
[node name="MarginContainer" type="MarginContainer" parent="."] [node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1 layout_mode = 1
@ -52,7 +52,7 @@ max_value = 1.0
step = 0.01 step = 0.01
tick_count = 11 tick_count = 11
ticks_on_borders = true ticks_on_borders = true
script = ExtResource("2_310mb") script = ExtResource("2_7hpi4")
[node name="VSlider2" type="VSlider" parent="MarginContainer/GridContainer"] [node name="VSlider2" type="VSlider" parent="MarginContainer/GridContainer"]
custom_minimum_size = Vector2(50, 300) custom_minimum_size = Vector2(50, 300)
@ -63,7 +63,7 @@ max_value = 1.0
step = 0.01 step = 0.01
tick_count = 11 tick_count = 11
ticks_on_borders = true ticks_on_borders = true
script = ExtResource("2_310mb") script = ExtResource("2_7hpi4")
[node name="VSlider3" type="VSlider" parent="MarginContainer/GridContainer"] [node name="VSlider3" type="VSlider" parent="MarginContainer/GridContainer"]
custom_minimum_size = Vector2(50, 300) custom_minimum_size = Vector2(50, 300)
@ -74,7 +74,7 @@ max_value = 1.0
step = 0.01 step = 0.01
tick_count = 11 tick_count = 11
ticks_on_borders = true ticks_on_borders = true
script = ExtResource("2_310mb") script = ExtResource("2_7hpi4")
[node name="VSlider4" type="VSlider" parent="MarginContainer/GridContainer"] [node name="VSlider4" type="VSlider" parent="MarginContainer/GridContainer"]
custom_minimum_size = Vector2(50, 300) custom_minimum_size = Vector2(50, 300)
@ -85,32 +85,32 @@ max_value = 1.0
step = 0.01 step = 0.01
tick_count = 11 tick_count = 11
ticks_on_borders = true ticks_on_borders = true
script = ExtResource("2_310mb") script = ExtResource("2_7hpi4")
[node name="Button1" type="Button" parent="MarginContainer/GridContainer"] [node name="Button1" type="Button" parent="MarginContainer/GridContainer"]
layout_mode = 2 layout_mode = 2
theme_override_styles/pressed = SubResource("StyleBoxFlat_y0b18") theme_override_styles/pressed = SubResource("StyleBoxFlat_y0b18")
toggle_mode = true toggle_mode = true
text = "1" text = "1"
script = ExtResource("3_ct66k") script = ExtResource("3_ch7hh")
[node name="Button2" type="Button" parent="MarginContainer/GridContainer"] [node name="Button2" type="Button" parent="MarginContainer/GridContainer"]
layout_mode = 2 layout_mode = 2
theme_override_styles/pressed = SubResource("StyleBoxFlat_iu8u7") theme_override_styles/pressed = SubResource("StyleBoxFlat_iu8u7")
toggle_mode = true toggle_mode = true
text = "2" text = "2"
script = ExtResource("3_ct66k") script = ExtResource("3_ch7hh")
[node name="Button3" type="Button" parent="MarginContainer/GridContainer"] [node name="Button3" type="Button" parent="MarginContainer/GridContainer"]
layout_mode = 2 layout_mode = 2
theme_override_styles/pressed = SubResource("StyleBoxFlat_7e33t") theme_override_styles/pressed = SubResource("StyleBoxFlat_7e33t")
toggle_mode = true toggle_mode = true
text = "3" text = "3"
script = ExtResource("3_ct66k") script = ExtResource("3_ch7hh")
[node name="Button4" type="Button" parent="MarginContainer/GridContainer"] [node name="Button4" type="Button" parent="MarginContainer/GridContainer"]
layout_mode = 2 layout_mode = 2
theme_override_styles/pressed = SubResource("StyleBoxFlat_eeife") theme_override_styles/pressed = SubResource("StyleBoxFlat_eeife")
toggle_mode = true toggle_mode = true
text = "4 " text = "4 "
script = ExtResource("3_ct66k") script = ExtResource("3_ch7hh")

View File

@ -1,5 +1,7 @@
extends Node extends Node
class_name OSCReceiver
@export var default_address:String = "0.0.0.0" @export var default_address:String = "0.0.0.0"
@export var default_port: int = 9001 @export var default_port: int = 9001
@ -51,6 +53,11 @@ func stop_server():
_server.stop() _server.stop()
func _debug(msg):
if debug:
print(msg)
func _poll(): func _poll():
if not _server.is_listening(): if not _server.is_listening():
return return
@ -91,7 +98,7 @@ func _poll():
for callback in _observers[[address, types]]: for callback in _observers[[address, types]]:
callback.call(msg_info, values) callback.call(msg_info, values)
func _parse_osc_addr_and_types(packet): func _parse_osc_addr_and_types(packet: PackedByteArray):
var asep = packet.find(0) var asep = packet.find(0)
var address = packet.slice(0, asep).get_string_from_ascii() var address = packet.slice(0, asep).get_string_from_ascii()
@ -103,7 +110,7 @@ func _parse_osc_addr_and_types(packet):
return [address, types, tsep + (4 - tsep % 4)] return [address, types, tsep + (4 - tsep % 4)]
func _parse_osc_values(packet, types): func _parse_osc_values(packet: PackedByteArray, types: String):
var result var result
var values = [] var values = []
var stream = StreamPeerBuffer.new() var stream = StreamPeerBuffer.new()
@ -173,8 +180,3 @@ func _parse_osc_values(packet, types):
return return
return values return values
func _debug(msg):
if debug:
print(msg)

View File

@ -1,7 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://bqci5bw1h0lh7"]
[ext_resource type="Script" path="res://OSCReceiver.gd" id="1_ujyu5"]
[node name="OSCReceiver" type="Node"]
script = ExtResource("1_ujyu5")
debug = true

108
osc_sender.gd Normal file
View File

@ -0,0 +1,108 @@
extends Node
class_name OSCSender
@export var default_address:String = "0.0.0.0"
@export var default_port: int = 9001
@export var debug: bool = false
var _address: String
var _port: int
var socket = PacketPeerUDP.new()
func _init():
_address = default_address
_port = default_port
func _debug(msg):
if debug:
print(msg)
func set_destination(dest):
if dest is int:
socket.set_dest_address(_address, dest)
_port = dest
elif dest is String:
socket.set_dest_address(dest, _port)
_address = dest
elif dest is Array:
socket.set_dest_address(dest[0], dest[1])
_address = dest[0]
_port = dest[1]
func send_osc(dest, oscaddress: String, argtypes: String, values: Array):
assert(oscaddress.begins_with("/"))
var buf = StreamPeerBuffer.new()
buf.set_big_endian(true)
_pack_string(buf, oscaddress)
_pack_string(buf, "," + argtypes)
var vidx: int = 0
for i in argtypes.length():
var typetag = argtypes[i]
var inc_vidx = true
match typetag:
"i":
buf.put_32(values[vidx])
"h":
buf.put_64(values[vidx])
"f":
buf.put_float(values[vidx])
"d":
buf.put_double(values[vidx])
"c":
buf.put_32(values[vidx])
"s", "S":
_pack_string(buf, values[vidx])
"b":
_pack_blob(buf, values[vidx])
"t":
assert(values[i].size() == 8)
buf.put_data(values[vidx])
"m", "r":
assert(values[vidx].size() == 4)
buf.put_data(PackedByteArray(values[vidx]))
"TFI":
inc_vidx = false # no argument value sent
_:
_debug("Argument type '%s' not supported." % typetag)
return FAILED
if inc_vidx:
vidx += 1
if dest != null:
set_destination(dest)
socket.put_packet(buf.get_data_array())
return OK
func _pack_string(buf: StreamPeerBuffer, s: String):
## Pack a string into a binary OSC buffer
buf.put_data(s.to_ascii_buffer())
# pad to next 32-bit offset
while buf.get_position() % 4:
buf.put_u8(0)
func _pack_blob(buf, blob):
## Pack a PackedByteArray, Array or String into a binary OSC buffer
if blob is String:
blob = blob.to_utf8_buffer()
elif blob is Array:
blob = PackedByteArray(blob)
assert(blob.size() < 2 ** 31) # blob size per spec is _signed_ 32-bit int
buf.put_32(blob.size())
buf.put_data(blob)
# pad to next 32-bit offset
while buf.get_position() % 4:
buf.put_u8(0)

View File

@ -10,7 +10,7 @@ config_version=5
[application] [application]
config/name="OSCReceiver Demo" config/name="OSC Demo"
run/main_scene="res://UI.tscn" run/main_scene="res://UI.tscn"
config/features=PackedStringArray("4.1") config/features=PackedStringArray("4.1")

View File

@ -6,4 +6,4 @@ func recv_osc(msg_info, values):
print("Sender Port: %d" % msg_info["port"]) print("Sender Port: %d" % msg_info["port"])
print("Address: %s" % msg_info["address"]) print("Address: %s" % msg_info["address"])
print("Types: %s" % msg_info["types"]) print("Types: %s" % msg_info["types"])
set_value(clampf(values[0], 0.0, 1.0)) set_value_no_signal(clampf(values[0], 0.0, 1.0))

13
testosc.sh Executable file
View File

@ -0,0 +1,13 @@
HOST=localhost
PORT=9001
for ctrl in 0 1 2 3; do
for value in `LC_ALL=C seq 0.1 0.1 1.0`; do
oscsend $HOST $PORT /slider/$ctrl/set f $value
sleep 0.1
done
oscsend $HOST $PORT /button/$ctrl/set i 1
sleep 1
oscsend $HOST $PORT /button/$ctrl/set i 0
done

49
ui.gd Normal file
View File

@ -0,0 +1,49 @@
extends Control
const OSCReceiver = preload("res://osc_receiver.gd")
var osc_server: OSCReceiver
const OSCSender = preload("res://osc_sender.gd")
var osc_client: OSCSender
func _ready():
osc_client = OSCSender.new()
osc_server = OSCReceiver.new()
add_child(osc_server)
var button_callback = Callable(self, "_button_pressed")
var slider_callback = Callable(self, "_slider_moved")
# configure all sensors
for i in range(4):
var nodename = "VSlider%d" % (i + 1)
var osc_addr = "/slider/%d/set" % i
var slider_node = find_child(nodename)
osc_server.register_callback(osc_addr, "f", slider_node.recv_osc)
slider_node.value_changed.connect(slider_callback.bind(i))
for i in range(4):
var nodename = "Button%d" % (i + 1)
var osc_addr = "/button/%d/set" % i
var button_node = find_child(nodename)
osc_server.register_callback(osc_addr, "i", button_node.recv_osc)
button_node.pressed.connect(button_callback.bind(button_node, i))
# start listening for osc messages
osc_server.start_server()
func _button_pressed(btn: Button, idx: int):
print("Button %s pressed: state = %s" % [idx, btn.button_pressed])
osc_client.send_osc(9000, "/button/%d" % idx, "i", [1 if btn.button_pressed else 0])
func _slider_moved(value: float, idx: int):
print("Slider %d moved: value = %f" % [idx, value])
osc_client.send_osc(9000, "/slider/%d" % idx, "f", [value])
func recv_osc(msg_info, values):
print("Sender IP: %s" % msg_info["ip"])
print("Sender Port: %d" % msg_info["port"])
print("Address: %s" % msg_info["address"])
print("Types: %s" % msg_info["types"])
print("Values:", values)