mirror of
https://github.com/jwetzell/QControlKit.git
synced 2026-08-18 13:44:09 +00:00
Initial Commit
This commit is contained in:
+199
@@ -0,0 +1,199 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
Components
|
||||||
|
[Pp]ackages
|
||||||
|
*.userprefs
|
||||||
|
Resource.designer.cs
|
||||||
|
|
||||||
|
# Visual Studio (>=2015) project-specific, machine local files
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sln.docstates
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# ignore Xamarin.Android Resource.Designer.cs files
|
||||||
|
**/*.Droid/**/[Rr]esource.[Dd]esigner.cs
|
||||||
|
**/*.Android/**/[Rr]esource.[Dd]esigner.cs
|
||||||
|
**/Android/**/[Rr]esource.[Dd]esigner.cs
|
||||||
|
**/Droid/**/[Rr]esource.[Dd]esigner.cs
|
||||||
|
|
||||||
|
# Xamarin Components
|
||||||
|
Components/
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
x64/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
#NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding addin-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
*.ncrunch*
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
|
||||||
|
# NuGet Packages Directory
|
||||||
|
packages/
|
||||||
|
*.nuget.targets
|
||||||
|
*.lock.json
|
||||||
|
*.nuget.props
|
||||||
|
|
||||||
|
## TODO: If the tool you use requires repositories.config uncomment the next line
|
||||||
|
#!packages/repositories.config
|
||||||
|
|
||||||
|
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
|
||||||
|
# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
|
||||||
|
!packages/build/
|
||||||
|
|
||||||
|
# Windows Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
sql/
|
||||||
|
*.Cache
|
||||||
|
ClientBin/
|
||||||
|
[Ss]tyle[Cc]op.*
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
.DS_Store
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file to a newer
|
||||||
|
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
.vscode/settings.json
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSharp", "QSharp\QSharp.csproj", "{51CD2579-79C9-49A9-B68D-93CBE878BAA1}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSharpDemo", "QSharpDemo\QSharpDemo.csproj", "{8518D042-D511-4DBB-8406-5B11B2777ED2}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using Zeroconf;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QBrowser
|
||||||
|
{
|
||||||
|
const string UDPServiceType = "_qlab._udp.local.";
|
||||||
|
const string TCPServiceType = "_qlab._tcp.local.";
|
||||||
|
|
||||||
|
const int udpPort = 53000;
|
||||||
|
|
||||||
|
IObservable<IZeroconfHost> netServices;
|
||||||
|
ZeroconfResolver.ResolverListener netServiceTCPBrowser;
|
||||||
|
|
||||||
|
ObservableCollection<QServer> servers = new ObservableCollection<QServer>();
|
||||||
|
|
||||||
|
public QBrowser()
|
||||||
|
{
|
||||||
|
|
||||||
|
Console.WriteLine("QBrowser Init");
|
||||||
|
netServiceTCPBrowser = ZeroconfResolver.CreateListener(TCPServiceType);
|
||||||
|
netServiceTCPBrowser.ServiceFound += ServerFound;
|
||||||
|
netServiceTCPBrowser.ServiceLost += ServerLost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ServerLost(object sender, IZeroconfHost e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Lost {e.DisplayName} : {e.IPAddress}");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ServerFound(object sender, IZeroconfHost e)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
foreach(var service in e.Services)
|
||||||
|
{
|
||||||
|
if (service.Key.Equals(TCPServiceType))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Found {e.DisplayName} : {e.IPAddress} : {service.Value.Port}");
|
||||||
|
|
||||||
|
QServer server = getServerFromAddress(e.IPAddress);
|
||||||
|
|
||||||
|
if(server == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("New Server Found so adding");
|
||||||
|
QServer serverToAdd = new QServer(e.IPAddress, service.Value.Port, e.DisplayName);
|
||||||
|
servers.Add(serverToAdd);
|
||||||
|
serverToAdd.refreshWorkspaces();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
server.name = e.DisplayName;
|
||||||
|
Console.WriteLine("Server already exists in list so just updating name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public QServer getServerFromAddress(string address)
|
||||||
|
{
|
||||||
|
QServer foundServer = null;
|
||||||
|
foreach(var server in servers)
|
||||||
|
{
|
||||||
|
if (server.host.Equals(address))
|
||||||
|
{
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using SharpOSC;
|
||||||
|
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QClient
|
||||||
|
{
|
||||||
|
TCPClient tcpClient;
|
||||||
|
|
||||||
|
public class WorkspacesUpdatedArgs : EventArgs
|
||||||
|
{
|
||||||
|
public List<QWorkspaceInfo> Workspaces { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public delegate void WorkspacesUpdatedHandler(object source, WorkspacesUpdatedArgs args);
|
||||||
|
public event WorkspacesUpdatedHandler WorkspacesUpdated;
|
||||||
|
|
||||||
|
public QClient(string host, int port)
|
||||||
|
{
|
||||||
|
tcpClient = new TCPClient(host, port);
|
||||||
|
tcpClient.Connect();
|
||||||
|
Console.WriteLine($"QClient setup {host}:{port}");
|
||||||
|
tcpClient.MessageReceived += ResponseReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResponseReceived(object source, MessageEventArgs args)
|
||||||
|
{
|
||||||
|
OscMessage msg = args.Message;
|
||||||
|
foreach (var obj in msg.Arguments)
|
||||||
|
{
|
||||||
|
QResponse response = JsonConvert.DeserializeObject<QResponse>(obj.ToString());
|
||||||
|
|
||||||
|
switch (response.getReplyType())
|
||||||
|
{
|
||||||
|
case "WORKSPACES":
|
||||||
|
Console.WriteLine("WORKSPACES RECEIVED");
|
||||||
|
OnWorkspacesUpdated(response);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(string address, params object[] args)
|
||||||
|
{
|
||||||
|
tcpClient.Send(new OscMessage(address, args));
|
||||||
|
Console.WriteLine($"QClient send message {address}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnWorkspacesUpdated(QResponse response)
|
||||||
|
{
|
||||||
|
if(WorkspacesUpdated != null)
|
||||||
|
{
|
||||||
|
List<QWorkspaceInfo> workspaces = new List<QWorkspaceInfo>();
|
||||||
|
foreach (var item in response.data)
|
||||||
|
{
|
||||||
|
QWorkspaceInfo workspacefound = JsonConvert.DeserializeObject<QWorkspaceInfo>(item.ToString());
|
||||||
|
workspaces.Add(workspacefound);
|
||||||
|
Console.WriteLine(workspacefound.displayName);
|
||||||
|
}
|
||||||
|
WorkspacesUpdated(this, new WorkspacesUpdatedArgs { Workspaces = workspaces });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QCue
|
||||||
|
{
|
||||||
|
Dictionary<string,object> cueData = new Dictionary<string,object>();
|
||||||
|
public QCue()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public object propertyForKey(string key)
|
||||||
|
{
|
||||||
|
if (cueData.ContainsKey(key))
|
||||||
|
return cueData[key];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QResponse
|
||||||
|
{
|
||||||
|
private const string WORKSPACES_REGEX = @"/workspaces";
|
||||||
|
public string status { get; set; }
|
||||||
|
public string address { get; set; }
|
||||||
|
public object[] data { get; set; }
|
||||||
|
|
||||||
|
public string getReplyType()
|
||||||
|
{
|
||||||
|
Console.WriteLine($"QRESPONSE - Address to match: '{address}'");
|
||||||
|
Match m = Regex.Match(address, WORKSPACES_REGEX);
|
||||||
|
if (m.Success)
|
||||||
|
return "WORKSPACES";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//General information about a QLab Workspace
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QServer
|
||||||
|
{
|
||||||
|
private QClient qClient;
|
||||||
|
|
||||||
|
public string host;
|
||||||
|
public int port;
|
||||||
|
public string name;
|
||||||
|
List<QWorkspace> workspaces = new List<QWorkspace>();
|
||||||
|
|
||||||
|
public QServer(string host, int port, string name)
|
||||||
|
{
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
qClient = new QClient(host, port);
|
||||||
|
qClient.WorkspacesUpdated += OnWorkspacesUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorkspacesUpdated(object source, QClient.WorkspacesUpdatedArgs args)
|
||||||
|
{
|
||||||
|
foreach(var workspace in args.Workspaces)
|
||||||
|
{
|
||||||
|
QWorkspace existingWorkspace = workspaceWithID(workspace.uniqueID);
|
||||||
|
if(existingWorkspace == null)
|
||||||
|
{
|
||||||
|
QWorkspace workspaceToAdd = new QWorkspace(workspace, this);
|
||||||
|
workspaces.Add(workspaceToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshWorkspaces()
|
||||||
|
{
|
||||||
|
qClient.sendMessage("/workspaces");
|
||||||
|
}
|
||||||
|
|
||||||
|
public QWorkspace workspaceWithID(string uniqueID)
|
||||||
|
{
|
||||||
|
foreach(var workspace in workspaces)
|
||||||
|
{
|
||||||
|
if (workspace.getUniqueID().Equals(uniqueID))
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Zeroconf" Version="3.3.16" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//TODO: connect methods, cue property fetch methods, everything else
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QWorkspace
|
||||||
|
{
|
||||||
|
|
||||||
|
private QServer server;
|
||||||
|
private QClient client;
|
||||||
|
|
||||||
|
private string name;
|
||||||
|
private string uniqueID;
|
||||||
|
private bool connected;
|
||||||
|
private bool hasPasscode;
|
||||||
|
private string oscPrefix;
|
||||||
|
|
||||||
|
private void Init()
|
||||||
|
{
|
||||||
|
name = "";
|
||||||
|
uniqueID = "";
|
||||||
|
connected = false;
|
||||||
|
|
||||||
|
hasPasscode = false;
|
||||||
|
|
||||||
|
//TODO: add version
|
||||||
|
}
|
||||||
|
|
||||||
|
public QWorkspace(QWorkspaceInfo workspaceInfo, QServer server)
|
||||||
|
{
|
||||||
|
if(workspaceInfo.uniqueID.Length > 0)
|
||||||
|
{
|
||||||
|
uniqueID = workspaceInfo.uniqueID;
|
||||||
|
oscPrefix = workspacePrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO add version if it has one if no version then it is QLab 3?
|
||||||
|
updateWithWorkspaceInfo(workspaceInfo);
|
||||||
|
|
||||||
|
client = new QClient(server.host, server.port);
|
||||||
|
this.server = server;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool updateWithWorkspaceInfo(QWorkspaceInfo workspaceInfo) {
|
||||||
|
bool didUpdate = false;
|
||||||
|
if(workspaceInfo.displayName.Length > 0 && !workspaceInfo.displayName.Equals(this.name))
|
||||||
|
{
|
||||||
|
this.name = workspaceInfo.displayName;
|
||||||
|
didUpdate = true;
|
||||||
|
}
|
||||||
|
if (!workspaceInfo.hasPasscode.Equals(this.hasPasscode))
|
||||||
|
{
|
||||||
|
this.hasPasscode = workspaceInfo.hasPasscode;
|
||||||
|
didUpdate = true;
|
||||||
|
}
|
||||||
|
return didUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public string getUniqueID()
|
||||||
|
{
|
||||||
|
return uniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region OSC address helpers
|
||||||
|
private string addressForCue(QCue cue, string action)
|
||||||
|
{
|
||||||
|
return $"{oscPrefix}/cue_id/{cue.propertyForKey("uniqueId")}/{action}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string addressForWildcardNumber(string number, string action) {
|
||||||
|
return $"{oscPrefix}/cue/{number}/{action}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string workspacePrefix()
|
||||||
|
{
|
||||||
|
return $"/workspace/{uniqueID}";
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Workspace Methods
|
||||||
|
public void disconnectFromWorkspace() { client.sendMessage($"{oscPrefix}/disconnect"); }
|
||||||
|
public void startReceivingUpdates() { client.sendMessage($"{oscPrefix}/updates", 1); }
|
||||||
|
public void stopReceivingUpdates() { client.sendMessage($"{oscPrefix}/updates", 0); }
|
||||||
|
public void enableAlwaysReply() { client.sendMessage($"{oscPrefix}/alwaysReply", 1); }
|
||||||
|
public void disableAlwaysReply() { client.sendMessage($"{oscPrefix}/alwaysReply", 0); }
|
||||||
|
|
||||||
|
public void fetchQLabVersion() {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fetchCueLists()
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fetchPlaybackPositionForCue()
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void go() { client.sendMessage($"{oscPrefix}/go"); }
|
||||||
|
public void save() { client.sendMessage($"{oscPrefix}/save"); }
|
||||||
|
public void undo() { client.sendMessage($"{oscPrefix}/undo"); }
|
||||||
|
public void redo() { client.sendMessage($"{oscPrefix}/redo"); }
|
||||||
|
public void resetAll() { client.sendMessage($"{oscPrefix}/reset"); }
|
||||||
|
public void pauseAll() { client.sendMessage($"{oscPrefix}/pause"); }
|
||||||
|
public void resumeAll() { client.sendMessage($"{oscPrefix}/resume"); }
|
||||||
|
public void stopAll() { client.sendMessage($"{oscPrefix}/stop"); }
|
||||||
|
public void panicAll() { client.sendMessage($"{oscPrefix}/panic"); }
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//General information about a QLab Workspace
|
||||||
|
namespace QSharp
|
||||||
|
{
|
||||||
|
public class QWorkspaceInfo
|
||||||
|
{
|
||||||
|
public string version { get; set; }
|
||||||
|
public string displayName { get; set; }
|
||||||
|
public string uniqueID { get; set; }
|
||||||
|
public bool hasPasscode { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
internal static class Extensions
|
||||||
|
{
|
||||||
|
public static int FirstIndexAfter<T>(this IEnumerable<T> items, int start, Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
if (items == null) throw new ArgumentNullException("items");
|
||||||
|
if (predicate == null) throw new ArgumentNullException("predicate");
|
||||||
|
if (start >= items.Count()) throw new ArgumentOutOfRangeException("start");
|
||||||
|
|
||||||
|
int retVal = 0;
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (retVal >= start && predicate(item)) return retVal;
|
||||||
|
retVal++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<List<T>> Split<T>(this IEnumerable<T> data, Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
var output = new List<List<T>>();
|
||||||
|
var curr = new List<T>();
|
||||||
|
output.Add(curr);
|
||||||
|
foreach (var x in data)
|
||||||
|
{
|
||||||
|
if (predicate(x))
|
||||||
|
{
|
||||||
|
curr = new List<T>();
|
||||||
|
output.Add(curr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
curr.Add(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static T[] SubArray<T>(this T[] data, int index, int length)
|
||||||
|
{
|
||||||
|
T[] result = new T[length];
|
||||||
|
Array.Copy(data, index, result, 0, length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+64
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public struct Midi
|
||||||
|
{
|
||||||
|
public byte Port;
|
||||||
|
public byte Status;
|
||||||
|
public byte Data1;
|
||||||
|
public byte Data2;
|
||||||
|
|
||||||
|
public Midi(byte port, byte status, byte data1, byte data2)
|
||||||
|
{
|
||||||
|
this.Port = port;
|
||||||
|
this.Status = status;
|
||||||
|
this.Data1 = data1;
|
||||||
|
this.Data2 = data2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(Midi))
|
||||||
|
{
|
||||||
|
if (this.Port == ((Midi)obj).Port && this.Status == ((Midi)obj).Status && this.Data1 == ((Midi)obj).Data1 && this.Data2 == ((Midi)obj).Data2)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(byte[]))
|
||||||
|
{
|
||||||
|
if (this.Port == ((byte[])obj)[0] && this.Status == ((byte[])obj)[1] && this.Data1 == ((byte[])obj)[2] && this.Data2 == ((byte[])obj)[3])
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Midi a, Midi b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Midi a, Midi b)
|
||||||
|
{
|
||||||
|
if (!a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (Port << 24) + (Status << 16) + (Data1 << 8) + (Data2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class OscBundle : OscPacket
|
||||||
|
{
|
||||||
|
Timetag _timetag;
|
||||||
|
|
||||||
|
public UInt64 Timetag
|
||||||
|
{
|
||||||
|
get { return _timetag.Tag; }
|
||||||
|
set { _timetag.Tag = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime Timestamp
|
||||||
|
{
|
||||||
|
get { return _timetag.Timestamp; }
|
||||||
|
set { _timetag.Timestamp = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OscMessage> Messages;
|
||||||
|
|
||||||
|
public OscBundle(UInt64 timetag, params OscMessage[] args)
|
||||||
|
{
|
||||||
|
_timetag = new Timetag(timetag);
|
||||||
|
Messages = new List<OscMessage>();
|
||||||
|
Messages.AddRange(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] GetBytes()
|
||||||
|
{
|
||||||
|
string bundle = "#bundle";
|
||||||
|
int bundleTagLen = Utils.AlignedStringLength(bundle);
|
||||||
|
byte[] tag = setULong(_timetag.Tag);
|
||||||
|
|
||||||
|
List<byte[]> outMessages = new List<byte[]>();
|
||||||
|
foreach (OscMessage msg in Messages)
|
||||||
|
{
|
||||||
|
outMessages.Add(msg.GetBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = bundleTagLen + tag.Length + outMessages.Sum(x => x.Length + 4);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
byte[] output = new byte[len];
|
||||||
|
Encoding.UTF8.GetBytes(bundle).CopyTo(output, i);
|
||||||
|
i += bundleTagLen;
|
||||||
|
tag.CopyTo(output, i);
|
||||||
|
i += tag.Length;
|
||||||
|
|
||||||
|
foreach (byte[] msg in outMessages)
|
||||||
|
{
|
||||||
|
var size = setInt(msg.Length);
|
||||||
|
size.CopyTo(output, i);
|
||||||
|
i += size.Length;
|
||||||
|
|
||||||
|
msg.CopyTo(output, i);
|
||||||
|
i += msg.Length; // msg size is always a multiple of 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+160
@@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class OscMessage : OscPacket
|
||||||
|
{
|
||||||
|
public string Address;
|
||||||
|
public List<object> Arguments;
|
||||||
|
|
||||||
|
public OscMessage(string address, params object[] args)
|
||||||
|
{
|
||||||
|
this.Address = address;
|
||||||
|
Arguments = new List<object>();
|
||||||
|
Arguments.AddRange(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] GetBytes()
|
||||||
|
{
|
||||||
|
List<byte[]> parts = new List<byte[]>();
|
||||||
|
|
||||||
|
List<object> currentList = Arguments;
|
||||||
|
int ArgumentsIndex = 0;
|
||||||
|
|
||||||
|
string typeString = ",";
|
||||||
|
int i = 0;
|
||||||
|
while (i < currentList.Count)
|
||||||
|
{
|
||||||
|
var arg = currentList[i];
|
||||||
|
|
||||||
|
string type = (arg != null) ? arg.GetType().ToString() : "null";
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "System.Int32":
|
||||||
|
typeString += "i";
|
||||||
|
parts.Add(setInt((int)arg));
|
||||||
|
break;
|
||||||
|
case "System.Single":
|
||||||
|
if (float.IsPositiveInfinity((float)arg))
|
||||||
|
{
|
||||||
|
typeString += "I";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeString += "f";
|
||||||
|
parts.Add(setFloat((float)arg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "System.String":
|
||||||
|
typeString += "s";
|
||||||
|
parts.Add(setString((string)arg));
|
||||||
|
break;
|
||||||
|
case "System.Byte[]":
|
||||||
|
typeString += "b";
|
||||||
|
parts.Add(setBlob((byte[])arg));
|
||||||
|
break;
|
||||||
|
case "System.Int64":
|
||||||
|
typeString += "h";
|
||||||
|
parts.Add(setLong((Int64)arg));
|
||||||
|
break;
|
||||||
|
case "System.UInt64":
|
||||||
|
typeString += "t";
|
||||||
|
parts.Add(setULong((UInt64)arg));
|
||||||
|
break;
|
||||||
|
case "SharpOSC.Timetag":
|
||||||
|
typeString += "t";
|
||||||
|
parts.Add(setULong(((Timetag)arg).Tag));
|
||||||
|
break;
|
||||||
|
case "System.Double":
|
||||||
|
if (Double.IsPositiveInfinity((double)arg))
|
||||||
|
{
|
||||||
|
typeString += "I";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeString += "d";
|
||||||
|
parts.Add(setDouble((double)arg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "SharpOSC.Symbol":
|
||||||
|
typeString += "S";
|
||||||
|
parts.Add(setString(((Symbol)arg).Value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "System.Char":
|
||||||
|
typeString += "c";
|
||||||
|
parts.Add(setChar((char)arg));
|
||||||
|
break;
|
||||||
|
case "SharpOSC.RGBA":
|
||||||
|
typeString += "r";
|
||||||
|
parts.Add(setRGBA((RGBA)arg));
|
||||||
|
break;
|
||||||
|
case "SharpOSC.Midi":
|
||||||
|
typeString += "m";
|
||||||
|
parts.Add(setMidi((Midi)arg));
|
||||||
|
break;
|
||||||
|
case "System.Boolean":
|
||||||
|
typeString += ((bool)arg) ? "T" : "F";
|
||||||
|
break;
|
||||||
|
case "null":
|
||||||
|
typeString += "N";
|
||||||
|
break;
|
||||||
|
|
||||||
|
// This part handles arrays. It points currentList to the array and resets i
|
||||||
|
// The array is processed like normal and when it is finished we replace
|
||||||
|
// currentList back with Arguments and continue from where we left off
|
||||||
|
case "System.Object[]":
|
||||||
|
case "System.Collections.Generic.List`1[System.Object]":
|
||||||
|
if(arg.GetType() == typeof(object[]))
|
||||||
|
arg = ((object[])arg).ToList();
|
||||||
|
|
||||||
|
if (Arguments != currentList)
|
||||||
|
throw new Exception("Nested Arrays are not supported");
|
||||||
|
typeString += "[";
|
||||||
|
currentList = (List<object>)arg;
|
||||||
|
ArgumentsIndex = i;
|
||||||
|
i = 0;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("Unable to transmit values of type " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (currentList != Arguments && i == currentList.Count)
|
||||||
|
{
|
||||||
|
// End of array, go back to main Argument list
|
||||||
|
typeString += "]";
|
||||||
|
currentList = Arguments;
|
||||||
|
i = ArgumentsIndex+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int addressLen = (Address.Length == 0 || Address == null ) ? 0 : Utils.AlignedStringLength(Address);
|
||||||
|
int typeLen = Utils.AlignedStringLength(typeString);
|
||||||
|
|
||||||
|
int total = addressLen + typeLen + parts.Sum(x => x.Length);
|
||||||
|
|
||||||
|
byte[] output = new byte[total];
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
Encoding.UTF8.GetBytes(Address).CopyTo(output, i);
|
||||||
|
i += addressLen;
|
||||||
|
|
||||||
|
Encoding.UTF8.GetBytes(typeString).CopyTo(output, i);
|
||||||
|
i += typeLen;
|
||||||
|
|
||||||
|
foreach (byte[] part in parts)
|
||||||
|
{
|
||||||
|
part.CopyTo(output, i);
|
||||||
|
i += part.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,492 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public abstract class OscPacket
|
||||||
|
{
|
||||||
|
public static OscPacket GetPacket(byte[] OscData)
|
||||||
|
{
|
||||||
|
if (OscData[0] == '#')
|
||||||
|
return parseBundle(OscData);
|
||||||
|
else
|
||||||
|
return parseMessage(OscData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract byte[] GetBytes();
|
||||||
|
|
||||||
|
#region Parse OSC packages
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes in an OSC bundle package in byte form and parses it into a more usable OscBundle object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns>Message containing various arguments and an address</returns>
|
||||||
|
private static OscMessage parseMessage(byte[] msg)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
//Log.Debug("Raw ASCII DATA: " + System.Text.Encoding.ASCII.GetString(msg));
|
||||||
|
//Log.Debug("Raw UTF-8 DATA: " + System.Text.Encoding.UTF8.GetString(msg));
|
||||||
|
string address = null;
|
||||||
|
char[] types = new char[0];
|
||||||
|
List<object> arguments = new List<object>();
|
||||||
|
List<object> mainArray = arguments; // used as a reference when we are parsing arrays to get the main array back
|
||||||
|
// Get address
|
||||||
|
address = getAddress(msg, index);
|
||||||
|
index += msg.FirstIndexAfter(address.Length, x => x == ',');
|
||||||
|
|
||||||
|
if (index % 4 != 0)
|
||||||
|
throw new Exception("Misaligned OSC Packet data. Address string is not padded correctly and does not align to 4 byte interval");
|
||||||
|
|
||||||
|
// Get type tags
|
||||||
|
types = getTypes(msg, index);
|
||||||
|
index += types.Length;
|
||||||
|
|
||||||
|
while (index % 4 != 0)
|
||||||
|
index++;
|
||||||
|
|
||||||
|
bool commaParsed = false;
|
||||||
|
|
||||||
|
foreach (char type in types)
|
||||||
|
{
|
||||||
|
// skip leading comma
|
||||||
|
if (type == ',' && !commaParsed)
|
||||||
|
{
|
||||||
|
commaParsed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case ('\0'):
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('i'):
|
||||||
|
int intVal = getInt(msg, index);
|
||||||
|
arguments.Add(intVal);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('f'):
|
||||||
|
float floatVal = getFloat(msg, index);
|
||||||
|
arguments.Add(floatVal);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('s'):
|
||||||
|
string stringVal = getString(msg, index);
|
||||||
|
arguments.Add(stringVal);
|
||||||
|
index += stringVal.Length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case('b'):
|
||||||
|
byte[] blob = getBlob(msg, index);
|
||||||
|
arguments.Add(blob);
|
||||||
|
index += 4 + blob.Length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('h'):
|
||||||
|
Int64 hval = getLong(msg, index);
|
||||||
|
arguments.Add(hval);
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('t'):
|
||||||
|
UInt64 sval = getULong(msg, index);
|
||||||
|
arguments.Add(new Timetag(sval));
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('d'):
|
||||||
|
double dval = getDouble(msg, index);
|
||||||
|
arguments.Add(dval);
|
||||||
|
index += 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('S'):
|
||||||
|
string SymbolVal = getString(msg, index);
|
||||||
|
arguments.Add(new Symbol(SymbolVal));
|
||||||
|
index += SymbolVal.Length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('c'):
|
||||||
|
char cval = getChar(msg, index);
|
||||||
|
arguments.Add(cval);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('r'):
|
||||||
|
RGBA rgbaval = getRGBA(msg, index);
|
||||||
|
arguments.Add(rgbaval);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('m'):
|
||||||
|
Midi midival = getMidi(msg, index);
|
||||||
|
arguments.Add(midival);
|
||||||
|
index += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('T'):
|
||||||
|
arguments.Add(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('F'):
|
||||||
|
arguments.Add(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('N'):
|
||||||
|
arguments.Add(null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('I'):
|
||||||
|
arguments.Add(double.PositiveInfinity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ('['):
|
||||||
|
if (arguments != mainArray)
|
||||||
|
throw new Exception("SharpOSC does not support nested arrays");
|
||||||
|
arguments = new List<object>(); // make arguments point to a new object array
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (']'):
|
||||||
|
mainArray.Add(arguments); // add the array to the main array
|
||||||
|
arguments = mainArray; // make arguments point back to the main array
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("OSC type tag '" + type + "' is unknown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index % 4 != 0)
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OscMessage(address, arguments.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes in an OSC bundle package in byte form and parses it into a more usable OscBundle object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bundle"></param>
|
||||||
|
/// <returns>Bundle containing elements and a timetag</returns>
|
||||||
|
private static OscBundle parseBundle(byte[] bundle)
|
||||||
|
{
|
||||||
|
UInt64 timetag;
|
||||||
|
List<OscMessage> messages = new List<OscMessage>();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
var bundleTag = Encoding.UTF8.GetString(bundle.SubArray(0, 8));
|
||||||
|
index += 8;
|
||||||
|
|
||||||
|
timetag = getULong(bundle, index);
|
||||||
|
index += 8;
|
||||||
|
|
||||||
|
if (bundleTag != "#bundle\0")
|
||||||
|
throw new Exception("Not a bundle");
|
||||||
|
|
||||||
|
while (index < bundle.Length)
|
||||||
|
{
|
||||||
|
int size = getInt(bundle, index);
|
||||||
|
index += 4;
|
||||||
|
|
||||||
|
byte[] messageBytes = bundle.SubArray(index, size);
|
||||||
|
var message = parseMessage(messageBytes);
|
||||||
|
|
||||||
|
messages.Add(message);
|
||||||
|
|
||||||
|
index += size;
|
||||||
|
while (index % 4 != 0)
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
OscBundle output = new OscBundle(timetag, messages.ToArray());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get arguments from byte array
|
||||||
|
|
||||||
|
private static string getAddress(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int i = index;
|
||||||
|
string address = "";
|
||||||
|
for (; i < msg.Length; i += 4)
|
||||||
|
{
|
||||||
|
if (msg[i] == ',')
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
address = Encoding.UTF8.GetString(msg.SubArray(index, i - 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= msg.Length && address == null)
|
||||||
|
throw new Exception("no comma found");
|
||||||
|
|
||||||
|
return address.Replace("\0", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char[] getTypes(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int i = index + 4;
|
||||||
|
char[] types = null;
|
||||||
|
|
||||||
|
for (; i < msg.Length; i += 4)
|
||||||
|
{
|
||||||
|
if (msg[i - 1] == 0)
|
||||||
|
{
|
||||||
|
types = Encoding.UTF8.GetChars(msg.SubArray(index, i - index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types == null)
|
||||||
|
{
|
||||||
|
byte[] term = { 0 };
|
||||||
|
types = Encoding.UTF8.GetChars(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= msg.Length && types == null)
|
||||||
|
throw new Exception("No null terminator after type string");
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int val = (msg[index] << 24) + (msg[index + 1] << 16) + (msg[index + 2] << 8) + (msg[index + 3] << 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getFloat(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
byte[] reversed = new byte[4];
|
||||||
|
reversed[3] = msg[index];
|
||||||
|
reversed[2] = msg[index+1];
|
||||||
|
reversed[1] = msg[index+2];
|
||||||
|
reversed[0] = msg[index + 3];
|
||||||
|
float val = System.BitConverter.ToSingle(reversed, 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string getString(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
string output = null;
|
||||||
|
int i = index + 4;
|
||||||
|
for (; (i-1) < msg.Length; i += 4)
|
||||||
|
{
|
||||||
|
if (msg[i - 1] == 0)
|
||||||
|
{
|
||||||
|
output = Encoding.UTF8.GetString(msg.SubArray(index, i - index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (output == null)
|
||||||
|
{
|
||||||
|
byte[] term = { 0 };
|
||||||
|
output = Encoding.UTF8.GetString(term);
|
||||||
|
}
|
||||||
|
if (i >= msg.Length && output == null)
|
||||||
|
throw new Exception("No null terminator after type string");
|
||||||
|
|
||||||
|
return output.Replace("\0", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBlob(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
int size = getInt(msg, index);
|
||||||
|
return msg.SubArray(index + 4, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UInt64 getULong(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
UInt64 val = ((UInt64)msg[index] << 56) + ((UInt64)msg[index + 1] << 48) + ((UInt64)msg[index + 2] << 40) + ((UInt64)msg[index + 3] << 32)
|
||||||
|
+ ((UInt64)msg[index + 4] << 24) + ((UInt64)msg[index + 5] << 16) + ((UInt64)msg[index + 6] << 8) + ((UInt64)msg[index + 7] << 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Int64 getLong(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
byte[] var = new byte[8];
|
||||||
|
var[7] = msg[index];
|
||||||
|
var[6] = msg[index+1];
|
||||||
|
var[5] = msg[index+2];
|
||||||
|
var[4] = msg[index+3];
|
||||||
|
var[3] = msg[index+4];
|
||||||
|
var[2] = msg[index+5];
|
||||||
|
var[1] = msg[index+6];
|
||||||
|
var[0] = msg[index+7];
|
||||||
|
|
||||||
|
Int64 val = BitConverter.ToInt64(var, 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double getDouble(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
byte[] var = new byte[8];
|
||||||
|
var[7] = msg[index];
|
||||||
|
var[6] = msg[index + 1];
|
||||||
|
var[5] = msg[index + 2];
|
||||||
|
var[4] = msg[index + 3];
|
||||||
|
var[3] = msg[index + 4];
|
||||||
|
var[2] = msg[index + 5];
|
||||||
|
var[1] = msg[index + 6];
|
||||||
|
var[0] = msg[index + 7];
|
||||||
|
|
||||||
|
double val = BitConverter.ToDouble(var, 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char getChar(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
return (char)msg[index + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RGBA getRGBA(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
return new RGBA(msg[index], msg[index + 1], msg[index + 2], msg[index + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Midi getMidi(byte[] msg, int index)
|
||||||
|
{
|
||||||
|
return new Midi(msg[index], msg[index + 1], msg[index + 2], msg[index + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Create byte arrays for arguments
|
||||||
|
|
||||||
|
protected static byte[] setInt(int value)
|
||||||
|
{
|
||||||
|
byte[] msg = new byte[4];
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
msg[0] = bytes[3];
|
||||||
|
msg[1] = bytes[2];
|
||||||
|
msg[2] = bytes[1];
|
||||||
|
msg[3] = bytes[0];
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setFloat(float value)
|
||||||
|
{
|
||||||
|
byte[] msg = new byte[4];
|
||||||
|
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
msg[0] = bytes[3];
|
||||||
|
msg[1] = bytes[2];
|
||||||
|
msg[2] = bytes[1];
|
||||||
|
msg[3] = bytes[0];
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setString(string value)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
int len = bytes.Length + (4 - bytes.Length % 4);
|
||||||
|
if (len <= bytes.Length) len = len + 4;
|
||||||
|
|
||||||
|
byte[] msg = new byte[len];
|
||||||
|
bytes.CopyTo(msg, 0);
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setBlob(byte[] value)
|
||||||
|
{
|
||||||
|
int len = value.Length + 4;
|
||||||
|
len = len + (4 - len % 4);
|
||||||
|
|
||||||
|
byte[] msg = new byte[len];
|
||||||
|
byte[] size = setInt(value.Length);
|
||||||
|
size.CopyTo(msg, 0);
|
||||||
|
value.CopyTo(msg, 4);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setLong(Int64 value)
|
||||||
|
{
|
||||||
|
byte[] rev = BitConverter.GetBytes(value);
|
||||||
|
byte[] output = new byte[8];
|
||||||
|
output[0] = rev[7];
|
||||||
|
output[1] = rev[6];
|
||||||
|
output[2] = rev[5];
|
||||||
|
output[3] = rev[4];
|
||||||
|
output[4] = rev[3];
|
||||||
|
output[5] = rev[2];
|
||||||
|
output[6] = rev[1];
|
||||||
|
output[7] = rev[0];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setULong(UInt64 value)
|
||||||
|
{
|
||||||
|
byte[] rev = BitConverter.GetBytes(value);
|
||||||
|
byte[] output = new byte[8];
|
||||||
|
output[0] = rev[7];
|
||||||
|
output[1] = rev[6];
|
||||||
|
output[2] = rev[5];
|
||||||
|
output[3] = rev[4];
|
||||||
|
output[4] = rev[3];
|
||||||
|
output[5] = rev[2];
|
||||||
|
output[6] = rev[1];
|
||||||
|
output[7] = rev[0];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setDouble(double value)
|
||||||
|
{
|
||||||
|
byte[] rev = BitConverter.GetBytes(value);
|
||||||
|
byte[] output = new byte[8];
|
||||||
|
output[0] = rev[7];
|
||||||
|
output[1] = rev[6];
|
||||||
|
output[2] = rev[5];
|
||||||
|
output[3] = rev[4];
|
||||||
|
output[4] = rev[3];
|
||||||
|
output[5] = rev[2];
|
||||||
|
output[6] = rev[1];
|
||||||
|
output[7] = rev[0];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setChar(char value)
|
||||||
|
{
|
||||||
|
byte[] output = new byte[4];
|
||||||
|
output[0] = 0;
|
||||||
|
output[1] = 0;
|
||||||
|
output[2] = 0;
|
||||||
|
output[3] = (byte)value;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setRGBA(RGBA value)
|
||||||
|
{
|
||||||
|
byte[] output = new byte[4];
|
||||||
|
output[0] = value.R;
|
||||||
|
output[1] = value.G;
|
||||||
|
output[2] = value.B;
|
||||||
|
output[3] = value.A;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] setMidi(Midi value)
|
||||||
|
{
|
||||||
|
byte[] output = new byte[4];
|
||||||
|
output[0] = value.Port;
|
||||||
|
output[1] = value.Status;
|
||||||
|
output[2] = value.Data1;
|
||||||
|
output[3] = value.Data2;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+64
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public struct RGBA
|
||||||
|
{
|
||||||
|
public byte R;
|
||||||
|
public byte G;
|
||||||
|
public byte B;
|
||||||
|
public byte A;
|
||||||
|
|
||||||
|
public RGBA(byte red, byte green, byte blue, byte alpha)
|
||||||
|
{
|
||||||
|
this.R = red;
|
||||||
|
this.G = green;
|
||||||
|
this.B = blue;
|
||||||
|
this.A = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(RGBA))
|
||||||
|
{
|
||||||
|
if (this.R == ((RGBA)obj).R && this.G == ((RGBA)obj).G && this.B == ((RGBA)obj).B && this.A == ((RGBA)obj).A)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(byte[]))
|
||||||
|
{
|
||||||
|
if (this.R == ((byte[])obj)[0] && this.G == ((byte[])obj)[1] && this.B == ((byte[])obj)[2] && this.A == ((byte[])obj)[3])
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(RGBA a, RGBA b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(RGBA a, RGBA b)
|
||||||
|
{
|
||||||
|
if (!a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (R << 24) + (G << 16) + (B << 8) + (A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class Symbol
|
||||||
|
{
|
||||||
|
public string Value;
|
||||||
|
|
||||||
|
public Symbol()
|
||||||
|
{
|
||||||
|
Value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Symbol(string value)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override
|
||||||
|
public string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(Symbol))
|
||||||
|
{
|
||||||
|
if (this.Value == ((Symbol)obj).Value)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(string))
|
||||||
|
{
|
||||||
|
if (this.Value == ((string)obj))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Symbol a, Symbol b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Symbol a, Symbol b)
|
||||||
|
{
|
||||||
|
if (!a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Value.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class MessageEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public OscMessage Message
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TCPClient
|
||||||
|
{
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return _port; }
|
||||||
|
}
|
||||||
|
int _port;
|
||||||
|
|
||||||
|
public string Address
|
||||||
|
{
|
||||||
|
get { return _address; }
|
||||||
|
}
|
||||||
|
public delegate void MessageReceivedHandler(object source, MessageEventArgs args);
|
||||||
|
public event MessageReceivedHandler MessageReceived;
|
||||||
|
|
||||||
|
string _address;
|
||||||
|
TcpClient client;
|
||||||
|
|
||||||
|
byte END = 0xc0;
|
||||||
|
byte ESC = 0xdb;
|
||||||
|
byte ESC_END = 0xDC;
|
||||||
|
byte ESC_ESC = 0xDD;
|
||||||
|
|
||||||
|
|
||||||
|
public TCPClient(string address, int port)
|
||||||
|
{
|
||||||
|
_port = port;
|
||||||
|
_address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect()
|
||||||
|
{
|
||||||
|
client = new TcpClient(Address, Port);
|
||||||
|
Console.WriteLine($"TCPClient - connect called for <{Address}:{Port}>");
|
||||||
|
Thread receivingThread = new Thread(ReceiveLoop);
|
||||||
|
receivingThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(byte[] message)
|
||||||
|
{
|
||||||
|
byte[] slipData = SlipEncode(message);
|
||||||
|
NetworkStream netStream = client.GetStream();
|
||||||
|
netStream.Write(slipData.ToArray(), 0, slipData.ToArray().Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(OscPacket packet)
|
||||||
|
{
|
||||||
|
byte[] data = packet.GetBytes();
|
||||||
|
Send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveLoop()
|
||||||
|
{
|
||||||
|
while (client.Connected)
|
||||||
|
{
|
||||||
|
Receive();
|
||||||
|
}
|
||||||
|
Console.WriteLine("TCPClient - Receive Loop has exited for some reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive()
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
int num = random.Next(1000);
|
||||||
|
NetworkStream netStream = client.GetStream();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
netStream.ReadTimeout = 250;
|
||||||
|
List<byte> responseData = new List<byte>();
|
||||||
|
if (netStream.CanRead)
|
||||||
|
{
|
||||||
|
//var watch = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
byte[] buffer = new byte[256];
|
||||||
|
|
||||||
|
int bytesRead = 0;
|
||||||
|
int reads = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bytesRead = netStream.Read(buffer, 0, buffer.Length);
|
||||||
|
responseData.AddRange(buffer);
|
||||||
|
reads += 1;
|
||||||
|
Thread.Sleep(1);
|
||||||
|
//Log.Debug("Thread " + num + ": Bytes read: " + bytesRead + " - " + Encoding.UTF8.GetString(buffer));
|
||||||
|
} while (netStream.DataAvailable);
|
||||||
|
|
||||||
|
//Console.WriteLine("Raw TCP In: " + System.Text.Encoding.UTF8.GetString(responseData.ToArray()));
|
||||||
|
OscMessage response = (OscMessage)OscPacket.GetPacket(responseData.Skip(1).ToArray());
|
||||||
|
//watch.Stop();
|
||||||
|
//Console.WriteLine($"TCPCLient - message receive took {watch.ElapsedMilliseconds}ms and {reads} reads");
|
||||||
|
OnMessageReceived(response);
|
||||||
|
}
|
||||||
|
} catch(Exception e)
|
||||||
|
{
|
||||||
|
//Log.Debug("TCPSENDER - Receive Exception: " + e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] SlipEncode(byte[] data)
|
||||||
|
{
|
||||||
|
List<byte> slipData = new List<byte>();
|
||||||
|
|
||||||
|
byte[] esc_end = { ESC, ESC_END };
|
||||||
|
byte[] esc_esc = { ESC, ESC_ESC };
|
||||||
|
byte[] end = { END };
|
||||||
|
|
||||||
|
int length = data.Length;
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
if (data[i] == END)
|
||||||
|
{
|
||||||
|
slipData.AddRange(esc_end);
|
||||||
|
}
|
||||||
|
else if (data[i] == ESC)
|
||||||
|
{
|
||||||
|
slipData.AddRange(esc_esc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slipData.Add(data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slipData.AddRange(end);
|
||||||
|
return slipData.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
client.GetStream().Close();
|
||||||
|
client.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnMessageReceived(OscMessage msg)
|
||||||
|
{
|
||||||
|
if (MessageReceived != null)
|
||||||
|
MessageReceived(this, new MessageEventArgs() { Message = msg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+92
@@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public struct Timetag
|
||||||
|
{
|
||||||
|
public UInt64 Tag;
|
||||||
|
|
||||||
|
public DateTime Timestamp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Utils.TimetagToDateTime(Tag);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Tag = Utils.DateTimeToTimetag(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the fraction of a second in the timestamp. the double precision number is multiplied by 2^32
|
||||||
|
/// giving an accuracy down to about 230 picoseconds ( 1/(2^32) of a second)
|
||||||
|
/// </summary>
|
||||||
|
public double Fraction
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Utils.TimetagToFraction(Tag);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Tag = (Tag & 0xFFFFFFFF00000000) + (UInt32)(value * 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timetag(UInt64 value)
|
||||||
|
{
|
||||||
|
this.Tag = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Timetag(DateTime value)
|
||||||
|
{
|
||||||
|
Tag = 0;
|
||||||
|
this.Timestamp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(System.Object obj)
|
||||||
|
{
|
||||||
|
if (obj.GetType() == typeof(Timetag))
|
||||||
|
{
|
||||||
|
if (this.Tag == ((Timetag)obj).Tag)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (obj.GetType() == typeof(UInt64))
|
||||||
|
{
|
||||||
|
if (this.Tag == ((UInt64)obj))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Timetag a, Timetag b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Timetag a, Timetag b)
|
||||||
|
{
|
||||||
|
if (a.Equals(b))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (int)( ((uint)(Tag >> 32) + (uint)(Tag & 0x00000000FFFFFFFF)) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public delegate void HandleOscPacket(OscPacket packet);
|
||||||
|
public delegate void HandleBytePacket(byte[] packet);
|
||||||
|
|
||||||
|
public class UDPListener : IDisposable
|
||||||
|
{
|
||||||
|
public int Port { get; private set; }
|
||||||
|
|
||||||
|
object callbackLock;
|
||||||
|
|
||||||
|
UdpClient receivingUdpClient;
|
||||||
|
IPEndPoint RemoteIpEndPoint;
|
||||||
|
|
||||||
|
HandleBytePacket BytePacketCallback = null;
|
||||||
|
HandleOscPacket OscPacketCallback = null;
|
||||||
|
|
||||||
|
Queue<byte[]> queue;
|
||||||
|
ManualResetEvent ClosingEvent;
|
||||||
|
|
||||||
|
public UDPListener(int port)
|
||||||
|
{
|
||||||
|
Port = port;
|
||||||
|
queue = new Queue<byte[]>();
|
||||||
|
ClosingEvent = new ManualResetEvent(false);
|
||||||
|
callbackLock = new object();
|
||||||
|
|
||||||
|
// try to open the port 10 times, else fail
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
receivingUdpClient = new UdpClient(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Failed in ten tries, throw the exception and give up
|
||||||
|
if (i >= 9)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
Thread.Sleep(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
|
||||||
|
// setup first async event
|
||||||
|
AsyncCallback callBack = new AsyncCallback(ReceiveCallback);
|
||||||
|
receivingUdpClient.BeginReceive(callBack, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UDPListener(int port, HandleOscPacket callback) : this(port)
|
||||||
|
{
|
||||||
|
this.OscPacketCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UDPListener(int port, HandleBytePacket callback) : this(port)
|
||||||
|
{
|
||||||
|
this.BytePacketCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReceiveCallback(IAsyncResult result)
|
||||||
|
{
|
||||||
|
Monitor.Enter(callbackLock);
|
||||||
|
Byte[] bytes = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytes = receivingUdpClient.EndReceive(result, ref RemoteIpEndPoint);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException e)
|
||||||
|
{
|
||||||
|
// Ignore if disposed. This happens when closing the listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process bytes
|
||||||
|
if (bytes != null && bytes.Length > 0)
|
||||||
|
{
|
||||||
|
if (BytePacketCallback != null)
|
||||||
|
{
|
||||||
|
BytePacketCallback(bytes);
|
||||||
|
}
|
||||||
|
else if (OscPacketCallback != null)
|
||||||
|
{
|
||||||
|
OscPacket packet = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
packet = OscPacket.GetPacket(bytes);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.ToString());
|
||||||
|
// If there is an error reading the packet, null is sent to the callback
|
||||||
|
}
|
||||||
|
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
|
||||||
|
OscPacketCallback(packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
queue.Enqueue(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closing)
|
||||||
|
ClosingEvent.Set();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Setup next async event
|
||||||
|
AsyncCallback callBack = new AsyncCallback(ReceiveCallback);
|
||||||
|
receivingUdpClient.BeginReceive(callBack, null);
|
||||||
|
}
|
||||||
|
Monitor.Exit(callbackLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool closing = false;
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
lock (callbackLock)
|
||||||
|
{
|
||||||
|
ClosingEvent.Reset();
|
||||||
|
closing = true;
|
||||||
|
receivingUdpClient.Close();
|
||||||
|
}
|
||||||
|
ClosingEvent.WaitOne();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public OscPacket Receive()
|
||||||
|
{
|
||||||
|
if (closing) throw new Exception("UDPListener has been closed.");
|
||||||
|
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count() > 0)
|
||||||
|
{
|
||||||
|
byte[] bytes = queue.Dequeue();
|
||||||
|
var packet = OscPacket.GetPacket(bytes);
|
||||||
|
//Log.Debug("Raw UDP In: " + System.Text.Encoding.ASCII.GetString(bytes));
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ReceiveBytes()
|
||||||
|
{
|
||||||
|
if (closing) throw new Exception("UDPListener has been closed.");
|
||||||
|
|
||||||
|
lock (queue)
|
||||||
|
{
|
||||||
|
if (queue.Count() > 0)
|
||||||
|
{
|
||||||
|
byte[] bytes = queue.Dequeue();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class UDPSender
|
||||||
|
{
|
||||||
|
public int Port
|
||||||
|
{
|
||||||
|
get { return _port; }
|
||||||
|
}
|
||||||
|
int _port;
|
||||||
|
|
||||||
|
public string Address
|
||||||
|
{
|
||||||
|
get { return _address; }
|
||||||
|
}
|
||||||
|
string _address;
|
||||||
|
|
||||||
|
IPEndPoint RemoteIpEndPoint;
|
||||||
|
Socket sock;
|
||||||
|
|
||||||
|
public UDPSender(string address, int port)
|
||||||
|
{
|
||||||
|
_port = port;
|
||||||
|
_address = address;
|
||||||
|
|
||||||
|
sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
|
||||||
|
var addresses = System.Net.Dns.GetHostAddresses(address);
|
||||||
|
if (addresses.Length == 0) throw new Exception("Unable to find IP address for " + address);
|
||||||
|
|
||||||
|
RemoteIpEndPoint = new IPEndPoint(addresses[0], port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(byte[] message)
|
||||||
|
{
|
||||||
|
sock.SendTo(message, RemoteIpEndPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(OscPacket packet)
|
||||||
|
{
|
||||||
|
byte[] data = packet.GetBytes();
|
||||||
|
Send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
sock.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpOSC
|
||||||
|
{
|
||||||
|
public class Utils
|
||||||
|
{
|
||||||
|
public static DateTime TimetagToDateTime(UInt64 val)
|
||||||
|
{
|
||||||
|
if (val == 1)
|
||||||
|
return DateTime.Now;
|
||||||
|
|
||||||
|
UInt32 seconds = (UInt32)(val >> 32);
|
||||||
|
var time = DateTime.Parse("1900-01-01 00:00:00");
|
||||||
|
time = time.AddSeconds(seconds);
|
||||||
|
var fraction = TimetagToFraction(val);
|
||||||
|
time = time.AddSeconds(fraction);
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double TimetagToFraction(UInt64 val)
|
||||||
|
{
|
||||||
|
if (val == 1)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
UInt32 seconds = (UInt32)(val & 0x00000000FFFFFFFF);
|
||||||
|
double fraction = (double)seconds / (UInt32)(0xFFFFFFFF);
|
||||||
|
return fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UInt64 DateTimeToTimetag(DateTime value)
|
||||||
|
{
|
||||||
|
UInt64 seconds = (UInt32)(value - DateTime.Parse("1900-01-01 00:00:00.000")).TotalSeconds;
|
||||||
|
UInt64 fraction = (UInt32)(0xFFFFFFFF * ((double)value.Millisecond / 1000));
|
||||||
|
|
||||||
|
UInt64 output = (seconds << 32) + fraction;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int AlignedStringLength(string val)
|
||||||
|
{
|
||||||
|
int len = val.Length + (4 - val.Length % 4);
|
||||||
|
if (len <= val.Length) len += 4;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using QSharp;
|
||||||
|
|
||||||
|
namespace qControlDemo
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("qControlDemo Started");
|
||||||
|
QBrowser browser = new QBrowser();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Thread.Sleep(100000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\QSharp\QSharp.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user