initial commit
This commit is contained in:
commit
628562f762
67
OscAddress.cs
Normal file
67
OscAddress.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace godotOscSharp
|
||||||
|
{
|
||||||
|
public class Address
|
||||||
|
{
|
||||||
|
public string Pattern { get; }
|
||||||
|
|
||||||
|
public Address(string pattern)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(pattern))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Pattern cannot be null or empty");
|
||||||
|
}
|
||||||
|
if (!pattern.StartsWith("/"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Pattern must start with a slash (/)");
|
||||||
|
}
|
||||||
|
if (pattern.Contains("//"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Pattern cannot contain two consecutive slashes (//)");
|
||||||
|
}
|
||||||
|
if (pattern.Contains(" "))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Pattern cannot contain spaces");
|
||||||
|
}
|
||||||
|
if (!Regex.IsMatch(pattern, @"^/([\w\-\.\*]+/)*([\w\-\.\*]+|\*)$"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Pattern contains invalid characters");
|
||||||
|
}
|
||||||
|
Pattern = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
var result = new System.Collections.Generic.List<byte>();
|
||||||
|
result.AddRange(System.Text.Encoding.ASCII.GetBytes(Pattern));
|
||||||
|
result.Add(0);
|
||||||
|
var padding = 4 - (result.Count % 4);
|
||||||
|
for (int i = 0; i < padding; i++)
|
||||||
|
{
|
||||||
|
result.Add(0);
|
||||||
|
}
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Address Parse(byte[] data, ref int index)
|
||||||
|
{
|
||||||
|
var start = index;
|
||||||
|
while (data[index] != 0)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
var pattern = System.Text.Encoding.ASCII.GetString(data, start, index - start);
|
||||||
|
index++;
|
||||||
|
var padding = 4 - ((index - start) % 4);
|
||||||
|
index += padding;
|
||||||
|
return new Address(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
OscArgument.cs
Normal file
100
OscArgument.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace godotOscSharp
|
||||||
|
{
|
||||||
|
// A class that represents a DWord
|
||||||
|
public class OscArgument
|
||||||
|
{
|
||||||
|
// The value of the DWord as an unsigned integer
|
||||||
|
public char Type { get; }
|
||||||
|
public object Value { get; }
|
||||||
|
|
||||||
|
// The constructor that takes an unsigned integer as the value
|
||||||
|
public OscArgument(object value, char type)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A method that parses a byte array to a DWord
|
||||||
|
public static OscArgument Parse(byte[] data, ref int index, char type)
|
||||||
|
{
|
||||||
|
// Use BitConverter to get the unsigned integer from the bytes at the given index in little-endian order
|
||||||
|
object value = null;
|
||||||
|
var start = index;
|
||||||
|
switch (type) {
|
||||||
|
case 'i':
|
||||||
|
value = BitConverter.ToInt32(data, index);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
value = BitConverter.ToSingle(data, index);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
while (data[index] != 0) // Find the null terminator
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
value = System.Text.Encoding.ASCII.GetString(data, start, index - start);
|
||||||
|
while (data[index] == 0 && index < data.Length)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
value = BitConverter.ToInt64(data, index);
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
value = BitConverter.ToDouble(data, index);
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
value = true;
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
value = false;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
value = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the index by 4 bytes
|
||||||
|
|
||||||
|
// Return a new DWord instance with the value
|
||||||
|
return new OscArgument(value, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
switch (Type) {
|
||||||
|
case 'i':
|
||||||
|
return BitConverter.GetBytes((int)Value);
|
||||||
|
case 'f':
|
||||||
|
return BitConverter.GetBytes((float)Value);
|
||||||
|
case 's':
|
||||||
|
var result = new System.Collections.Generic.List<byte>();
|
||||||
|
result.AddRange(System.Text.Encoding.ASCII.GetBytes((string)Value));
|
||||||
|
result.Add(0);
|
||||||
|
var padding = 4 - (result.Count % 4);
|
||||||
|
for (int i = 0; i < padding; i++)
|
||||||
|
{
|
||||||
|
result.Add(0);
|
||||||
|
}
|
||||||
|
return result.ToArray();
|
||||||
|
case 'h':
|
||||||
|
return BitConverter.GetBytes((long)Value);
|
||||||
|
case 'd':
|
||||||
|
return BitConverter.GetBytes((double)Value);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
OscBundle.cs
Normal file
78
OscBundle.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace godotOscSharp
|
||||||
|
{
|
||||||
|
public class OscBundle
|
||||||
|
{
|
||||||
|
public long TimeTag { get; }
|
||||||
|
public List<OscMessage> Messages { get; }
|
||||||
|
|
||||||
|
public OscBundle(long timeTag, List<OscMessage> messages)
|
||||||
|
{
|
||||||
|
TimeTag = timeTag;
|
||||||
|
Messages = messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
var result = new List<byte>();
|
||||||
|
result.AddRange(Encoding.ASCII.GetBytes("#bundle"));
|
||||||
|
result.Add(0);
|
||||||
|
result.AddRange(BitConverter.GetBytes(TimeTag));
|
||||||
|
foreach (var message in Messages)
|
||||||
|
{
|
||||||
|
var messageBytes = message.ToBytes();
|
||||||
|
result.AddRange(BitConverter.GetBytes(messageBytes.Length));
|
||||||
|
result.AddRange(messageBytes);
|
||||||
|
}
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OscBundle Parse(byte[] data)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
var identifier = Encoding.ASCII.GetString(data, index, 7);
|
||||||
|
if (identifier != "#bundle")
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid bundle identifier");
|
||||||
|
}
|
||||||
|
index += 8;
|
||||||
|
var timeTag = BitConverter.ToInt64(data, index);
|
||||||
|
index += 8;
|
||||||
|
var messages = new List<OscMessage>();
|
||||||
|
while (index < data.Length)
|
||||||
|
{
|
||||||
|
var size = BitConverter.ToInt32(data, index);
|
||||||
|
index += 4;
|
||||||
|
var messageData = data.Skip(index).Take(size).ToArray();
|
||||||
|
var message = OscMessage.Parse(messageData);
|
||||||
|
messages.Add(message);
|
||||||
|
index += size;
|
||||||
|
}
|
||||||
|
return new OscBundle(timeTag, messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append("OscBundle: ");
|
||||||
|
sb.Append("TimeTag: ");
|
||||||
|
sb.Append(TimeTag);
|
||||||
|
sb.Append(", ");
|
||||||
|
sb.Append("Messages: ");
|
||||||
|
sb.Append("[");
|
||||||
|
foreach (var message in Messages)
|
||||||
|
{
|
||||||
|
sb.Append(message.ToString());
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
sb.Remove(sb.Length - 2, 2);
|
||||||
|
sb.Append("]");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
OscMessage.cs
Normal file
74
OscMessage.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace godotOscSharp
|
||||||
|
{
|
||||||
|
public class OscMessage
|
||||||
|
{
|
||||||
|
public Address Address { get; }
|
||||||
|
|
||||||
|
public List<OscArgument> Data { get; }
|
||||||
|
|
||||||
|
public OscMessage(Address address, List<OscArgument> data)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OscMessage Parse(byte[] data)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
var address = Address.Parse(data, ref index);
|
||||||
|
var start = index;
|
||||||
|
while (data[index] != 0)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
var pattern = System.Text.Encoding.ASCII.GetString(data, start, index - start);
|
||||||
|
while (data[index] == 0)
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
var dataList = new List<OscArgument>();
|
||||||
|
for (var items = 1; items < pattern.Length; items++)
|
||||||
|
{
|
||||||
|
dataList.Add(OscArgument.Parse(data, ref index, pattern[items]));
|
||||||
|
}
|
||||||
|
return new OscMessage(address, dataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(Address.ToString());
|
||||||
|
foreach (var d in Data)
|
||||||
|
{
|
||||||
|
sb.Append(" ");
|
||||||
|
sb.Append(d.ToString());
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
var result = new System.Collections.Generic.List<byte>();
|
||||||
|
result.AddRange(Address.ToBytes().ToList<byte>());
|
||||||
|
result.Add(0x2c);
|
||||||
|
for (var i = 0; i < Data.Count(); i++) {
|
||||||
|
result.Add(BitConverter.GetBytes(Data[i].Type).ToList<byte>()[0]);
|
||||||
|
}
|
||||||
|
var padding = 4 - (result.Count % 4);
|
||||||
|
for (int i = 0; i < padding; i++)
|
||||||
|
{
|
||||||
|
result.Add(0);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < Data.Count(); i++) {
|
||||||
|
result.AddRange(Data[i].ToBytes().ToList<byte>());
|
||||||
|
}
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
OscReceiver.cs
Normal file
125
OscReceiver.cs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace godotOscSharp
|
||||||
|
{
|
||||||
|
// A class that represents an OSC receiver
|
||||||
|
public class OscReceiver : IDisposable
|
||||||
|
{
|
||||||
|
// The UDP client for receiving data
|
||||||
|
private UdpClient udpClient;
|
||||||
|
|
||||||
|
// The thread for listening to incoming messages
|
||||||
|
private Thread listenThread;
|
||||||
|
|
||||||
|
// The flag to indicate if the receiver is running
|
||||||
|
private bool running;
|
||||||
|
|
||||||
|
// The constructor that takes a port number
|
||||||
|
public OscReceiver(int port)
|
||||||
|
{
|
||||||
|
// Create a UDP client with the given port
|
||||||
|
udpClient = new UdpClient(port);
|
||||||
|
|
||||||
|
// Create a thread for listening to incoming messages
|
||||||
|
listenThread = new Thread(new ThreadStart(Listen));
|
||||||
|
|
||||||
|
// Set the running flag to true
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
// Start the thread
|
||||||
|
listenThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A method that listens to incoming messages
|
||||||
|
private void Listen()
|
||||||
|
{
|
||||||
|
// While the receiver is running
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Receive data from any source
|
||||||
|
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
byte[] data = udpClient.Receive(ref remoteEndPoint);
|
||||||
|
// Parse the data to an OSC message
|
||||||
|
if (data[0] == 0x2f)
|
||||||
|
{
|
||||||
|
OscMessage message = OscMessage.Parse(data);
|
||||||
|
MessageReceived?.Invoke(this, new OscMessageReceivedEventArgs(message, remoteEndPoint.Address.ToString(), remoteEndPoint.Port));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// GD.Print(string.Join(", ", data));
|
||||||
|
OscBundle bundle = OscBundle.Parse(data);
|
||||||
|
foreach (var message in bundle.Messages)
|
||||||
|
{
|
||||||
|
MessageReceived?.Invoke(this, new OscMessageReceivedEventArgs(message, remoteEndPoint.Address.ToString(), remoteEndPoint.Port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// If an exception occurs, invoke the error received event with the exception message
|
||||||
|
ErrorReceived?.Invoke(this, new OscErrorReceivedEventArgs(e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A method that disposes the receiver and releases resources
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Set the running flag to false
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
// Close the UDP client
|
||||||
|
udpClient.Close();
|
||||||
|
|
||||||
|
// Join the thread
|
||||||
|
listenThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// An event that occurs when a message is received
|
||||||
|
public event EventHandler<OscMessageReceivedEventArgs> MessageReceived;
|
||||||
|
|
||||||
|
// An event that occurs when an error is received
|
||||||
|
public event EventHandler<OscErrorReceivedEventArgs> ErrorReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A class that contains the data for the message received event
|
||||||
|
public class OscMessageReceivedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
// The OSC message that was received
|
||||||
|
public OscMessage Message { get; }
|
||||||
|
|
||||||
|
// The sender's IP address
|
||||||
|
public string IPAddress { get; }
|
||||||
|
|
||||||
|
// The sender's port number
|
||||||
|
public int Port { get; }
|
||||||
|
|
||||||
|
// The constructor that takes a message, an IP address and a port number
|
||||||
|
public OscMessageReceivedEventArgs(OscMessage message, string ipAddress, int port)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
IPAddress = ipAddress;
|
||||||
|
Port = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A class that contains the data for the error received event
|
||||||
|
public class OscErrorReceivedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
// The error message that was received
|
||||||
|
public string ErrorMessage { get; }
|
||||||
|
|
||||||
|
// The constructor that takes an error message
|
||||||
|
public OscErrorReceivedEventArgs(string errorMessage)
|
||||||
|
{
|
||||||
|
ErrorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
OscSender.cs
Normal file
38
OscSender.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace godotOscSharp
|
||||||
|
{
|
||||||
|
public class OscSender : IDisposable
|
||||||
|
{
|
||||||
|
private UdpClient udpClient;
|
||||||
|
|
||||||
|
private IPAddress host;
|
||||||
|
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
public OscSender(IPAddress host, int port)
|
||||||
|
{
|
||||||
|
udpClient = new UdpClient(0);
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
udpClient.Connect(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(OscMessage message)
|
||||||
|
{
|
||||||
|
byte[] data = message.ToBytes();
|
||||||
|
udpClient.Send(data, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
udpClient.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# godotOscSharp
|
||||||
|
|
||||||
|
A simple library for OpenSoundControl sending and receiving over a network, for use in Godot.
|
||||||
|
|
||||||
|
It's been lightly tested in Godot 4.1, invoked from a scene's script written in C#.
|
||||||
|
|
||||||
|
### How to Use
|
||||||
|
|
||||||
|
The easiest way to download this is to use one of the following commands from inside your project: `git submodule add https://github.com/cass-dlcm/godotOscSharp.git` or `git submodule add git@github.com:cass-dlcm/godotOscSharp.git`, depending on whether you connect to GitHub using HTTPS or SSH.
|
||||||
|
|
||||||
|
Then use:
|
||||||
|
|
||||||
|
```Bash
|
||||||
|
cd godotOscSharp
|
||||||
|
git checkout tags/v0.1.0 -b v0.1.0-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
It'll put the files inside a directory in your project named `godotOscSharp`, and checkout the version listed in the tag.
|
||||||
|
|
||||||
|
The constructor for the OscReceiver takes in an `int` port as a mandatory argument.
|
||||||
|
|
||||||
|
The constructor for the OscSender takes in an `IPAddress` and a `int` port as mandatory arguments.
|
||||||
|
|
||||||
|
Here's an example of how to create and use a receiver.
|
||||||
|
```C#
|
||||||
|
var receiver = new godotOscSharp.OscReceiver(9000);
|
||||||
|
receiver.MessageReceived += (sender, e) =>
|
||||||
|
{
|
||||||
|
GD.Print($"Received a message from {e.IPAddress}:{e.Port}");
|
||||||
|
GD.Print(e.Message.ToString());
|
||||||
|
};
|
||||||
|
receiver.ErrorReceived += (sender, e) =>
|
||||||
|
{
|
||||||
|
GD.Print($"Error: {e.ErrorMessage}");
|
||||||
|
};
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user