feat: several enhancements
* feat: added gdUnit4 addon * feat: added support for parsing OSC timetags to Unix time * feat: added more static types to function signatures * feat: OSCReceiver server handles up to `max_packets_per_poll` (default `10`) packets per poll interval. * feat: more than one callback can be registered and dispatched to for a single OSC Address * fix: correct padding for OSC strings * docs: added docstrings for exported variables Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
parent
4e5e0f7345
commit
eaac73ab2e
|
@ -0,0 +1,3 @@
|
|||
[submodule "gdUnit4"]
|
||||
path = gdUnit4
|
||||
url = https://github.com/MikeSchulze/gdUnit4
|
|
@ -0,0 +1 @@
|
|||
../gdUnit4/addons/gdUnit4
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3fb08ac9ef5f45eedfe896dc7826aff756d18f10
|
|
@ -6,11 +6,16 @@ class_name OSCReceiver
|
|||
@export var default_address:String = "*"
|
||||
@export var default_port: int = 9001
|
||||
@export var poll_interval: float = 0.05
|
||||
@export var max_packets_per_poll: int = 10
|
||||
@export var debug: bool = false
|
||||
var _server: UDPServer
|
||||
var _observers: Dictionary
|
||||
var _timer: Timer
|
||||
|
||||
# UNIX_EPOCH = 1970-01-01 00:00:00
|
||||
# NTP_EPOCH = 1900-01-01 00:00:00
|
||||
# NTP_DELTA = UNIX_EPOCH - NTP_EPOCH in seconds
|
||||
const NTP_DELTA = 2208988800
|
||||
const ISIZE = 4294967296 # 2**32
|
||||
|
||||
func _init():
|
||||
_observers = {}
|
||||
|
@ -26,7 +31,7 @@ func _exit_tree():
|
|||
stop_server()
|
||||
|
||||
|
||||
func register_callback(osc_address, arg_types, callback) -> void:
|
||||
func register_callback(osc_address: String, arg_types: String, callback: Callable) -> void:
|
||||
if not _observers.has([osc_address, arg_types]):
|
||||
_observers[[osc_address, arg_types]] = []
|
||||
|
||||
|
@ -64,11 +69,14 @@ func _poll() -> void:
|
|||
|
||||
_server.poll()
|
||||
|
||||
if _server.is_connection_available():
|
||||
var num_packets = 0
|
||||
|
||||
while num_packets < max_packets_per_poll and _server.is_connection_available():
|
||||
var peer: PacketPeerUDP = _server.take_connection()
|
||||
var packet = peer.get_packet()
|
||||
var sender_ip = peer.get_packet_ip()
|
||||
var sender_port = peer.get_packet_port()
|
||||
num_packets += 1
|
||||
|
||||
_debug("Accepted peer: %s:%s" % [sender_ip, sender_port])
|
||||
|
||||
|
@ -80,7 +88,11 @@ func _poll() -> void:
|
|||
_debug("OSC address: %s" % address)
|
||||
_debug("OSC arg types: %s" % types)
|
||||
|
||||
if _observers.has([address, types]):
|
||||
var callbacks = _observers.get([address, types], [])
|
||||
# Get callbacks registered with types wild-card.
|
||||
callbacks.append_array(_observers.get([address, "*"], []))
|
||||
|
||||
if callbacks:
|
||||
result = _parse_osc_values(packet.slice(offset), types)
|
||||
|
||||
if result[0] != OK:
|
||||
|
@ -95,7 +107,7 @@ func _poll() -> void:
|
|||
"types": types
|
||||
}
|
||||
|
||||
for callback in _observers[[address, types]]:
|
||||
for callback in callbacks:
|
||||
callback.call(msg_info, result[1])
|
||||
|
||||
func _parse_osc_addr_and_types(packet: PackedByteArray) -> Array:
|
||||
|
@ -155,13 +167,9 @@ func _parse_osc_values(packet: PackedByteArray, types: String) -> Array:
|
|||
_debug("Could not read OSC blob argument.")
|
||||
return [ERR_PARSE_ERROR]
|
||||
"t":
|
||||
result = stream.get_data(8)
|
||||
|
||||
if result[0] == OK:
|
||||
values.append(result[1])
|
||||
else:
|
||||
_debug("Could not read OSC timetag argument.")
|
||||
return [ERR_PARSE_ERROR]
|
||||
var sec = stream.get_u32()
|
||||
var frac = stream.get_u32()
|
||||
values.append(to_time(sec, frac))
|
||||
"m", "r":
|
||||
values.append([
|
||||
stream.get_u8(),
|
||||
|
@ -180,3 +188,14 @@ func _parse_osc_values(packet: PackedByteArray, types: String) -> Array:
|
|||
return [ERR_INVALID_DATA]
|
||||
|
||||
return [OK, values]
|
||||
|
||||
|
||||
## Return seconds and fractional part of NTP timestamp as 2-item array.
|
||||
func to_frac(timestamp) -> Array:
|
||||
var sec = int(timestamp)
|
||||
return [sec, int(abs(timestamp - sec) * ISIZE)]
|
||||
|
||||
|
||||
## Return NTP timestamp from integer seconds and fractional part.
|
||||
func to_time(sec: int, frac: int) -> float:
|
||||
return sec + float(frac) / ISIZE
|
||||
|
|
|
@ -11,10 +11,8 @@ var _port: int
|
|||
var _socket = PacketPeerUDP.new()
|
||||
|
||||
|
||||
func _init():
|
||||
_address = default_address
|
||||
_port = default_port
|
||||
_socket.set_dest_address(_address, _port)
|
||||
func _init(dest = [default_address, default_port]):
|
||||
set_destination(dest)
|
||||
|
||||
|
||||
func _debug(msg) -> void:
|
||||
|
@ -35,7 +33,7 @@ func set_destination(dest) -> void:
|
|||
_port = dest[1]
|
||||
|
||||
|
||||
func create_message(osc_address: String, arg_types: String, values: Array) -> Array:
|
||||
func create_message(osc_address: String, arg_types: String = "", values: Array = []) -> Array:
|
||||
if not osc_address.begins_with("/"):
|
||||
return [ERR_INVALID_DATA]
|
||||
|
||||
|
@ -82,7 +80,7 @@ func create_message(osc_address: String, arg_types: String, values: Array) -> Ar
|
|||
return [OK, buf.get_data_array()]
|
||||
|
||||
|
||||
func send_osc(osc_address: String, arg_types: String, values: Array, dest = null) -> Error:
|
||||
func send_osc(osc_address: String, arg_types: String = "", values: Array = [], dest = null) -> Error:
|
||||
var res = create_message(osc_address, arg_types, values)
|
||||
|
||||
if res[0] != OK:
|
||||
|
@ -98,6 +96,7 @@ func send_osc(osc_address: String, arg_types: String, values: Array, dest = null
|
|||
func _pack_string(buf: StreamPeerBuffer, s: String) -> void:
|
||||
## Pack a string into a binary OSC buffer
|
||||
buf.put_data(s.to_ascii_buffer())
|
||||
buf.put_u8(0)
|
||||
|
||||
# pad to next 32-bit offset
|
||||
while buf.get_position() % 4:
|
||||
|
|
|
@ -26,6 +26,10 @@ window/size/viewport_height=800
|
|||
window/stretch/mode="canvas_items"
|
||||
window/handheld/orientation=1
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/gdUnit4/plugin.cfg")
|
||||
|
||||
[input_devices]
|
||||
|
||||
pointing/emulate_touch_from_mouse=true
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# GdUnit4 TestSuite
|
||||
class_name OscSenderTest
|
||||
extends GdUnitTestSuite
|
||||
@warning_ignore('unused_parameter')
|
||||
@warning_ignore('return_value_discarded')
|
||||
|
||||
# TestSuite for
|
||||
const __source = 'res://osc_sender.gd'
|
||||
|
||||
|
||||
func test_create_message_minimal() -> void:
|
||||
var sender = OSCSender.new()
|
||||
var result = sender.create_message("/")
|
||||
assert_bool(result[0] == OK).is_true()
|
||||
assert_int(result[1].size()).is_equal(8)
|
||||
assert_str(result[1].get_string_from_ascii()).is_equal("/")
|
||||
assert_str(result[1].slice(4).get_string_from_ascii()).is_equal(",")
|
||||
assert_array(Array(result[1])).is_equal([47, 0, 0 , 0, 44, 0, 0, 0])
|
||||
sender.free()
|
||||
|
||||
func test_address_padding0() -> void:
|
||||
var sender = OSCSender.new()
|
||||
var result = sender.create_message("/f12")
|
||||
assert_bool(result[0] == OK).is_true()
|
||||
assert_int(result[1].size()).is_equal(8)
|
||||
assert_str(result[1].get_string_from_ascii()).is_equal("/12")
|
||||
assert_array(Array(result[1])).is_equal([47, 49, 50 , 0, 44, 0, 0, 0])
|
||||
sender.free()
|
||||
|
||||
func test_address_padding1() -> void:
|
||||
var sender = OSCSender.new()
|
||||
var result = sender.create_message("/f1")
|
||||
assert_bool(result[0] == OK).is_true()
|
||||
assert_int(result[1].size()).is_equal(8)
|
||||
assert_str(result[1].get_string_from_ascii()).is_equal("/1")
|
||||
assert_array(Array(result[1])).is_equal([47, 49, 0 , 0, 44, 0, 0, 0])
|
||||
sender.free()
|
||||
|
||||
func test_address_padding2() -> void:
|
||||
var sender = OSCSender.new()
|
||||
var result = sender.create_message("/1234")
|
||||
assert_bool(result[0] == OK).is_true()
|
||||
assert_int(result[1].size()).is_equal(12)
|
||||
assert_str(result[1].get_string_from_ascii()).is_equal("/1234")
|
||||
assert_array(Array(result[1].slice(0, 8))).is_equal([47, 49, 50 , 51, 52, 0, 0, 0])
|
||||
sender.free()
|
||||
|
||||
func test_address_padding3() -> void:
|
||||
var sender = OSCSender.new()
|
||||
var result = sender.create_message("/123")
|
||||
assert_bool(result[0] == OK).is_true()
|
||||
assert_int(result[1].size()).is_equal(12)
|
||||
assert_str(result[1].get_string_from_ascii()).is_equal("/123")
|
||||
assert_array(Array(result[1].slice(0, 8))).is_equal([47, 49, 50 , 51, 0, 0, 0, 0])
|
||||
sender.free()
|
12
ui.gd
12
ui.gd
|
@ -6,10 +6,22 @@ var osc_server: OSCReceiver
|
|||
const OSCSender = preload("res://osc_sender.gd")
|
||||
var osc_client: OSCSender
|
||||
|
||||
## The network address the OSCReceiver UDP server listens on.
|
||||
##
|
||||
## A string containing a hostname or IP address or a special wildcard.
|
||||
##
|
||||
## '*' (default) will make the server listen on all IPv4 and IPv6 interfaces
|
||||
## '0.0.0.0' means all IPv4 and '::1' all IPv6 interfaces.
|
||||
@export var osc_server_address:String = "*"
|
||||
## The port the OSCReceiver UDP server listens on.
|
||||
@export var osc_server_port: int = 9001
|
||||
## The address the OSCSender UDP client will send packets too.
|
||||
## A string containing a hostname or IP address.
|
||||
@export var osc_dest_address:String = "127.0.0.1"
|
||||
## The port the OSCSender UDP client will send packets too.
|
||||
@export var osc_dest_port: int = 9000
|
||||
## Setting this to true enables logging of received OSC message information
|
||||
## and UI control value changes.
|
||||
@export var debug: bool = false
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue