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
					
				
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					[submodule "gdUnit4"]
 | 
				
			||||||
 | 
						path = gdUnit4
 | 
				
			||||||
 | 
						url = https://github.com/MikeSchulze/gdUnit4
 | 
				
			||||||
							
								
								
									
										1
									
								
								addons/gdUnit4
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								addons/gdUnit4
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					../gdUnit4/addons/gdUnit4
 | 
				
			||||||
							
								
								
									
										1
									
								
								gdUnit4
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								gdUnit4
									
									
									
									
									
										Submodule
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Subproject commit 3fb08ac9ef5f45eedfe896dc7826aff756d18f10
 | 
				
			||||||
@ -6,11 +6,16 @@ class_name OSCReceiver
 | 
				
			|||||||
@export var default_address:String = "*"
 | 
					@export var default_address:String = "*"
 | 
				
			||||||
@export var default_port: int = 9001
 | 
					@export var default_port: int = 9001
 | 
				
			||||||
@export var poll_interval: float = 0.05
 | 
					@export var poll_interval: float = 0.05
 | 
				
			||||||
 | 
					@export var max_packets_per_poll: int = 10
 | 
				
			||||||
@export var debug: bool = false
 | 
					@export var debug: bool = false
 | 
				
			||||||
var _server: UDPServer
 | 
					var _server: UDPServer
 | 
				
			||||||
var _observers: Dictionary
 | 
					var _observers: Dictionary
 | 
				
			||||||
var _timer: Timer
 | 
					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():
 | 
					func _init():
 | 
				
			||||||
	_observers = {}
 | 
						_observers = {}
 | 
				
			||||||
@ -26,7 +31,7 @@ func _exit_tree():
 | 
				
			|||||||
	stop_server()
 | 
						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]):
 | 
						if not _observers.has([osc_address, arg_types]):
 | 
				
			||||||
		_observers[[osc_address, arg_types]] = []
 | 
							_observers[[osc_address, arg_types]] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,11 +69,14 @@ func _poll() -> void:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	_server.poll()
 | 
						_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 peer: PacketPeerUDP = _server.take_connection()
 | 
				
			||||||
		var packet = peer.get_packet()
 | 
							var packet = peer.get_packet()
 | 
				
			||||||
		var sender_ip = peer.get_packet_ip()
 | 
							var sender_ip = peer.get_packet_ip()
 | 
				
			||||||
		var sender_port = peer.get_packet_port()
 | 
							var sender_port = peer.get_packet_port()
 | 
				
			||||||
 | 
							num_packets += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_debug("Accepted peer: %s:%s" % [sender_ip, sender_port])
 | 
							_debug("Accepted peer: %s:%s" % [sender_ip, sender_port])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,7 +88,11 @@ func _poll() -> void:
 | 
				
			|||||||
		_debug("OSC address: %s" % address)
 | 
							_debug("OSC address: %s" % address)
 | 
				
			||||||
		_debug("OSC arg types: %s" % types)
 | 
							_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)
 | 
								result = _parse_osc_values(packet.slice(offset), types)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if result[0] != OK:
 | 
								if result[0] != OK:
 | 
				
			||||||
@ -95,17 +107,17 @@ func _poll() -> void:
 | 
				
			|||||||
					"types": types
 | 
										"types": types
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				for callback in _observers[[address, types]]:
 | 
									for callback in callbacks:
 | 
				
			||||||
					callback.call(msg_info, result[1])
 | 
										callback.call(msg_info, result[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func _parse_osc_addr_and_types(packet: PackedByteArray) -> Array:
 | 
					func _parse_osc_addr_and_types(packet: PackedByteArray) -> Array:
 | 
				
			||||||
	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()
 | 
				
			||||||
	
 | 
					
 | 
				
			||||||
	var toffset = asep + (4 - asep % 4)
 | 
						var toffset = asep + (4 - asep % 4)
 | 
				
			||||||
	assert(char(packet.decode_u8(toffset)) == ",")
 | 
						assert(char(packet.decode_u8(toffset)) == ",")
 | 
				
			||||||
	var tsep = packet.find(0, toffset)
 | 
						var tsep = packet.find(0, toffset)
 | 
				
			||||||
	var types = packet.slice(toffset + 1, tsep).get_string_from_ascii() 
 | 
						var types = packet.slice(toffset + 1, tsep).get_string_from_ascii()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return [address, types, tsep + (4 - tsep % 4)]
 | 
						return [address, types, tsep + (4 - tsep % 4)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -145,7 +157,7 @@ func _parse_osc_values(packet: PackedByteArray, types: String) -> Array:
 | 
				
			|||||||
			"b":
 | 
								"b":
 | 
				
			||||||
				var count = stream.get_u32()
 | 
									var count = stream.get_u32()
 | 
				
			||||||
				result = stream.get_data(count)
 | 
									result = stream.get_data(count)
 | 
				
			||||||
				
 | 
					
 | 
				
			||||||
				if result[0] == OK:
 | 
									if result[0] == OK:
 | 
				
			||||||
					values.append(result[1])
 | 
										values.append(result[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,13 +167,9 @@ func _parse_osc_values(packet: PackedByteArray, types: String) -> Array:
 | 
				
			|||||||
					_debug("Could not read OSC blob argument.")
 | 
										_debug("Could not read OSC blob argument.")
 | 
				
			||||||
					return [ERR_PARSE_ERROR]
 | 
										return [ERR_PARSE_ERROR]
 | 
				
			||||||
			"t":
 | 
								"t":
 | 
				
			||||||
				result = stream.get_data(8)
 | 
									var sec = stream.get_u32()
 | 
				
			||||||
 | 
									var frac = stream.get_u32()
 | 
				
			||||||
				if result[0] == OK:
 | 
									values.append(to_time(sec, frac))
 | 
				
			||||||
					values.append(result[1])
 | 
					 | 
				
			||||||
				else:
 | 
					 | 
				
			||||||
					_debug("Could not read OSC timetag argument.")
 | 
					 | 
				
			||||||
					return [ERR_PARSE_ERROR]
 | 
					 | 
				
			||||||
			"m", "r":
 | 
								"m", "r":
 | 
				
			||||||
				values.append([
 | 
									values.append([
 | 
				
			||||||
					stream.get_u8(),
 | 
										stream.get_u8(),
 | 
				
			||||||
@ -180,3 +188,14 @@ func _parse_osc_values(packet: PackedByteArray, types: String) -> Array:
 | 
				
			|||||||
				return [ERR_INVALID_DATA]
 | 
									return [ERR_INVALID_DATA]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return [OK, values]
 | 
						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()
 | 
					var _socket = PacketPeerUDP.new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func _init():
 | 
					func _init(dest = [default_address, default_port]):
 | 
				
			||||||
	_address = default_address
 | 
						set_destination(dest)
 | 
				
			||||||
	_port = default_port
 | 
					 | 
				
			||||||
	_socket.set_dest_address(_address, _port)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func _debug(msg) -> void:
 | 
					func _debug(msg) -> void:
 | 
				
			||||||
@ -35,7 +33,7 @@ func set_destination(dest) -> void:
 | 
				
			|||||||
		_port = dest[1]
 | 
							_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("/"):
 | 
						if not osc_address.begins_with("/"):
 | 
				
			||||||
		return [ERR_INVALID_DATA]
 | 
							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()]
 | 
						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)
 | 
						var res = create_message(osc_address, arg_types, values)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if res[0] != OK:
 | 
						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:
 | 
					func _pack_string(buf: StreamPeerBuffer, s: String) -> void:
 | 
				
			||||||
	## Pack a string into a binary OSC buffer
 | 
						## Pack a string into a binary OSC buffer
 | 
				
			||||||
	buf.put_data(s.to_ascii_buffer())
 | 
						buf.put_data(s.to_ascii_buffer())
 | 
				
			||||||
 | 
						buf.put_u8(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# pad to next 32-bit offset
 | 
						# pad to next 32-bit offset
 | 
				
			||||||
	while buf.get_position() % 4:
 | 
						while buf.get_position() % 4:
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,10 @@ window/size/viewport_height=800
 | 
				
			|||||||
window/stretch/mode="canvas_items"
 | 
					window/stretch/mode="canvas_items"
 | 
				
			||||||
window/handheld/orientation=1
 | 
					window/handheld/orientation=1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[editor_plugins]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enabled=PackedStringArray("res://addons/gdUnit4/plugin.cfg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[input_devices]
 | 
					[input_devices]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pointing/emulate_touch_from_mouse=true
 | 
					pointing/emulate_touch_from_mouse=true
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										55
									
								
								test/osc_sender_test.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								test/osc_sender_test.gd
									
									
									
									
									
										Normal file
									
								
							@ -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")
 | 
					const OSCSender = preload("res://osc_sender.gd")
 | 
				
			||||||
var osc_client: OSCSender
 | 
					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 = "*"
 | 
					@export var osc_server_address:String = "*"
 | 
				
			||||||
 | 
					## The port the OSCReceiver UDP server listens on.
 | 
				
			||||||
@export var osc_server_port: int = 9001
 | 
					@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"
 | 
					@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
 | 
					@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
 | 
					@export var debug: bool = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user