mirror of
https://github.com/jwetzell/QControlKit.git
synced 2026-08-08 00:43:51 +00:00
Rest of rename to QControlKit
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QServerFoundArgs : EventArgs
|
||||
{
|
||||
public QServer server { get; set; }
|
||||
}
|
||||
|
||||
public class QServerUpdatedArgs : EventArgs
|
||||
{
|
||||
public QServer server { get; set; }
|
||||
}
|
||||
|
||||
public class QWorkspacesUpdatedArgs : EventArgs
|
||||
{
|
||||
public List<QWorkspaceInfo> Workspaces { get; set; }
|
||||
}
|
||||
|
||||
public class QWorkspaceUpdatedArgs : EventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class QWorkspaceSettingsUpdatedArgs : EventArgs
|
||||
{
|
||||
public string settingsType { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class QWorkspaceLightDashboardUpdatedArgs : EventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class QWorkspaceConnectedArgs : EventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class QWorkspaceDisconnectedArgs : EventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class QWorkspaceConnectionErrorArgs : EventArgs
|
||||
{
|
||||
public string status { get; set; }
|
||||
}
|
||||
public class QQLabPreferencesUpdatedArgs : EventArgs
|
||||
{
|
||||
public string key { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class QCueUpdatedArgs : EventArgs
|
||||
{
|
||||
public string cueID { get; set; }
|
||||
public JToken data { get; set; }
|
||||
}
|
||||
|
||||
public class QCuePropertiesUpdatedArgs : EventArgs
|
||||
{
|
||||
public List<string> properties { get; set; }
|
||||
}
|
||||
|
||||
public class QCueListsUpdatedArgs : EventArgs
|
||||
{
|
||||
public JToken data { get; set; }
|
||||
}
|
||||
|
||||
public class QCueNeedsUpdatedArgs : EventArgs
|
||||
{
|
||||
public string cueID { get; set; }
|
||||
}
|
||||
|
||||
public class QCueListChangedPlaybackPositionArgs : EventArgs
|
||||
{
|
||||
public string cueListID { get; set; }
|
||||
public string cueID { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace QControlKit
|
||||
{
|
||||
public delegate void QServerFoundHandler(object source, QServerFoundArgs args);
|
||||
public delegate void QServerUpdatedHandler(object source, QServerUpdatedArgs args);
|
||||
|
||||
public delegate void QWorkspacesUpdatedHandler(object source, QWorkspacesUpdatedArgs args);
|
||||
|
||||
public delegate void QWorkspaceUpdatedHandler(object source, QWorkspaceUpdatedArgs args);
|
||||
public delegate void QWorkspaceSettingsUpdatedHandler(object source, QWorkspaceSettingsUpdatedArgs args);
|
||||
public delegate void QWorkspaceLightDashboardUpdatedHandler(object source, QWorkspaceLightDashboardUpdatedArgs args);
|
||||
public delegate void QWorkspaceConnectedHandler(object source, QWorkspaceConnectedArgs args);
|
||||
public delegate void QWorkspaceDisconnectedHandler(object source, QWorkspaceDisconnectedArgs args);
|
||||
public delegate void QWorkspaceConnectionErrorHandler(object source, QWorkspaceConnectionErrorArgs args);
|
||||
public delegate void QQLabPreferencesUpdatedHandler(object source, QQLabPreferencesUpdatedArgs args);
|
||||
|
||||
|
||||
public delegate void QCueUpdatedHandler(object source, QCueUpdatedArgs args);
|
||||
public delegate void QCuePropertiesUpdatedHandler(object source, QCuePropertiesUpdatedArgs args);
|
||||
public delegate void QCueListsUpdatesHandler(object source, QCueListsUpdatedArgs args);
|
||||
public delegate void QCueNeedsUpdatedHandler(object source, QCueNeedsUpdatedArgs args);
|
||||
public delegate void QCueListChangedPlaybackPositionHandler(object source, QCueListChangedPlaybackPositionArgs args);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<LastUsedPlatform>AnyCPU</LastUsedPlatform>
|
||||
<publishUrl>bin/Release/netstandard2.0/publish</publishUrl>
|
||||
<DeleteExistingFiles>false</DeleteExistingFiles>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<SelfContained>false</SelfContained>
|
||||
<_IsPortable>true</_IsPortable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,106 @@
|
||||
using Zeroconf;
|
||||
using System.Collections.ObjectModel;
|
||||
using Serilog;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QBrowser
|
||||
{
|
||||
|
||||
ZeroconfResolver.ResolverListener zeroconfTCPBrowser;
|
||||
|
||||
public ObservableCollection<QServer> servers = new ObservableCollection<QServer>();
|
||||
|
||||
public event QServerFoundHandler ServerFound;
|
||||
public event QServerUpdatedHandler ServerUpdatedWorkspaces;
|
||||
|
||||
public QBrowser()
|
||||
{
|
||||
zeroconfTCPBrowser = ZeroconfResolver.CreateListener(QBonjour.TCPService);
|
||||
zeroconfTCPBrowser.ServiceFound += ZeroconfHostFound;
|
||||
zeroconfTCPBrowser.ServiceLost += ZeroconfHostLost;
|
||||
}
|
||||
|
||||
private void ZeroconfHostLost(object sender, IZeroconfHost e)
|
||||
{
|
||||
Log.Information($"[browser] Lost {e.DisplayName} : {e.IPAddress}");
|
||||
|
||||
}
|
||||
|
||||
private void ZeroconfHostFound(object sender, IZeroconfHost e)
|
||||
{
|
||||
|
||||
|
||||
foreach(var service in e.Services)
|
||||
{
|
||||
if (service.Key.Equals(QBonjour.TCPService))
|
||||
{
|
||||
Log.Information($"Found {e.DisplayName} : {e.IPAddress} : {service.Value.Port}");
|
||||
|
||||
QServer server = serverForAddress(e.IPAddress);
|
||||
|
||||
if(server == null)
|
||||
{
|
||||
QServer serverToAdd = new QServer(e.IPAddress, service.Value.Port);
|
||||
serverToAdd.name = e.DisplayName;
|
||||
serverToAdd.zeroconfHost = e;
|
||||
servers.Add(serverToAdd);
|
||||
serverToAdd.refreshWorkspaces();
|
||||
serverToAdd.ServerUpdated += OnServerUpdatedWorkspace;
|
||||
OnServerFound(serverToAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
server.name = e.DisplayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public QServer serverForAddress(string address)
|
||||
{
|
||||
foreach(var server in servers)
|
||||
{
|
||||
if (server.host.Equals(address))
|
||||
{
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public QServer serverForIZeroconfHost(IZeroconfHost zeroconfHost)
|
||||
{
|
||||
foreach (var server in servers)
|
||||
{
|
||||
if (server.zeroconfHost == zeroconfHost)
|
||||
{
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual void OnServerFound(QServer server)
|
||||
{
|
||||
ServerFound?.Invoke(this, new QServerFoundArgs { server = server });
|
||||
}
|
||||
|
||||
protected virtual void OnServerUpdatedWorkspace(object source, QServerUpdatedArgs args)
|
||||
{
|
||||
ServerUpdatedWorkspaces?.Invoke(this, args);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
foreach(var server in servers)
|
||||
{
|
||||
server.disconnect();
|
||||
}
|
||||
zeroconfTCPBrowser.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SharpOSC;
|
||||
using Serilog;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QClient
|
||||
{
|
||||
TCPClient tcpClient;
|
||||
|
||||
public event QWorkspacesUpdatedHandler WorkspacesUpdated;
|
||||
|
||||
public event QCueUpdatedHandler CueUpdated;
|
||||
public event QCueListsUpdatesHandler CueListsUpdated;
|
||||
public event QCueNeedsUpdatedHandler CueNeedsUpdated;
|
||||
|
||||
public event QCueListChangedPlaybackPositionHandler CueListChangedPlaybackPosition;
|
||||
|
||||
public event QWorkspaceUpdatedHandler WorkspaceUpdated;
|
||||
public event QWorkspaceSettingsUpdatedHandler WorkspaceSettingsUpdated;
|
||||
|
||||
public event QWorkspaceLightDashboardUpdatedHandler WorkspaceLightDashboardUpdated;
|
||||
public event QQLabPreferencesUpdatedHandler QLabPreferencesUpdated;
|
||||
|
||||
public event QWorkspaceDisconnectedHandler WorkspaceDisconnected;
|
||||
public event QWorkspaceConnectedHandler WorkspaceConnected;
|
||||
public event QWorkspaceConnectionErrorHandler WorkspaceConnectionError;
|
||||
|
||||
public QClient(string host, int port)
|
||||
{
|
||||
tcpClient = new TCPClient(host, port);
|
||||
Log.Debug($"[client] setup connection to: <{host}:{port}>");
|
||||
tcpClient.MessageReceived += ProcessMessage;
|
||||
}
|
||||
|
||||
|
||||
public bool IsConnnected { get { return tcpClient.IsConnected; } }
|
||||
|
||||
public bool connect()
|
||||
{
|
||||
if (tcpClient == null)
|
||||
return false;
|
||||
else
|
||||
return tcpClient.Connect();
|
||||
}
|
||||
|
||||
public void disconnect()
|
||||
{
|
||||
tcpClient.Close();
|
||||
}
|
||||
|
||||
|
||||
public void sendMessage(string address, params object[] args)
|
||||
{
|
||||
tcpClient.Send(new OscMessage(address, args));
|
||||
Log.Debug($"[client] send message {address} : {args}");
|
||||
}
|
||||
|
||||
private void ProcessMessage(object source, MessageEventArgs args)
|
||||
{
|
||||
|
||||
QMessage message = new QMessage(args.Message);
|
||||
|
||||
if (message.IsReply)
|
||||
{
|
||||
JToken data = message.response;
|
||||
|
||||
//special case, want to update cue properties
|
||||
if (message.IsReplyFromCue)
|
||||
{
|
||||
if(data.Type == JTokenType.Object)
|
||||
{
|
||||
OnCueUpdated(message.cueID, data);
|
||||
}
|
||||
else if (data.Type == JTokenType.String || data.Type == JTokenType.Integer || data.Type == JTokenType.Float)
|
||||
{
|
||||
string property = message.AddressParts.Last();
|
||||
if (property == null)
|
||||
return;
|
||||
if (property == QOSCKey.PlaybackPositionId)
|
||||
{
|
||||
OnCueListChangedPlaybackPosition(message.cueID, data.ToString());
|
||||
return;
|
||||
}
|
||||
//create object manually since single value replies don't have dictionaries
|
||||
JObject properties = new JObject();
|
||||
properties.Add(property, data);
|
||||
|
||||
OnCueUpdated(message.cueID, properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"[client] unhandled reply from cue: Type: {data.Type} value: {message.response}");
|
||||
}
|
||||
|
||||
}
|
||||
else if (message.IsReplyFromCueLists)
|
||||
{
|
||||
OnCueListsUpdated(message.response);
|
||||
}
|
||||
else if (message.IsWorkspacesInfo)
|
||||
{
|
||||
OnWorkspacesUpdated(message);
|
||||
}
|
||||
else if (message.IsConnect)
|
||||
{
|
||||
if (message.response.ToString() == "ok")
|
||||
OnWorkspaceConnected();
|
||||
else
|
||||
OnWorkspaceConnectionError(message.response.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"[client] unhandled reply message: {message.address}");
|
||||
}
|
||||
}
|
||||
else if(message.IsUpdate) {
|
||||
if (message.IsCueUpdate)
|
||||
{
|
||||
OnCueNeedsUpdated(message.cueID);
|
||||
}
|
||||
else if (message.IsPlaybackPositionUpdate)
|
||||
{
|
||||
string cueListID = message.AddressParts[4];
|
||||
OnCueListChangedPlaybackPosition(cueListID, message.cueID);
|
||||
}
|
||||
else if (message.IsWorkspaceUpdate)
|
||||
{
|
||||
OnWorkspaceUpdated();
|
||||
}
|
||||
else if (message.IsWorkspaceSettingsUpdate)
|
||||
{
|
||||
string settingsType = message.AddressParts.Last();
|
||||
if (settingsType == null)
|
||||
return;
|
||||
OnWorkspaceSettingsUpdated(settingsType);
|
||||
}
|
||||
else if (message.IsLightDashboardUpdate)
|
||||
{
|
||||
//need to do checks for 4.2 or newer
|
||||
OnWorkspaceLightDashboardUpdated();
|
||||
}
|
||||
else if ( message.IsPreferencesUpdate)
|
||||
{
|
||||
//need to do checks for 4.2 or newer
|
||||
|
||||
string key = message.AddressParts.Last();
|
||||
if (key == null)
|
||||
return;
|
||||
OnQLabPreferencesUpdated(key);
|
||||
}
|
||||
else if (message.IsDisconnect)
|
||||
{
|
||||
Log.Debug($"[client] disconnect message received: {message.address}");
|
||||
OnWorkspaceDisconnected();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"[client] unhandled update message: {message.address}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"[client] unhandled message: {message.address}");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnCueUpdated(string cueID, JToken properties)
|
||||
{
|
||||
Log.Debug($"[client] cue updated: {cueID}");
|
||||
CueUpdated?.Invoke(this, new QCueUpdatedArgs { cueID = cueID, data = properties });
|
||||
}
|
||||
protected virtual void OnCueNeedsUpdated(string cueID)
|
||||
{
|
||||
Log.Debug($"[client] cue needs updated: {cueID}");
|
||||
CueNeedsUpdated?.Invoke(this, new QCueNeedsUpdatedArgs { cueID = cueID });
|
||||
}
|
||||
|
||||
protected virtual void OnCueListsUpdated(JToken response)
|
||||
{
|
||||
Log.Debug($"[client] Cue Lists Updated");
|
||||
CueListsUpdated?.Invoke(this, new QCueListsUpdatedArgs { data = response });
|
||||
}
|
||||
|
||||
protected virtual void OnCueListChangedPlaybackPosition(string cueListID, string cueID)
|
||||
{
|
||||
Log.Debug($"[client] CueList <{cueListID}> Playback Position Changed to <{cueID}>");
|
||||
CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = cueListID, cueID = cueID });
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspaceUpdated()
|
||||
{
|
||||
Log.Debug($"[client] Workspace Updated");
|
||||
WorkspaceUpdated?.Invoke(this, new QWorkspaceUpdatedArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspaceSettingsUpdated(string settingsType)
|
||||
{
|
||||
Log.Debug($"[client] Workspace Settings Updated");
|
||||
WorkspaceSettingsUpdated?.Invoke(this, new QWorkspaceSettingsUpdatedArgs { settingsType = settingsType });
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspaceLightDashboardUpdated()
|
||||
{
|
||||
Log.Debug($"[client] Workspace Light Dashboard Updated");
|
||||
WorkspaceLightDashboardUpdated?.Invoke(this, new QWorkspaceLightDashboardUpdatedArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnQLabPreferencesUpdated(string key)
|
||||
{
|
||||
Log.Debug($"[client] QLab Preferences Updated");
|
||||
QLabPreferencesUpdated?.Invoke(this, new QQLabPreferencesUpdatedArgs { key = key });
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspaceDisconnected()
|
||||
{
|
||||
Log.Debug($"[client] Workspace Disconnected");
|
||||
WorkspaceDisconnected?.Invoke(this, new QWorkspaceDisconnectedArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspaceConnected()
|
||||
{
|
||||
WorkspaceConnected?.Invoke(this, new QWorkspaceConnectedArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspaceConnectionError(string status)
|
||||
{
|
||||
WorkspaceConnectionError?.Invoke(this, new QWorkspaceConnectionErrorArgs { status = status });
|
||||
}
|
||||
|
||||
protected virtual void OnWorkspacesUpdated(QMessage message)
|
||||
{
|
||||
if(WorkspacesUpdated != null)
|
||||
{
|
||||
List<QWorkspaceInfo> workspaces = new List<QWorkspaceInfo>();
|
||||
foreach (var item in message.response)
|
||||
{
|
||||
QWorkspaceInfo workspacefound = JsonConvert.DeserializeObject<QWorkspaceInfo>(item.ToString());
|
||||
workspaces.Add(workspacefound);
|
||||
}
|
||||
WorkspacesUpdated(this, new QWorkspacesUpdatedArgs { Workspaces = workspaces });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QColor
|
||||
{
|
||||
public string name { get; set; }
|
||||
|
||||
public string Hex { get; set; }
|
||||
public string lightHex { get; set; }
|
||||
public string darkHex { get; set; }
|
||||
public QColor()
|
||||
{
|
||||
defaultColor();
|
||||
}
|
||||
|
||||
public QColor(string name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "red":
|
||||
redColor();
|
||||
break;
|
||||
case "orange":
|
||||
orangeColor();
|
||||
break;
|
||||
case "yellow":
|
||||
yellowColor();
|
||||
break;
|
||||
case "green":
|
||||
greenColor();
|
||||
break;
|
||||
case "blue":
|
||||
blueColor();
|
||||
break;
|
||||
case "indigo":
|
||||
indigoColor();
|
||||
break;
|
||||
case "purple":
|
||||
purpleColor();
|
||||
break;
|
||||
default:
|
||||
defaultColor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void defaultColor()
|
||||
{
|
||||
name = "default";
|
||||
lightHex = "#ACAAB2";
|
||||
Hex = "#8F8A99";
|
||||
darkHex = "#77737F";
|
||||
}
|
||||
|
||||
private void redColor()
|
||||
{
|
||||
name = "red";
|
||||
lightHex = "#FC563C";
|
||||
Hex = "#FC363B";
|
||||
darkHex = "#E31C24";
|
||||
}
|
||||
private void orangeColor()
|
||||
{
|
||||
name = "orange";
|
||||
lightHex = "#FFAA00";
|
||||
Hex = "#FF9500";
|
||||
darkHex = "#FF6A00";
|
||||
}
|
||||
private void yellowColor ()
|
||||
{
|
||||
name = "yellow";
|
||||
lightHex = "#FFF480";
|
||||
Hex = "#F7E519";
|
||||
darkHex = "#FFD500";
|
||||
}
|
||||
private void greenColor()
|
||||
{
|
||||
name = "green";
|
||||
lightHex = "#17E639";
|
||||
Hex = "#00CC22";
|
||||
darkHex = "#00B300";
|
||||
}
|
||||
|
||||
private void blueColor()
|
||||
{
|
||||
name = "blue";
|
||||
lightHex = "#5C73E6";
|
||||
Hex = "#415AD9";
|
||||
darkHex = "#2944CC";
|
||||
}
|
||||
private void indigoColor()
|
||||
{
|
||||
name = "purple";
|
||||
lightHex = "#3F388C";
|
||||
Hex = "#5A5499";
|
||||
darkHex = "#6262B3";
|
||||
}
|
||||
private void purpleColor()
|
||||
{
|
||||
name = "purple";
|
||||
lightHex = "#B500D9";
|
||||
Hex = "#9500B3";
|
||||
darkHex = "#730099";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<Authors>Joel Wetzell</Authors>
|
||||
<Version>1.0.0-devel</Version>
|
||||
<PackOnBuild>true</PackOnBuild>
|
||||
<PackageVersion>0.0.1</PackageVersion>
|
||||
<PackageId>QControlKit</PackageId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog" Version="2.9.0" />
|
||||
<PackageReference Include="Zeroconf" Version="3.4.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,862 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QCue
|
||||
{
|
||||
|
||||
public QWorkspace workspace;
|
||||
public Dictionary<string, object> cueData;
|
||||
public List<QCue> childCues;
|
||||
public Dictionary<string, QCue> childCuesUIDMap;
|
||||
|
||||
public int sortIndex;
|
||||
|
||||
private bool needsSortChildCues;//?
|
||||
private bool needsNotifyCueUpdated;//?
|
||||
|
||||
public bool ignoreUpdates;
|
||||
|
||||
public event QCuePropertiesUpdatedHandler CuePropertiesUpdated;
|
||||
|
||||
public QCue()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
public QCue(QWorkspace workspace)
|
||||
{
|
||||
init();
|
||||
this.workspace = workspace;
|
||||
}
|
||||
|
||||
public QCue(JToken dict, QWorkspace workspace)
|
||||
{
|
||||
init();
|
||||
this.workspace = workspace;
|
||||
|
||||
//this breaks things???
|
||||
//it definitely is in the original but somehow the recursion is not working as I expect..
|
||||
//but cue population still works without this as child cues are populated in a later
|
||||
|
||||
/*JToken children = dict[QOSCKey.Cues];
|
||||
if(children != null && children.Type == JTokenType.Array)
|
||||
{
|
||||
Log.Debug($"[cue] new cue being created with {children.Count()} childCues");
|
||||
foreach (var aChildDict in children)
|
||||
{
|
||||
string uid = (string)aChildDict[QOSCKey.UID];
|
||||
//Log.Debug($"[cue] childDict with uid: {uid} being processed");
|
||||
if (uid == null)
|
||||
continue;
|
||||
string name = dict[QOSCKey.UID].ToString();
|
||||
Log.Debug($"[cue] calling new cue from {name}");
|
||||
|
||||
//QCue child = new QCue(aChildDict, workspace);
|
||||
|
||||
*//*childCues.Add(child);
|
||||
childCuesUIDMap.Add(uid, child);*//*
|
||||
}
|
||||
}*/
|
||||
|
||||
updatePropertiesWithDictionary(dict);
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
cueData = new Dictionary<string, object> {
|
||||
{ QOSCKey.Flagged, false },
|
||||
{ QOSCKey.Armed, true },
|
||||
{ QOSCKey.PreWait, 0.0 },
|
||||
{ QOSCKey.PercentPreWaitElapsed, 0.0 },
|
||||
{ QOSCKey.PercentActionElapsed, 0.0 },
|
||||
{ QOSCKey.PostWait, 0.0 },
|
||||
{ QOSCKey.PercentPostWaitElapsed, 0.0 },
|
||||
{ QOSCKey.IsPanicking, false },
|
||||
{ QOSCKey.IsTailingOut, false },
|
||||
{ QOSCKey.IsRunning, false },
|
||||
{ QOSCKey.IsLoaded, false },
|
||||
{ QOSCKey.IsPaused, false },
|
||||
{ QOSCKey.IsBroken, false },
|
||||
{ QOSCKey.IsOverridden, false },
|
||||
{ QOSCKey.ContinueMode, 0 }
|
||||
};
|
||||
|
||||
childCues = new List<QCue>();
|
||||
childCuesUIDMap = new Dictionary<string, QCue>();
|
||||
}
|
||||
|
||||
public string description
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"(Cue: {this}) name: {name} [id:{uid} number:{number} type: {type}";
|
||||
}
|
||||
}
|
||||
|
||||
//isEqual?
|
||||
//hash?
|
||||
//compare?
|
||||
public bool isEqualToCue(QCue cue) { throw new NotImplementedException(); }
|
||||
public void setWorkspace(QWorkspace workspace)
|
||||
{
|
||||
if(this.workspace != workspace)
|
||||
{
|
||||
this.workspace = workspace;
|
||||
}
|
||||
}
|
||||
public void addChildCue(QCue cue)
|
||||
{
|
||||
string uid = propertyForKey(QOSCKey.UID).ToString();
|
||||
addChildCue(cue, uid);
|
||||
}
|
||||
public void addChildCue(QCue cue, string uid)
|
||||
{
|
||||
if (uid.Length == 0)
|
||||
return;
|
||||
childCues.Add(cue);
|
||||
childCuesUIDMap.Add(uid, cue);
|
||||
//some sorting of the childCues needs to be done?
|
||||
//reset sorting index
|
||||
}
|
||||
public void removeChildCue(QCue cue) { throw new NotImplementedException(); }
|
||||
public void removeAllChildCues() { throw new NotImplementedException(); }
|
||||
public void removeChildCuesWithIDs(List<string> uids) { throw new NotImplementedException(); }
|
||||
|
||||
//copied: yes
|
||||
//implemented: no
|
||||
#region Class Methods
|
||||
public string iconForType(string type) { throw new NotImplementedException(); }
|
||||
public List<string> fadeModeTitles() { throw new NotImplementedException(); }
|
||||
#endregion
|
||||
|
||||
//copied: yes
|
||||
//implemented: not all
|
||||
#region Convenience Accessors
|
||||
public List<QCue> cues
|
||||
{
|
||||
get
|
||||
{
|
||||
List<QCue> cues = (List<QCue>)propertyForKey(QOSCKey.Cues);
|
||||
if (cues == null)
|
||||
return new List<QCue>();
|
||||
return cues;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.Cues);
|
||||
}
|
||||
}
|
||||
public string parentID
|
||||
{
|
||||
get
|
||||
{
|
||||
return propertyForKey(QOSCKey.Parent).ToString();
|
||||
}
|
||||
}
|
||||
public string playbackPositionID {
|
||||
get {
|
||||
if (propertyForKey(QOSCKey.PlaybackPositionId) == null)
|
||||
return null;
|
||||
else
|
||||
return propertyForKey(QOSCKey.PlaybackPositionId).ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.PlaybackPositionId);
|
||||
}
|
||||
}
|
||||
public string name
|
||||
{
|
||||
get
|
||||
{
|
||||
return propertyForKey(QOSCKey.Name).ToString();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.Name);
|
||||
}
|
||||
}
|
||||
public string number
|
||||
{
|
||||
get
|
||||
{
|
||||
object num = propertyForKey(QOSCKey.Number);
|
||||
if (num != null)
|
||||
return num.ToString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.Number);
|
||||
}
|
||||
}
|
||||
public string uid
|
||||
{
|
||||
get
|
||||
{
|
||||
return propertyForKey(QOSCKey.UID).ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.UID);
|
||||
}
|
||||
}
|
||||
public string listName
|
||||
{
|
||||
get
|
||||
{
|
||||
return propertyForKey(QOSCKey.ListName).ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.ListName);
|
||||
}
|
||||
}
|
||||
public string type
|
||||
{
|
||||
get
|
||||
{
|
||||
return propertyForKey(QOSCKey.Type).ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.Type);
|
||||
}
|
||||
}
|
||||
public string notes
|
||||
{
|
||||
get
|
||||
{
|
||||
return propertyForKey(QOSCKey.Notes).ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.Notes);
|
||||
}
|
||||
}
|
||||
|
||||
//Check the bool casting on these?
|
||||
public bool IsFlagged
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)propertyForKey(QOSCKey.Flagged);
|
||||
}
|
||||
set
|
||||
{
|
||||
setProperty(value, QOSCKey.Flagged);
|
||||
}
|
||||
}
|
||||
public bool IsOverridden
|
||||
{
|
||||
get
|
||||
{
|
||||
if (workspace.connectedToQLab3 || workspace.isOlderThanVersion("4.2.0"))
|
||||
return false;
|
||||
|
||||
bool overridden = (bool)propertyForKey(QOSCKey.IsOverridden);
|
||||
|
||||
if (overridden)
|
||||
return true;
|
||||
foreach (var cue in cues)
|
||||
{
|
||||
if (cue.IsOverridden)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsBroken
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((bool)propertyForKey(QOSCKey.IsBroken))
|
||||
return true;
|
||||
foreach(var cue in cues)
|
||||
{
|
||||
if (cue.IsBroken)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
bool running = (bool)propertyForKey(QOSCKey.IsRunning);
|
||||
|
||||
if (running)
|
||||
return true;
|
||||
foreach(var cue in cues)
|
||||
{
|
||||
if (cue.IsRunning)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsTailingOut
|
||||
{
|
||||
get
|
||||
{
|
||||
if (workspace.connectedToQLab3)
|
||||
return false;
|
||||
|
||||
bool tailingOut = (bool)propertyForKey(QOSCKey.IsTailingOut);
|
||||
|
||||
if (tailingOut)
|
||||
return true;
|
||||
foreach(var cue in cues)
|
||||
{
|
||||
if (cue.IsTailingOut)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsPanicking
|
||||
{
|
||||
get
|
||||
{
|
||||
if (workspace.connectedToQLab3)
|
||||
return false;
|
||||
|
||||
bool panicking = (bool)propertyForKey(QOSCKey.IsPanicking);
|
||||
|
||||
if (panicking)
|
||||
return true;
|
||||
foreach (var cue in cues)
|
||||
{
|
||||
if (cue.IsPanicking)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsGroup
|
||||
{
|
||||
get
|
||||
{
|
||||
if (type.Equals(QCueType.Group))
|
||||
return true;
|
||||
if (type.Equals(QCueType.CueList))
|
||||
return true;
|
||||
if (type.Equals(QCueType.Cart))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsCueList
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( type.Equals(QCueType.CueList))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public string displayName
|
||||
{
|
||||
get
|
||||
{
|
||||
string number = this.number;
|
||||
if(number != null)
|
||||
{
|
||||
if (number.Length > 0)
|
||||
return $"{number} \u00b7 {nonEmptyName}";
|
||||
else
|
||||
return nonEmptyName;
|
||||
}
|
||||
else
|
||||
return nonEmptyName;
|
||||
}
|
||||
}
|
||||
public string nonEmptyName
|
||||
{
|
||||
get
|
||||
{
|
||||
string nonEmptyName;
|
||||
if (name != null && !name.Equals(""))
|
||||
nonEmptyName = name;
|
||||
else if (listName != null && !listName.Equals(""))
|
||||
nonEmptyName = listName;
|
||||
else
|
||||
nonEmptyName = $"Untitled {type} Cue";
|
||||
|
||||
return nonEmptyName;
|
||||
}
|
||||
}
|
||||
public string workspaceName{ get { return workspace.name; } }
|
||||
public double currentDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
//try v4 current duration key first
|
||||
if (propertyForKey(QOSCKey.CurrentDuration) != null)
|
||||
return (double)propertyForKey(QOSCKey.CurrentDuration);
|
||||
else
|
||||
return (double)propertyForKey(QOSCKey.Duration);
|
||||
}
|
||||
}
|
||||
public string audioFadeModeName
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO
|
||||
if (type.Equals(QCueType.Fade))
|
||||
return "";
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public string geoFadeModeName
|
||||
{
|
||||
get
|
||||
{
|
||||
//TODO
|
||||
if (type.Equals(QCueType.Fade))
|
||||
return "";
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public string surfaceName{
|
||||
get {
|
||||
object property = propertyForKey("surfaceName");
|
||||
if (property == null)
|
||||
return null;
|
||||
else
|
||||
return propertyForKey("surfaceName").ToString();
|
||||
}
|
||||
}
|
||||
public string patchName
|
||||
{
|
||||
get
|
||||
{
|
||||
object property = propertyForKey("patchDescription");
|
||||
if (property == null)
|
||||
return null;
|
||||
else
|
||||
return propertyForKey("patchDecription").ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public QColor color {
|
||||
get {
|
||||
return new QColor(colorName);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
colorName = value.name;
|
||||
}
|
||||
}
|
||||
|
||||
public string colorName
|
||||
{
|
||||
get
|
||||
{
|
||||
object col = propertyForKey(QOSCKey.ColorName);
|
||||
if (col != null)
|
||||
return col.ToString();
|
||||
else
|
||||
return "none";
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
setProperty("none", QOSCKey.ColorName);
|
||||
else
|
||||
setProperty(value, QOSCKey.ColorName);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO add quaternion property and setter
|
||||
|
||||
public Size surfaceSize() { throw new NotImplementedException(); }
|
||||
public Size cueSize() { throw new NotImplementedException(); }
|
||||
public List<string> availableSurfaceName() { throw new NotImplementedException(); }
|
||||
public List<string> propertyKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
return cueData.Keys.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public bool hasChildren
|
||||
{
|
||||
get
|
||||
{
|
||||
return cues.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public QCue firstCue
|
||||
{
|
||||
get
|
||||
{
|
||||
return cues.First();
|
||||
}
|
||||
}
|
||||
|
||||
public QCue lastCue
|
||||
{
|
||||
get
|
||||
{
|
||||
return cues.Last();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setIgnoreUpdates(bool ignoreUpdates)
|
||||
{
|
||||
if(this.ignoreUpdates != ignoreUpdates)
|
||||
{
|
||||
this.ignoreUpdates = ignoreUpdates;
|
||||
|
||||
if (!this.ignoreUpdates)
|
||||
{
|
||||
//TODO: Send Cue Needs Updated
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
//Copied: Yes
|
||||
//Implemented: Not all
|
||||
#region Update Methods
|
||||
public bool updatePropertiesWithDictionary(JToken dict)
|
||||
{
|
||||
bool cueUpdated = false;
|
||||
|
||||
//TODO
|
||||
JObject dictObj = (JObject)dict;
|
||||
List<string> propertiesUpdated = new List<string>();
|
||||
foreach (var obj in dictObj)
|
||||
{
|
||||
JToken value = obj.Value;
|
||||
if (obj.Key.Equals(QOSCKey.Cues))
|
||||
{
|
||||
if (value.Type != JTokenType.Array)
|
||||
continue;
|
||||
updateChildCuesWithPropertiesArray(value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool didSetProptery = setProperty(value, obj.Key, false);
|
||||
if (didSetProptery)
|
||||
{
|
||||
cueUpdated = true;
|
||||
propertiesUpdated.Add(obj.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cueUpdated)
|
||||
return false;
|
||||
|
||||
OnCuePropertiesUpdated(propertiesUpdated);
|
||||
return true;
|
||||
}
|
||||
|
||||
//enqueue updated notification?
|
||||
public bool updateChildCuesWithPropertiesArray(JToken value, bool removeUnused)
|
||||
{
|
||||
if (!workspace.connected)
|
||||
return false;
|
||||
|
||||
List<string> previousUids = null;
|
||||
if (removeUnused)
|
||||
previousUids = allChildCueUids();
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (var dict in value)
|
||||
{
|
||||
string uid = (string)dict[QOSCKey.UID];
|
||||
if (uid == null)
|
||||
continue;
|
||||
|
||||
if (removeUnused)
|
||||
previousUids.Remove(uid);
|
||||
|
||||
QCue child = cueWithID(uid, false);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
bool didUpdateProperties = updatePropertiesWithDictionary(dict);
|
||||
if (didUpdateProperties)
|
||||
needsNotifyCueUpdated = true;
|
||||
|
||||
if (child.sortIndex != index)
|
||||
{
|
||||
child.sortIndex = index;
|
||||
|
||||
needsSortChildCues = true;
|
||||
needsNotifyCueUpdated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
child = new QCue(dict, workspace);
|
||||
child.sortIndex = index;
|
||||
addChildCue(child, uid);
|
||||
needsNotifyCueUpdated = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return needsNotifyCueUpdated;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
//Methods Copied: Yes
|
||||
//Methods Implemented: Not all
|
||||
#region Children Cues
|
||||
public List<string> allChildCueUids()
|
||||
{
|
||||
List<string> uids = new List<string>(childCuesUIDMap.Keys);
|
||||
return uids;
|
||||
}
|
||||
public QCue cueAtIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= cues.Count)
|
||||
return null;
|
||||
return cues[index];
|
||||
}
|
||||
public QCue cueWithID(string uid)
|
||||
{
|
||||
return cueWithID(uid, true);
|
||||
}
|
||||
public QCue cueWithID(string uid, bool includeChildren)
|
||||
{
|
||||
if(childCuesUIDMap.ContainsKey(uid))
|
||||
return childCuesUIDMap[uid];
|
||||
|
||||
if (!includeChildren)
|
||||
return null;
|
||||
|
||||
foreach(var cue in cues)
|
||||
{
|
||||
if (!cue.IsGroup)
|
||||
continue;
|
||||
QCue childCue = cue.cueWithID(uid, includeChildren);
|
||||
if (childCue != null)
|
||||
return childCue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public QCue cueWithNumber(string number)
|
||||
{
|
||||
foreach(var cue in cues)
|
||||
{
|
||||
if (cue.number.Equals(number))
|
||||
return cue;
|
||||
if (cue.IsGroup)
|
||||
{
|
||||
QCue childCue = cue.cueWithNumber(number);
|
||||
if (childCue != null)
|
||||
return childCue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public object propertyForKey(string key)
|
||||
{
|
||||
//TODO: implerment special key checks color, surfaceName, patchDescription,
|
||||
if (key.Equals(QOSCKey.Cues))
|
||||
{
|
||||
return childCues;
|
||||
}
|
||||
else if (key.Equals("surfaceName"))
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
else if (key.Equals("patchDescription"))
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
else if (cueData.ContainsKey(key))
|
||||
{
|
||||
return cueData[key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool setProperty(object value, string key)
|
||||
{
|
||||
return setProperty( value, key, workspace.defaultSendUpdatesOSC);
|
||||
}
|
||||
|
||||
public bool setProperty(object value, string key, bool osc)
|
||||
{
|
||||
object existingValue = null;
|
||||
if(cueData.ContainsKey(key))
|
||||
existingValue = cueData[key];
|
||||
|
||||
if(existingValue != null)
|
||||
{
|
||||
if (existingValue == value || existingValue.Equals(value))
|
||||
return false;
|
||||
|
||||
|
||||
if (workspace.connectedToQLab3 && key.Equals(QOSCKey.Type) && existingValue.Equals(QCueType.CueList) && value.Equals(QCueType.Group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key.Equals(QOSCKey.Cues))
|
||||
{
|
||||
if (value.GetType() != typeof(List<QCue>))
|
||||
return false;
|
||||
|
||||
|
||||
Dictionary<string,QCue> newChildCuesUIDMap = new Dictionary<string, QCue>();
|
||||
int index = 0;
|
||||
string uid = null;
|
||||
|
||||
foreach(var aCue in (List<QCue>)value)
|
||||
{
|
||||
uid = aCue.uid;
|
||||
if (uid == null)
|
||||
continue;
|
||||
aCue.sortIndex = index;
|
||||
newChildCuesUIDMap.Add(uid, aCue);
|
||||
index++;
|
||||
}
|
||||
childCues = (List<QCue>)value;
|
||||
childCuesUIDMap = newChildCuesUIDMap;
|
||||
|
||||
}
|
||||
else if(key.Equals(QOSCKey.PlaybackPositionId))
|
||||
{
|
||||
if (!IsCueList)
|
||||
return false;
|
||||
if (value != null && value.GetType() != typeof(string))
|
||||
return false;
|
||||
|
||||
if (value.Equals("none"))
|
||||
value = null;
|
||||
if (value != null)
|
||||
cueData[key] = value;
|
||||
else
|
||||
cueData.Remove(key);
|
||||
|
||||
//TODO: Cuelistchangedplaybackpositionid?
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value != null)
|
||||
cueData[key] = value;
|
||||
else
|
||||
cueData.Remove(key);
|
||||
}
|
||||
|
||||
|
||||
if (key.Equals(QOSCKey.Type))
|
||||
{
|
||||
//TODO: something icon related?
|
||||
}
|
||||
|
||||
if (osc)
|
||||
{
|
||||
if (playbackPositionID == null && key.Equals(QOSCKey.PlaybackPositionId) && workspace.isOlderThanVersion("4.2.0"))
|
||||
value = "none";
|
||||
workspace.updatePropertySend(this, value, key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void sendAllPropertiesToQLab() {
|
||||
List<string> allProperties = propertyKeys;
|
||||
foreach(var key in allProperties)
|
||||
{
|
||||
object property = propertyForKey(key);
|
||||
if (key.Equals(QOSCKey.Cues))
|
||||
{
|
||||
foreach(var cue in (List<QCue>)property)
|
||||
{
|
||||
cue.sendAllPropertiesToQLab();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
workspace.updatePropertySend(this, property, key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//TODO: I don't think I'll do this one
|
||||
public void pullDownPropertyForKey(string key) { }
|
||||
|
||||
|
||||
//Methods Copied: Yes
|
||||
//Methods Implemented: Yes
|
||||
#region Actions
|
||||
public void start() { workspace.startCue(this); }
|
||||
public void stop() { workspace.stopCue(this); }
|
||||
public void pause() { workspace.pauseCue(this); }
|
||||
public void reset() { workspace.resetCue(this); }
|
||||
public void load() { workspace.loadCue(this); }
|
||||
public void resume() { workspace.resumeCue(this); }
|
||||
public void hardStop() { workspace.hardStopCue(this); }
|
||||
public void hardPause() { workspace.hardPauseCue(this); }
|
||||
public void togglePause() { workspace.togglePauseCue(this); }
|
||||
public void preview() { workspace.previewCue(this); }
|
||||
public void panic() { workspace.panicCue(this); }
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region Event Handling
|
||||
void OnCuePropertiesUpdated(List<string> properties)
|
||||
{
|
||||
Log.Debug($"[cue] <{nonEmptyName}> properties have been updated.");
|
||||
CuePropertiesUpdated?.Invoke(this, new QCuePropertiesUpdatedArgs { properties = properties });
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Printing
|
||||
public void Print()
|
||||
{
|
||||
Print(0);
|
||||
}
|
||||
public void Print(int level)
|
||||
{
|
||||
string indent = new string(' ', level*2);
|
||||
|
||||
Log.Information($"{indent}\u00b7{displayName}");
|
||||
if (IsGroup)
|
||||
{
|
||||
level++;
|
||||
|
||||
if (cues.Count() > 0)
|
||||
{
|
||||
foreach (var cue in cues)
|
||||
{
|
||||
cue.Print(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
namespace QControlKit
|
||||
{
|
||||
static class QBonjour
|
||||
{
|
||||
public const string TCPService = "_qlab._tcp.local.";
|
||||
public const string UDPService = "_qlab._udp.local.";
|
||||
}
|
||||
static class QCueType
|
||||
{
|
||||
public const string Cue = "Cue";
|
||||
public const string CueList = "Cue List";
|
||||
public const string Cart = "Cart";
|
||||
public const string Group = "Group";
|
||||
public const string Audio = "";
|
||||
public const string Mic = "Mic";
|
||||
public const string Video = "Video";
|
||||
public const string Camera = "Camera";
|
||||
public const string Text = "Text";
|
||||
public const string Light = "Light";
|
||||
public const string Fade = "Fade";
|
||||
public const string Network = "Network";
|
||||
public const string MIDI = "MIDI";
|
||||
public const string MIDIFile = "MIDI File";
|
||||
public const string Timecode = "Timecode";
|
||||
public const string Start = "Start";
|
||||
public const string Stop = "Stop";
|
||||
public const string Pause = "Paus";
|
||||
public const string Load = "Load";
|
||||
public const string Reset = "Reset";
|
||||
public const string Devamp = "Devamp";
|
||||
public const string Goto = "GoTo";
|
||||
public const string Target = "Target";
|
||||
public const string Arm = "Arm";
|
||||
public const string Disarm = "Disarm";
|
||||
public const string Wait = "Wait";
|
||||
public const string Memo = "Memo";
|
||||
public const string Script = "Script";
|
||||
public const string Stagetracker = "Stagetracker";
|
||||
|
||||
//v3
|
||||
public const string OSC = "OSC";
|
||||
public const string Titles = "Titles";
|
||||
|
||||
}
|
||||
|
||||
public static class QOSCKey
|
||||
{
|
||||
public const string UID = "uniqueID";
|
||||
public const string Type = "type";
|
||||
public const string Parent = "parent";
|
||||
public const string Name = "name";
|
||||
public const string Number = "number";
|
||||
public const string Notes = "notes";
|
||||
public const string FileTarget = "fileTarget";
|
||||
public const string CueTargetNumber = "cueTargetNumber";
|
||||
public const string CurrentCueTarget = "currentCueTarget";
|
||||
public const string ColorName = "colorName";
|
||||
public const string Flagged = "flagged";
|
||||
public const string Armed = "armed";
|
||||
public const string ContinueMode = "continueMode";
|
||||
public const string PreWait = "preWait";
|
||||
public const string PostWait = "postWait";
|
||||
public const string CurrentDuration = "currentDuration";
|
||||
public const string PercentPreWaitElapsed = "percentPreWaitElapsed";
|
||||
public const string PercentPostWaitElapsed = "percentPostWaitElapsed";
|
||||
public const string PercentActionElapsed = "percentActionElapsed";
|
||||
public const string PreWaitElapsed = "preWaitElapsed";
|
||||
public const string PostWaitElapsed = "postWaitElapsed";
|
||||
public const string ActionElapsed = "actionElapsed";
|
||||
public const string GroupMode = "mode";
|
||||
public const string CartPosition = "cartPosition";
|
||||
public const string CartRows = "cartRows";
|
||||
public const string CartColumns = "cartColumns";
|
||||
public const string HasFileTargets = "hasFileTargets";
|
||||
public const string HasCueTargets = "hasCueTargets";
|
||||
public const string AllowsEditingDuration = "allowsEditingDuration";
|
||||
public const string IsPanicking = "isPanicking";
|
||||
public const string IsTailingOut = "isTailingOut";
|
||||
public const string IsRunning = "isRunning";
|
||||
public const string IsLoaded = "isLoaded";
|
||||
public const string IsPaused = "isPaused";
|
||||
public const string IsBroken = "isBroken";
|
||||
public const string IsOverridden = "isOverridden";
|
||||
public const string TranslationX = "translationX";
|
||||
public const string TranslationY = "translationY";
|
||||
public const string ScaleX = "scaleX";
|
||||
public const string ScaleY = "scaleY";
|
||||
public const string OriginX = "originX";
|
||||
public const string OriginY = "originY";
|
||||
public const string Quaternion = "quaternion";
|
||||
public const string SurfaceSize = "surfaceSize";
|
||||
public const string CueSize = "cueSize";
|
||||
public const string PreserveAspectRatio = "preserveAspectRatio";
|
||||
public const string Layer = "layer";
|
||||
public const string Patch = "patch";
|
||||
public const string PatchList = "patchList";
|
||||
public const string SurfaceList = "surfaceList";
|
||||
public const string Cues = "cues";
|
||||
public const string ListName = "listName";
|
||||
public const string SurfaceID = "surfaceID";
|
||||
public const string FullSurface = "fullSurface";
|
||||
public const string Opacity = "opacity";
|
||||
public const string RotationZ = "rotationZ";
|
||||
public const string RotationY = "rotationY";
|
||||
public const string RotationX = "rotationX";
|
||||
public const string PlaybackPositionId = "playbackPositionId";
|
||||
public const string StartNextCueWhenSliceEnds = "startNextCueWhenSliceEnds";
|
||||
public const string StopTargetWhenSliceEnds = "stopTargetWhenSliceEnds";
|
||||
public const string SliderLevel = "sliderLevel";
|
||||
|
||||
// v3
|
||||
public const string Duration = "duration";
|
||||
public const string FullScreen = "fullScreen";
|
||||
}
|
||||
|
||||
static class QIdentifiers
|
||||
{
|
||||
// Identifiers for "fake" cues
|
||||
public const string RootCue = "__root__";
|
||||
public const string ActiveCues = "__active__";
|
||||
}
|
||||
|
||||
|
||||
//these should really be enums
|
||||
static class QFadeMode
|
||||
{
|
||||
public const int Absolute = 0;
|
||||
public const int Relative = 1;
|
||||
}
|
||||
|
||||
static class QContinueMode
|
||||
{
|
||||
public const int NoContinue = 0;
|
||||
public const int AutoContinue = 1;
|
||||
public const int AutoFollow = 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SharpOSC;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QMessage
|
||||
{
|
||||
|
||||
private OscMessage OSCMessage;
|
||||
|
||||
public QMessage(OscMessage oscMessage)
|
||||
{
|
||||
OSCMessage = oscMessage;
|
||||
}
|
||||
|
||||
public string description
|
||||
{
|
||||
get
|
||||
{
|
||||
//string join probably won't work might need to cast argument objects to strings first
|
||||
return $"address: {address}, arguments: {string.Join(" - ", arguments)}";
|
||||
}
|
||||
}
|
||||
|
||||
#region Message Type Checks
|
||||
public bool IsReply { get { return OSCMessage.Address.StartsWith("/reply"); } }
|
||||
|
||||
// /reply/cue_id/1/action
|
||||
public bool IsReplyFromCue { get{ return OSCMessage.Address.StartsWith("/reply/cue_id"); } }
|
||||
public bool IsReplyFromCueLists { get { return IsReply && OSCMessage.Address.EndsWith("cueLists"); } }
|
||||
|
||||
public bool IsUpdate { get{ return OSCMessage.Address.StartsWith("/update"); } }
|
||||
|
||||
public bool IsWorkspaceUpdate {
|
||||
get {
|
||||
// /update/workspace/{workspace_id}
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 3 && parts[1].Equals("workspace"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWorkspacesInfo {
|
||||
get {
|
||||
// /workspaces
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 2 && parts[1] == "workspaces");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWorkspaceSettingsUpdate {
|
||||
get {
|
||||
// /update/workspace/{workspace_id}/settings/{settings_controller}
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 5 && parts[1].Equals("workspace") && parts[3].Equals("settings"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLightDashboardUpdate {
|
||||
get {
|
||||
// /update/workspace/{workspace_id}/dashboard
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 4 && parts[1].Equals("workspace") && parts[3].Equals("dashboard"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCueUpdate {
|
||||
get {
|
||||
// /update/workspace/{workspace_id}/cue_id/{cue_id}
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 5 && parts[1].Equals("workspace") && parts[3].Equals("cue_id"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPlaybackPositionUpdate {
|
||||
get {
|
||||
// /update/workspace/{workspace_id}/cueList/{cue_list_id}/playbackPosition {cue_id}
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 6 && address.EndsWith("/playbackPosition"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPreferencesUpdate {
|
||||
get {
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 4 && parts[3].Equals("preferences"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDisconnect {
|
||||
get {
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 4 && parts[3].Equals("disconnect"));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConnect
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] parts = AddressParts;
|
||||
return (parts.Length == 4 && parts[3].Equals("connect"));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
//host method
|
||||
|
||||
public string address { get { return OSCMessage.Address; } }
|
||||
|
||||
public string replyAddress { get{ return IsReply ? address.Substring("/reply".Length) : address; } }
|
||||
|
||||
|
||||
public string[] AddressParts { get { return address.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries); } }
|
||||
|
||||
public JToken response
|
||||
{
|
||||
get
|
||||
{
|
||||
JObject responseObj = JObject.Parse((string)arguments[0]);
|
||||
return responseObj["data"];
|
||||
}
|
||||
}
|
||||
|
||||
public List<Object> arguments { get { return OSCMessage.Arguments; } }
|
||||
|
||||
public string cueID
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsCueUpdate)
|
||||
{
|
||||
return AddressParts[4];
|
||||
}else if (IsPlaybackPositionUpdate)
|
||||
{
|
||||
//TODO: check string cast
|
||||
return arguments.Count > 0 ? (string)arguments[0] : null;
|
||||
}else if (IsReplyFromCue)
|
||||
{
|
||||
return AddressParts[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//General information about a QLab Workspace
|
||||
using Serilog;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Zeroconf;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QServer
|
||||
{
|
||||
public event QServerUpdatedHandler ServerUpdated;
|
||||
|
||||
private QClient client;
|
||||
|
||||
public string host { get; set; }
|
||||
public int port { get; set; }
|
||||
public string name { get; set; }
|
||||
public IZeroconfHost zeroconfHost;
|
||||
public List<QWorkspace> workspaces = new List<QWorkspace>();
|
||||
|
||||
public QServer(string host, int port)
|
||||
{
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
|
||||
client = new QClient(host, port);
|
||||
client.WorkspacesUpdated += OnServerWorkspacesUpdated;
|
||||
|
||||
if (!client.connect())
|
||||
{
|
||||
Log.Error($"[server] unable to connect to QLab Server: {host}:{port}");
|
||||
}
|
||||
}
|
||||
|
||||
public string description { get { return $"{name} - {host} - {port}"; } }
|
||||
|
||||
|
||||
public void refreshWorkspaces()
|
||||
{
|
||||
client.sendMessage("/workspaces");
|
||||
}
|
||||
|
||||
public QWorkspace workspaceWithID(string uniqueID)
|
||||
{
|
||||
foreach(var workspace in workspaces)
|
||||
{
|
||||
if (workspace.uniqueID.Equals(uniqueID))
|
||||
return workspace;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region EventHandlers
|
||||
private void OnServerWorkspacesUpdated(object source, QWorkspacesUpdatedArgs args)
|
||||
{
|
||||
foreach (var workspace in args.Workspaces)
|
||||
{
|
||||
QWorkspace existingWorkspace = workspaceWithID(workspace.uniqueID);
|
||||
if (existingWorkspace == null)
|
||||
{
|
||||
QWorkspace workspaceToAdd = new QWorkspace(workspace, this);
|
||||
workspaces.Add(workspaceToAdd);
|
||||
}
|
||||
}
|
||||
OnServerUpdated(this);
|
||||
}
|
||||
|
||||
protected virtual void OnServerUpdated(QServer server)
|
||||
{
|
||||
ServerUpdated?.Invoke(this, new QServerUpdatedArgs { server = server });
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void disconnect()
|
||||
{
|
||||
foreach (var workspace in workspaces)
|
||||
{
|
||||
if (workspace.connected)
|
||||
{
|
||||
Log.Debug($"[server] Close Called For workspace: {workspace.name}");
|
||||
workspace.disconnect();
|
||||
}
|
||||
}
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
//TODO: connect methods, cue property fetch methods, everything else
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
|
||||
namespace QControlKit
|
||||
{
|
||||
public class QWorkspace
|
||||
{
|
||||
|
||||
private QServer server;
|
||||
private QClient client;
|
||||
|
||||
public string name { get; set; }
|
||||
public string uniqueID;
|
||||
public bool connected;
|
||||
public bool hasPasscode;
|
||||
private string passcode;
|
||||
|
||||
private QCue root;
|
||||
|
||||
private Timer heartbeatTimer;
|
||||
private int heartbeatAttempts;
|
||||
|
||||
public bool defaultSendUpdatesOSC;
|
||||
|
||||
public string version;
|
||||
|
||||
|
||||
public event QWorkspaceUpdatedHandler WorkspaceUpdated;
|
||||
public event QCueListChangedPlaybackPositionHandler CueListChangedPlaybackPosition;
|
||||
private void Init()
|
||||
{
|
||||
name = "";
|
||||
uniqueID = "";
|
||||
connected = false;
|
||||
passcode = "";
|
||||
hasPasscode = false;
|
||||
defaultSendUpdatesOSC = false;
|
||||
|
||||
root = new QCue(this);
|
||||
root.setProperty(QIdentifiers.RootCue, QOSCKey.UID, false);
|
||||
root.setProperty("Cue Lists", QOSCKey.Name, false);
|
||||
root.setProperty(QCueType.CueList, QOSCKey.Type, false);
|
||||
|
||||
if (version == null || version.Length <= 0)
|
||||
version = "3.0.0";
|
||||
}
|
||||
|
||||
public QWorkspace(QWorkspaceInfo workspaceInfo, QServer server)
|
||||
{
|
||||
|
||||
|
||||
|
||||
if (workspaceInfo.version.Length > 0)
|
||||
version = workspaceInfo.version;
|
||||
|
||||
Init();
|
||||
|
||||
if (workspaceInfo.uniqueID.Length > 0)
|
||||
uniqueID = workspaceInfo.uniqueID;
|
||||
|
||||
updateWithWorkspaceInfo(workspaceInfo);
|
||||
|
||||
client = new QClient(server.host, server.port);
|
||||
|
||||
client.WorkspaceConnected += OnWorkspaceConnected;
|
||||
client.WorkspaceConnectionError += OnWorkspaceConnectionError;
|
||||
client.WorkspaceDisconnected += OnWorkspaceDisconnected;
|
||||
client.CueListsUpdated += OnCueListsUpdated;
|
||||
client.CueListChangedPlaybackPosition += OnCueListChangedPlaybackPosition;
|
||||
client.CueNeedsUpdated += OnCueNeedsUpdated;
|
||||
client.CueUpdated += OnCueUpdated;
|
||||
|
||||
this.server = server;
|
||||
Log.Debug($"[workspace] <{name}> initizalied for server: {server.name} ");
|
||||
}
|
||||
|
||||
//updateWithDictionary
|
||||
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 description { get { return $"{name} : {uniqueID}"; } }
|
||||
|
||||
public bool isOlderThanVersion(string version)
|
||||
{
|
||||
var thisVersion = new Version(this.version);
|
||||
var otherVersion = new Version(version);
|
||||
return thisVersion.CompareTo(otherVersion) < 0;
|
||||
}
|
||||
|
||||
public bool isEqualToVersion(string version)
|
||||
{
|
||||
var thisVersion = new Version(this.version);
|
||||
var otherVersion = new Version(version);
|
||||
return thisVersion.CompareTo(otherVersion) == 0;
|
||||
}
|
||||
|
||||
public bool isNewerThanVersion(string version)
|
||||
{
|
||||
var thisVersion = new Version(this.version);
|
||||
var otherVersion = new Version(version);
|
||||
return thisVersion.CompareTo(otherVersion) > 0;
|
||||
}
|
||||
|
||||
public string nameWithoutPathExtension {
|
||||
get {
|
||||
if (name.EndsWith(".cues"))
|
||||
return name.Substring(0, name.Length - 5);
|
||||
else if (name.EndsWith(".qlab4"))
|
||||
return name.Substring(0, name.Length - 6);
|
||||
return name;
|
||||
}
|
||||
} //TODO
|
||||
|
||||
public string serverName { get { return server.name; } }
|
||||
|
||||
public string fullName { get { return $"{name} ({server.name})"; } }
|
||||
|
||||
public QCue firstCue { get { return firstCueList.firstCue; } }
|
||||
public QCue firstCueList { get { return root.firstCue; } }
|
||||
|
||||
public List<QCue> cueLists { get { return root.cues; } }
|
||||
public string fullNameWithCueList(QCue cueList) { return ""; }
|
||||
//TODO other convenience methods
|
||||
public string[] versionParts { get { return version.Split('.'); } }
|
||||
public bool connectedToQLab3 { get { return versionParts[0] == "3"; } }
|
||||
|
||||
#region Connection/reconnection
|
||||
|
||||
public void connectWithPasscode(string passcode)
|
||||
{
|
||||
//TODO
|
||||
if (!client.connect())
|
||||
{
|
||||
Log.Error($"[workspace] *** Error: couldn't connect to server client is not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
//save password for reuse
|
||||
this.passcode = passcode;
|
||||
Log.Information("[workspace] connecting...");
|
||||
client.sendMessage($"{workspacePrefix}/connect",passcode);
|
||||
}
|
||||
|
||||
private void finishConnection()
|
||||
{
|
||||
//TODO
|
||||
Log.Information($"[workspace] connected to <{name}> running on QLab version <{version}>");
|
||||
connected = true;
|
||||
startReceivingUpdates();
|
||||
//fetchQLabVersion();
|
||||
fetchCueLists();
|
||||
|
||||
}
|
||||
|
||||
public void reconnect()
|
||||
{
|
||||
if (connected)
|
||||
return;
|
||||
connectWithPasscode(passcode);
|
||||
|
||||
//todo
|
||||
}
|
||||
|
||||
public void disconnect()
|
||||
{
|
||||
Log.Information($"[workspace] disconnect: {name}");
|
||||
if (heartbeatTimer != null)
|
||||
stopHeartbeat();
|
||||
stopReceivingUpdates();
|
||||
disconnectFromWorkspace();
|
||||
|
||||
connected = false;
|
||||
client.disconnect();
|
||||
|
||||
//TODO
|
||||
//root.removeAllChildCues();
|
||||
|
||||
}
|
||||
|
||||
public void temporarilyDisconnect()
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cues
|
||||
public QCue cueWithID(string uid)
|
||||
{
|
||||
return root.cueWithID(uid);
|
||||
}
|
||||
|
||||
public QCue cueWithNumber(string number)
|
||||
{
|
||||
return root.cueWithNumber(number);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Workspace Methods
|
||||
public void disconnectFromWorkspace() { client.sendMessage($"{workspacePrefix}/disconnect"); }
|
||||
public void startReceivingUpdates() { client.sendMessage($"{workspacePrefix}/updates", 1); }
|
||||
public void stopReceivingUpdates() { client.sendMessage($"{workspacePrefix}/updates", 0); }
|
||||
public void enableAlwaysReply() { client.sendMessage($"{workspacePrefix}/alwaysReply", 1); }
|
||||
public void disableAlwaysReply() { client.sendMessage($"{workspacePrefix}/alwaysReply", 0); }
|
||||
public void fetchQLabVersion() { client.sendMessage($"{workspacePrefix}/version"); } //TODO: EventHandler for this
|
||||
public void fetchCueLists() { client.sendMessage($"{workspacePrefix}/cueLists"); } //TODO: EventHandler for CueListUpdated
|
||||
public void fetchPlaybackPositionForCue(QCue cue) { client.sendMessage(addressForCue(cue, QOSCKey.PlaybackPositionId)); } //EventHandler for this? can I use the CueListPlaybackPosition one?
|
||||
public void go() { client.sendMessage($"{workspacePrefix}/go"); }
|
||||
public void save() { client.sendMessage($"{workspacePrefix}/save"); }
|
||||
public void undo() { client.sendMessage($"{workspacePrefix}/undo"); }
|
||||
public void redo() { client.sendMessage($"{workspacePrefix}/redo"); }
|
||||
public void resetAll() { client.sendMessage($"{workspacePrefix}/reset"); }
|
||||
public void pauseAll() { client.sendMessage($"{workspacePrefix}/pause"); }
|
||||
public void resumeAll() { client.sendMessage($"{workspacePrefix}/resume"); }
|
||||
public void stopAll() { client.sendMessage($"{workspacePrefix}/stop"); }
|
||||
public void panicAll() { client.sendMessage($"{workspacePrefix}/panic"); }
|
||||
#endregion
|
||||
|
||||
#region Heartbeat
|
||||
//TODO
|
||||
public void startHeartbeat()
|
||||
{
|
||||
clearHeartbeatTimeout();
|
||||
sendHeartbeat();
|
||||
}
|
||||
|
||||
public void stopHeartbeat()
|
||||
{
|
||||
clearHeartbeatTimeout();
|
||||
heartbeatAttempts = -1;
|
||||
}
|
||||
|
||||
public void clearHeartbeatTimeout()
|
||||
{
|
||||
heartbeatTimer.Stop();
|
||||
heartbeatTimer = null;
|
||||
heartbeatAttempts = 0;
|
||||
//TODO
|
||||
}
|
||||
|
||||
public void sendHeartbeat()
|
||||
{
|
||||
client.sendMessage("/thump");
|
||||
//TODO
|
||||
}
|
||||
|
||||
public void heartbeatTimeout(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cue Actions
|
||||
public void startCue(QCue cue) { client.sendMessage(addressForCue(cue, "start")); }
|
||||
public void stopCue(QCue cue) { client.sendMessage(addressForCue(cue, "stop")); }
|
||||
public void pauseCue(QCue cue) { client.sendMessage(addressForCue(cue, "pause")); } //TODO: immediately update local for snappier whatever
|
||||
public void loadCue(QCue cue) { client.sendMessage(addressForCue(cue, "load")); }
|
||||
public void resetCue(QCue cue) { client.sendMessage(addressForCue(cue, "reset")); }
|
||||
public void deleteCue(QCue cue) { client.sendMessage(addressForCue(cue, "")); }
|
||||
public void resumeCue(QCue cue) { client.sendMessage(addressForCue(cue, "resume")); }
|
||||
public void hardStopCue(QCue cue) { client.sendMessage(addressForCue(cue, "hardStop")); }
|
||||
public void hardPauseCue(QCue cue) { client.sendMessage(addressForCue(cue, "hardPause")); } //TODO: immediately update local for snappier whatever
|
||||
public void togglePauseCue(QCue cue) { client.sendMessage(addressForCue(cue, "togglePause")); }
|
||||
public void previewCue(QCue cue) { client.sendMessage(addressForCue(cue, "preview")); }
|
||||
public void panicCue(QCue cue) { client.sendMessage(addressForCue(cue, "panic")); } //TODO: immediately update local for snappier whatever
|
||||
#endregion
|
||||
|
||||
#region Cue Getters/Setters
|
||||
|
||||
public void valueForKey(QCue cue, string key) { client.sendMessage(addressForCue(cue, key)); }
|
||||
|
||||
public void valuesForKeys(QCue cue, string[] keys)
|
||||
{
|
||||
string keyString = JsonConvert.SerializeObject(keys);
|
||||
client.sendMessage(addressForCue(cue, "valuesForKeys"), keyString);
|
||||
}
|
||||
|
||||
public void updatePropertySend(QCue cue, object value, string key) {
|
||||
client.sendMessage(addressForCue(cue, key), value);
|
||||
}
|
||||
|
||||
public void updatePropertiesSend(QCue cue, object[] values, string key) { client.sendMessage(addressForCue(cue, key), values); }
|
||||
|
||||
public void updateAllCueProperties()
|
||||
{
|
||||
root.sendAllPropertiesToQLab();
|
||||
}
|
||||
public void runningOrPausedCues()
|
||||
{
|
||||
client.sendMessage($"{workspacePrefix}/runningOrPausedCues");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Property Fetching
|
||||
//TODO
|
||||
|
||||
|
||||
public void fetchDefaultPropertiesForCue(QCue cue)
|
||||
{
|
||||
string[] keys = new string[] { QOSCKey.UID, QOSCKey.Number, QOSCKey.Name,
|
||||
QOSCKey.ListName, QOSCKey.Type, QOSCKey.ColorName,
|
||||
QOSCKey.Flagged, QOSCKey.Armed, QOSCKey.Notes };
|
||||
fetchPropertiesForCue(cue, keys, false);
|
||||
}
|
||||
|
||||
public void fetchBasicPropertiesForCue(QCue cue)
|
||||
{
|
||||
string[] keys = new string[] { QOSCKey.Name, QOSCKey.Number, QOSCKey.FileTarget, QOSCKey.CueTargetNumber,
|
||||
QOSCKey.HasFileTargets, QOSCKey.HasCueTargets, QOSCKey.Armed, QOSCKey.ColorName,
|
||||
QOSCKey.ContinueMode, QOSCKey.Flagged, QOSCKey.PreWait, QOSCKey.PostWait,
|
||||
QOSCKey.Duration, QOSCKey.AllowsEditingDuration };
|
||||
fetchPropertiesForCue(cue, keys, false);
|
||||
}
|
||||
|
||||
public void fetchPropertiesForCue(QCue cue, string[] keys, bool includeChildren)
|
||||
{
|
||||
valuesForKeys(cue, keys);
|
||||
|
||||
if (!includeChildren)
|
||||
return;
|
||||
foreach (var childCue in cue.cues)
|
||||
{
|
||||
fetchPropertiesForCue(cue, keys, includeChildren);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OSC address helpers
|
||||
public void sendMessage(string address, params object[] args)
|
||||
{
|
||||
//check for workspace prefix?
|
||||
if (!address.StartsWith(workspacePrefix))
|
||||
address = workspacePrefix + address;
|
||||
client.sendMessage(address, args);
|
||||
}
|
||||
|
||||
private string addressForCue(QCue cue, string action)
|
||||
{
|
||||
return $"{workspacePrefix}/cue_id/{cue.propertyForKey(QOSCKey.UID)}/{action}";
|
||||
}
|
||||
|
||||
private string addressForWildcardNumber(string number, string action) {
|
||||
return $"{workspacePrefix}/cue/{number}/{action}";
|
||||
}
|
||||
|
||||
private string workspacePrefix
|
||||
{
|
||||
get { return $"/workspace/{uniqueID}"; }
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
public string workspaceID
|
||||
{
|
||||
get
|
||||
{
|
||||
return uniqueID;
|
||||
}
|
||||
}
|
||||
|
||||
#region Event Handling
|
||||
private void OnWorkspaceConnectionError(object source, QWorkspaceConnectionErrorArgs args)
|
||||
{
|
||||
if (args.status.Equals("badpass"))
|
||||
{
|
||||
//clear passcode if there was one set in the connect() method
|
||||
this.passcode = "";
|
||||
Log.Error($"[workspace] *** Password for workspace {name} was incorrect!");
|
||||
}
|
||||
else
|
||||
Log.Error($"[workspace] *** Unable to connect to workspace: {name} on server: {server.name}");
|
||||
|
||||
}
|
||||
|
||||
private void OnWorkspaceConnected(object source, QWorkspaceConnectedArgs args)
|
||||
{
|
||||
Log.Debug($"[workspace] Connection being finished.");
|
||||
finishConnection();
|
||||
}
|
||||
|
||||
private void OnWorkspaceDisconnected(object source, QWorkspaceDisconnectedArgs args)
|
||||
{
|
||||
//this might not be called with TCP?
|
||||
Log.Warning($"[workspace] *** Workspace has indicated it is disconnecting");
|
||||
}
|
||||
|
||||
private void OnCueListsUpdated(object source, QCueListsUpdatedArgs args)
|
||||
{
|
||||
bool rootCueUpdated = false;
|
||||
List<QCue> currentCueLists = new List<QCue>(args.data.Count());
|
||||
int index = 0;
|
||||
|
||||
foreach (var aCueList in args.data)
|
||||
{
|
||||
string uid = aCueList[QOSCKey.UID].ToString();
|
||||
if (uid == null)
|
||||
continue;
|
||||
QCue cueList = root.cueWithID(uid, false);
|
||||
|
||||
if(cueList != null)
|
||||
{
|
||||
if(cueList.sortIndex != index)
|
||||
{
|
||||
cueList.sortIndex = index;
|
||||
rootCueUpdated = true;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
cueList = new QCue(aCueList, this);
|
||||
cueList.sortIndex = index;
|
||||
if (connectedToQLab3)
|
||||
cueList.setProperty(QCueType.CueList, QOSCKey.Type);
|
||||
rootCueUpdated = true;
|
||||
}
|
||||
currentCueLists.Add(cueList);
|
||||
index++;
|
||||
}
|
||||
QCue activeCuesList = root.cueWithID(QIdentifiers.ActiveCues, false);
|
||||
if(activeCuesList != null)
|
||||
{
|
||||
if (activeCuesList.sortIndex != index)
|
||||
{
|
||||
activeCuesList.setProperty(QIdentifiers.ActiveCues, QOSCKey.UID, false);
|
||||
activeCuesList.setProperty("Active Cues", QOSCKey.Name, false);
|
||||
activeCuesList.setProperty(QCueType.CueList, QOSCKey.Type, false);
|
||||
|
||||
rootCueUpdated = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activeCuesList = new QCue(this);
|
||||
activeCuesList.setProperty(QIdentifiers.ActiveCues, QOSCKey.UID, false);
|
||||
activeCuesList.setProperty("Active Cues", QOSCKey.Name, false);
|
||||
activeCuesList.setProperty(QCueType.CueList, QOSCKey.Type, false);
|
||||
rootCueUpdated = true;
|
||||
}
|
||||
currentCueLists.Add(activeCuesList);
|
||||
|
||||
if (root.cues.Count() != currentCueLists.Count())
|
||||
rootCueUpdated = true;
|
||||
|
||||
root.setProperty(currentCueLists, QOSCKey.Cues, false) ;
|
||||
|
||||
if (rootCueUpdated)
|
||||
{
|
||||
//add Event handled? use CueUpdated one?
|
||||
}
|
||||
|
||||
Log.Debug($"[workspace] cueLists finished processing. root updated? {rootCueUpdated}");
|
||||
|
||||
|
||||
OnWorkspaceUpdated();
|
||||
}
|
||||
|
||||
private void OnCueListChangedPlaybackPosition(object source, QCueListChangedPlaybackPositionArgs args)
|
||||
{
|
||||
QCue cueList = cueWithID(args.cueListID);
|
||||
|
||||
if (cueList == null)
|
||||
return;
|
||||
if (cueList.ignoreUpdates)
|
||||
return;
|
||||
|
||||
cueList.setProperty(args.cueID, QOSCKey.PlaybackPositionId, false);
|
||||
|
||||
QCue selectedCue = cueWithID(args.cueID);
|
||||
if (selectedCue != null)
|
||||
{
|
||||
Log.Information($"[workspace] cue list <{cueList.displayName}> playback position changed to <{selectedCue.displayName}>");
|
||||
CueListChangedPlaybackPosition?.Invoke(this, new QCueListChangedPlaybackPositionArgs { cueListID = cueList.uid, cueID = selectedCue.uid });
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//OnWorkspaceUpdated
|
||||
//OnWorkspaceSettingsUpdated
|
||||
//LightDashboardUpdated
|
||||
//PreferencesUpdated
|
||||
|
||||
private void OnCueNeedsUpdated(object source, QCueNeedsUpdatedArgs args)
|
||||
{
|
||||
QCue cueToUpdate = cueWithID(args.cueID);
|
||||
if (cueToUpdate == null)
|
||||
return;
|
||||
|
||||
fetchBasicPropertiesForCue(cueToUpdate);
|
||||
}
|
||||
|
||||
private void OnCueUpdated(object source, QCueUpdatedArgs args)
|
||||
{
|
||||
QCue cue = cueWithID(args.cueID);
|
||||
|
||||
if (cue == null)
|
||||
return;
|
||||
if (cue.ignoreUpdates)
|
||||
return;
|
||||
|
||||
cue.updatePropertiesWithDictionary(args.data);
|
||||
|
||||
}
|
||||
|
||||
private void OnWorkspaceUpdated()
|
||||
{
|
||||
WorkspaceUpdated?.Invoke(this, new QWorkspaceUpdatedArgs());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Printing
|
||||
public void Print()
|
||||
{
|
||||
Log.Information($"[workspace] {name}");
|
||||
foreach (var cueList in root.cues)
|
||||
{
|
||||
cueList.Print();
|
||||
}
|
||||
Log.Information("[workspace] End of Workspace");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//General information about a QLab Workspace
|
||||
namespace QControlKit
|
||||
{
|
||||
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,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using Serilog;
|
||||
|
||||
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;
|
||||
private Thread receivingThread;
|
||||
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 bool Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
client = new TcpClient(Address, Port);
|
||||
receivingThread = new Thread(ReceiveLoop);
|
||||
receivingThread.Start();
|
||||
Console.WriteLine($"[tcpclient] connected to <{Address}:{Port}>");
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (client == null)
|
||||
return false;
|
||||
else
|
||||
return client.Connected;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveLoop()
|
||||
{
|
||||
while (client.Connected)
|
||||
{
|
||||
Receive();
|
||||
}
|
||||
//Log.Debug("[tcpclient] - ReceiveLoop has exited");
|
||||
}
|
||||
|
||||
public void Receive()
|
||||
{
|
||||
Random random = new Random();
|
||||
int num = random.Next(1000);
|
||||
try
|
||||
{
|
||||
NetworkStream netStream = client.GetStream();
|
||||
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)
|
||||
{
|
||||
//Console.WriteLine("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()
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
if (client.Connected)
|
||||
{
|
||||
client.GetStream().Close();
|
||||
client.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnMessageReceived(OscMessage msg)
|
||||
{
|
||||
MessageReceived?.Invoke(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user