Skip to content

Commit

Permalink
Add: Support sendinhg
Browse files Browse the repository at this point in the history
  • Loading branch information
sevenc-nanashi committed Sep 15, 2024
1 parent 37decb3 commit 7c8f336
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 54 deletions.
4 changes: 4 additions & 0 deletions OpenUtau.Core/Commands/Notifications.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,8 @@ public NotePresetChangedNotification() {
}
public override string ToString() => "Note preset changed.";
}

public class DawConnectedNotification : UNotification {
public override string ToString() => $"Connected to DAW.";
}
}
70 changes: 65 additions & 5 deletions OpenUtau.Core/DawIntegration/Client.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenUtau.Core.SignalChain;
using Vortice.Direct3D;
using WanaKanaNet.Helpers;
using OpenUtau.Core.Ustx;
using NAudio.Wave;
using Newtonsoft.Json;
using System.IO.Compression;

namespace OpenUtau.Core.DawIntegration {
public class Client {
static int VERSION = 1;
private readonly int port;
private TcpClient tcpClient;
private Stream? stream;
private Task? receiver;
private CancellationTokenSource? cancellationTokenSource;
private Dictionary<string, Action<string>> handlers = new Dictionary<string, Action<string>>();
private Dictionary<string, Action<string>> onetimeHandlers = new Dictionary<string, Action<string>>();

private Client(int port) {
this.port = port;
this.tcpClient = new TcpClient();
tcpClient = new TcpClient();
}

~Client() {
Expand All @@ -29,8 +37,9 @@ private Client(int port) {
}

private async Task StartReceiver(CancellationToken token) {
var stream = tcpClient.GetStream();

if (stream == null) {
throw new Exception("stream is null");
}
await Task.Run(async () => {
byte[] currentMessageBuffer = new byte[0];
while (true) {
Expand All @@ -46,6 +55,9 @@ await Task.Run(async () => {
var content = parts[1];
if (handlers.ContainsKey(kind)) {
handlers[kind](content);
} else if (onetimeHandlers.ContainsKey(kind)) {
onetimeHandlers[kind](content);
onetimeHandlers.Remove(kind);
} else {
Console.WriteLine($"Unhandled message: {kind}");
}
Expand All @@ -54,16 +66,64 @@ await Task.Run(async () => {
});
}

public static async Task Connect(int port) {
Client client = new Client(port);
public static async Task<Client> Connect(int port) {
var client = new Client(port);
await client.tcpClient.ConnectAsync("127.0.0.1", port);
client.stream = client.tcpClient.GetStream();

client.cancellationTokenSource = new CancellationTokenSource();
client.receiver = client.StartReceiver(client.cancellationTokenSource.Token);

var tcs = new TaskCompletionSource<object?>();
client.RegisterOnetimeListener("init", (string message) => {
tcs.SetResult(null);
});

var timeoutCanceller = new CancellationTokenSource();
timeoutCanceller.CancelAfter(TimeSpan.FromSeconds(5));
timeoutCanceller.Token.Register(() => tcs.TrySetCanceled());
await tcs.Task;
return client;
}

public async Task SendStatus(UProject project, List<WaveMix> mixes) {
var ustx = Format.Ustx.CreateUstx(project);
var base64Mixes = mixes.Select(mixSource => {
if (mixSource == null) {
return "";
}
var mix = new ExportAdapter(mixSource).ToWaveProvider();
using (var ms = new MemoryStream())
using (var compressor = new GZipStream(ms, CompressionMode.Compress)) {
var buffer = new byte[mix.WaveFormat.AverageBytesPerSecond];
int bytesRead;
while ((bytesRead = mix.Read(buffer, 0, buffer.Length)) > 0) {
compressor.Write(buffer, 0, bytesRead);
}
return Convert.ToBase64String(ms.ToArray());
}
});

var message = new UpdateStatusMessage(
ustx,
base64Mixes.ToList()
);

await SendMessage("status", message);
}

private async Task SendMessage(string kind, object json) {
if (stream == null) {
throw new Exception("stream is null");
}
await stream.WriteAsync(Encoding.UTF8.GetBytes($"{kind} {JsonConvert.SerializeObject(json)}\n"));
}

public void RegisterListener(string kind, Action<string> handler) {
handlers[kind] = handler;
}
public void RegisterOnetimeListener(string kind, Action<string> handler) {
onetimeHandlers[kind] = handler;
}
}
}
42 changes: 37 additions & 5 deletions OpenUtau.Core/DawIntegration/ServerFinder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using Newtonsoft.Json;

Expand All @@ -8,7 +11,7 @@ public class ServerFinder {
private static string getServerPath() {
var temp = Path.GetTempPath();

return $"{temp}/OpenUtau/PluginServers";
return $"{temp}OpenUtau/PluginServers";
}
public static async Task<List<Server>> FindServers() {
var path = getServerPath();
Expand All @@ -21,20 +24,49 @@ public static async Task<List<Server>> FindServers() {

var servers = new List<Server>();
foreach (FileInfo file in files) {
var json = await File.ReadAllTextAsync(file.FullName);
var server = JsonConvert.DeserializeObject<Server>(json);
if (server != null) {
servers.Add(server);
try {
var json = await File.ReadAllTextAsync(file.FullName);
var server = JsonConvert.DeserializeObject<Server>(json);
if (server != null) {
if (CheckPortUsing(server.Port)) {
servers.Add(server);
} else {

}
}
} catch {
// Ignore invalid server files
}
}

return servers;
}

private static bool CheckPortUsing(int port) {
var tcpListener = default(TcpListener);

try {
var ipAddress = IPAddress.Parse("127.0.0.1");

tcpListener = new TcpListener(ipAddress, port);
tcpListener.Start();

return false;
} catch (SocketException) {
} finally {
if (tcpListener != null)
tcpListener.Stop();
}

return true;
}
}

public class Server {
public int Port { get; }
public string Name { get; }

[JsonConstructor]
Server(int port, string name) {
Port = port;
Name = name;
Expand Down
67 changes: 66 additions & 1 deletion OpenUtau.Core/DocManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using OpenUtau.Api;
using OpenUtau.Classic;
using OpenUtau.Core.Lib;
using OpenUtau.Core.Render;
using OpenUtau.Core.Ustx;
using OpenUtau.Core.Util;
using Serilog;
Expand All @@ -21,6 +22,34 @@ public struct ValidateOptions {
public bool SkipPhoneme;
}

internal struct DawUpdatePoint : IEquatable<DawUpdatePoint> {
public UCommandGroup? commandGroup;
public UProject project;

public DawUpdatePoint(UCommandGroup commandGroup, UProject project) {
this.commandGroup = commandGroup;
this.project = project;
}

public bool Equals(DawUpdatePoint other) {
return commandGroup == other.commandGroup && project == other.project;
}
public override bool Equals(object obj) {
return obj is DawUpdatePoint point ? Equals(point) : base.Equals(obj);
}

public static bool operator ==(DawUpdatePoint obj1, DawUpdatePoint obj2) {
return obj1.Equals(obj2);
}

public static bool operator !=(DawUpdatePoint obj1, DawUpdatePoint obj2) {
return !(obj1 == obj2);
}
public override int GetHashCode() {
return (commandGroup == null ? 0 : commandGroup.GetHashCode()) ^ project.GetHashCode();
}
}

public class DocManager : SingletonBase<DocManager> {
DocManager() {
Project = new UProject();
Expand All @@ -40,7 +69,10 @@ public class DocManager : SingletonBase<DocManager> {
public List<UPart> PartsClipboard { get; set; }
public List<UNote> NotesClipboard { get; set; }
internal PhonemizerRunner PhonemizerRunner { get; private set; }
internal DawIntegration.Client? VstClient { get; set; }
public DawIntegration.Client? dawClient { get; set; }

internal bool isDawClientLocked = false;
internal DawUpdatePoint? lastDawStatusUpdateCheckPoint = null;

public void Initialize() {
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler((sender, args) => {
Expand Down Expand Up @@ -180,6 +212,39 @@ public void AutoSave() {
}
}

public async Task UpdateDaw() {
if (dawClient == null) {
return;
}
if (isDawClientLocked) {
Log.Information("DawClient is in use, skipping");
return;
}
isDawClientLocked = true;
try {
var currentPoint = new DawUpdatePoint(undoQueue.LastOrDefault(), Project);
if (lastDawStatusUpdateCheckPoint != currentPoint) {
lastDawStatusUpdateCheckPoint = currentPoint;
// To reduce some hangs while user is using editor.
Log.Information("Editor is active, skipping sending status to DAW.");
return;
}
Log.Information("Sending status to DAW...");
RenderEngine engine = new RenderEngine(Project, startTick: 0, endTick: -1, trackNo: -1);
var renderCancellation = new CancellationTokenSource();
var trackMixes = engine.RenderTracks(Inst.MainScheduler, ref renderCancellation);
await dawClient.SendStatus(
Project,
trackMixes
);
Log.Information("Sent status to DAW.");
} catch (Exception e) {
Log.Error(e, "Failed to send status to DAW.");
} finally {
isDawClientLocked = false;
}
}

public void ExecuteCmd(UCommand cmd) {
if (mainThread != Thread.CurrentThread) {
if (!(cmd is ProgressBarNotification)) {
Expand Down
17 changes: 12 additions & 5 deletions OpenUtau.Core/Format/USTx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,25 @@ public static UProject Create() {

public static void Save(string filePath, UProject project) {
try {
project.ustxVersion = kUstxVersion;
var ustx = CreateUstx(project);
project.FilePath = filePath;
project.BeforeSave();
File.WriteAllText(filePath, Yaml.DefaultSerializer.Serialize(project), Encoding.UTF8);
project.Saved = true;
project.AfterSave();
File.WriteAllText(filePath, ustx, Encoding.UTF8);
} catch (Exception ex) {
var e = new MessageCustomizableException("Failed to save ustx: {filePath}", $"<translate:errors.failed.save>: {filePath}", ex);
DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(e));
}
}

public static string CreateUstx(UProject project) {
project.ustxVersion = kUstxVersion;
project.BeforeSave();
var ustx = Yaml.DefaultSerializer.Serialize(project);
project.Saved = true;
project.AfterSave();

return ustx;
}

public static void AutoSave(string filePath, UProject project) {
try {
project.ustxVersion = kUstxVersion;
Expand Down
2 changes: 2 additions & 0 deletions OpenUtau/Strings/Strings.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@ General
<system:String x:Key="dawintegrationterminal.refresh">Refresh</system:String>
<system:String x:Key="dawintegrationterminal.connect">Connect</system:String>

<system:String x:Key="dawintegration.active"> (Attached to DAW)</system:String>

<FontFamily x:Key="ui.fontfamily">Segoe UI,San Francisco,Helvetica Neue</FontFamily>

<system:String x:Key="updater.caption">Check for Update</system:String>
Expand Down
16 changes: 14 additions & 2 deletions OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using DynamicData.Binding;
using Avalonia.Threading;
using DynamicData.Binding;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using OpenUtau.Core;
using OpenUtau.Core.DawIntegration;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
Expand All @@ -22,13 +24,23 @@ public DawIntegrationTerminalViewModel() {
public async Task RefreshServerList() {
var servers = await ServerFinder.FindServers();

ServerList.Load(servers);
if (servers.Count == 0) {
SelectedServer = null;
} else {
ServerList.Load(servers);
SelectedServer = ServerList[0];
}
}

public async Task Connect() {
if (SelectedServer == null) {
return;
}

var client = await Client.Connect(SelectedServer.Port);

DocManager.Inst.dawClient = client;
}

}
}
Loading

0 comments on commit 7c8f336

Please sign in to comment.