From 4511705bea0db4f54791ff3472a0143b9abf2d8f Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 1 Sep 2024 14:35:54 +0900 Subject: [PATCH 01/72] Add: Add Vst.Client --- OpenUtau.Core/DocManager.cs | 1 + OpenUtau.Core/Vst/Client.cs | 69 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 OpenUtau.Core/Vst/Client.cs diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 0ef989a35..a6665f68f 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -40,6 +40,7 @@ public class DocManager : SingletonBase { public List PartsClipboard { get; set; } public List NotesClipboard { get; set; } internal PhonemizerRunner PhonemizerRunner { get; private set; } + internal Vst.Client VstClient { get; set; } public void Initialize() { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler((sender, args) => { diff --git a/OpenUtau.Core/Vst/Client.cs b/OpenUtau.Core/Vst/Client.cs new file mode 100644 index 000000000..8d481ab33 --- /dev/null +++ b/OpenUtau.Core/Vst/Client.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Vortice.Direct3D; +using WanaKanaNet.Helpers; + +namespace OpenUtau.Core.Vst { + public class Client { + static int VERSION = 1; + private readonly int port; + private TcpClient tcpClient; + private Task? receiver; + private CancellationTokenSource? cancellationTokenSource; + private Dictionary> handlers = new Dictionary>(); + + private Client(int port) { + this.port = port; + this.tcpClient = new TcpClient(); + } + + ~Client() { + tcpClient.Close(); + cancellationTokenSource?.Cancel(); + } + + private async Task StartReceiver(CancellationToken token) { + var stream = tcpClient.GetStream(); + + await Task.Run(async () => { + byte[] currentMessageBuffer = new byte[0]; + while (true) { + byte[] buffer = new byte[1024]; + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token); + currentMessageBuffer = currentMessageBuffer.Concat(buffer).ToArray(); + while (currentMessageBuffer.Contains((byte)'\n')) { + int index = Array.IndexOf(currentMessageBuffer, (byte)'\n'); + string message = Encoding.UTF8.GetString(currentMessageBuffer.Take(index).ToArray()); + currentMessageBuffer = currentMessageBuffer.Skip(index + 1).ToArray(); + string[] parts = message.Split(' ', 2); + var kind = parts[0]; + var content = parts[1]; + if (handlers.ContainsKey(kind)) { + handlers[kind](content); + } else { + Console.WriteLine($"Unhandled message: {kind}"); + } + } + } + }); + } + + public static async Task Connect(int port) { + Client client = new Client(port); + await client.tcpClient.ConnectAsync("127.0.0.1", port); + + client.cancellationTokenSource = new CancellationTokenSource(); + client.receiver = client.StartReceiver(client.cancellationTokenSource.Token); + } + + public void RegisterListener(string kind, Action handler) { + handlers[kind] = handler; + } + } +} From 03830c02b15f74cb1dbcebc8c799e407621a0c5c Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Tue, 3 Sep 2024 21:36:43 +0900 Subject: [PATCH 02/72] Add: Add Vst.ServerFinder --- OpenUtau.Core/Vst/ServerFinder.cs | 46 ++++++++++++ OpenUtau/OpenUtau.csproj | 3 + OpenUtau/Strings/Strings.axaml | 9 +++ OpenUtau/ViewModels/VstTerminalViewModel.cs | 28 +++++++ OpenUtau/Views/MainWindow.axaml | 2 + OpenUtau/Views/MainWindow.axaml.cs | 82 ++++++++++++--------- OpenUtau/Views/VstTerminalDialog.axaml | 43 +++++++++++ OpenUtau/Views/VstTerminalDialog.axaml.cs | 31 ++++++++ 8 files changed, 209 insertions(+), 35 deletions(-) create mode 100644 OpenUtau.Core/Vst/ServerFinder.cs create mode 100644 OpenUtau/ViewModels/VstTerminalViewModel.cs create mode 100644 OpenUtau/Views/VstTerminalDialog.axaml create mode 100644 OpenUtau/Views/VstTerminalDialog.axaml.cs diff --git a/OpenUtau.Core/Vst/ServerFinder.cs b/OpenUtau.Core/Vst/ServerFinder.cs new file mode 100644 index 000000000..25d32fac9 --- /dev/null +++ b/OpenUtau.Core/Vst/ServerFinder.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; + +namespace OpenUtau.Core.Vst { + public class ServerFinder { + private static string getServerPath() { + string temp = Path.GetTempPath(); + + return $"{temp}/OpenUtau/VstServers"; + } + public static List FindServers() { + string path = getServerPath(); + if (!Directory.Exists(path)) { + return new List(); + } + + DirectoryInfo di = new DirectoryInfo(path); + FileInfo[] files = di.GetFiles("*.json"); + + List servers = new List(); + foreach (FileInfo file in files) { + string json = File.ReadAllText(file.FullName); + Server? server = JsonConvert.DeserializeObject(json); + if (server != null) { + servers.Add(server); + } + } + + return servers; + } + } + + public class Server { + public int Port { get; set; } + public string Name { get; set; } + Server(int port, string name) { + Port = port; + Name = name; + } + + public override string ToString() { + return Name; + } + } +} diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index d62d5a316..5a6958131 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -117,6 +117,9 @@ TrackColorDialog.axaml + + VstTerminalDialog.axaml + diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index afa89e2ee..ee5fb2884 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -185,6 +185,8 @@ Save Save As... Save Template... + Connect to VST... + Disconnect from VST Help About OpenUtau Check Update @@ -546,6 +548,13 @@ General Change track color Track Settings + Connect to VST + Download VST, show UI and connect (temporary text) + Download VST + Name + Refresh + Connect + Segoe UI,San Francisco,Helvetica Neue Check for Update diff --git a/OpenUtau/ViewModels/VstTerminalViewModel.cs b/OpenUtau/ViewModels/VstTerminalViewModel.cs new file mode 100644 index 000000000..8a1a12180 --- /dev/null +++ b/OpenUtau/ViewModels/VstTerminalViewModel.cs @@ -0,0 +1,28 @@ +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenUtau.App.ViewModels { + public class VstTerminalViewModel : ViewModelBase { + [Reactive] public VstServer? SelectedServer { get; set; } = null; + public List VstServers = new List(); + } + + public class VstServer { + public int Port; + public string Name; + + VstServer(int port, string name) { + Port = port; + Name = name; + } + + public override string ToString() { + return Name; + } + } + + +} diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index b5e38c1c7..69833b9b6 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -65,6 +65,8 @@ + + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 7180f6f07..efb3a4086 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -149,9 +149,9 @@ private void DelTempoChange(int tick) { DocManager.Inst.EndUndoGroup(); } - - - void OnMenuRemapTimeaxis(object sender, RoutedEventArgs e){ + + + void OnMenuRemapTimeaxis(object sender, RoutedEventArgs e) { var project = DocManager.Inst.Project; var dialog = new TypeInDialog { Title = ThemeManager.GetString("menu.project.remaptimeaxis") @@ -160,13 +160,13 @@ void OnMenuRemapTimeaxis(object sender, RoutedEventArgs e){ dialog.SetPrompt(ThemeManager.GetString("dialogs.remaptimeaxis.message")); dialog.SetText(project.tempos[0].bpm.ToString()); dialog.onFinish = s => { - try{ + try { if (double.TryParse(s, out double bpm)) { DocManager.Inst.StartUndoGroup(); var oldTimeAxis = project.timeAxis.Clone(); DocManager.Inst.ExecuteCmd(new BpmCommand( project, bpm)); - foreach(var tempo in project.tempos.Skip(1)){ + foreach (var tempo in project.tempos.Skip(1)) { DocManager.Inst.ExecuteCmd(new DelTempoChangeCommand( project, tempo.position)); } @@ -321,20 +321,20 @@ async void OnMenuImportTracks(object sender, RoutedEventArgs args) { } try { var loadedProjects = Formats.ReadProjects(files); - if(loadedProjects == null || loadedProjects.Length == 0){ + if (loadedProjects == null || loadedProjects.Length == 0) { return; - } + } bool importTempo = true; - switch(Preferences.Default.ImportTempo){ + switch (Preferences.Default.ImportTempo) { case 1: importTempo = false; break; case 2: - if(loadedProjects[0].tempos.Count == 0){ + if (loadedProjects[0].tempos.Count == 0) { importTempo = false; break; } - var tempoString = String.Join("\n", + var tempoString = String.Join("\n", loadedProjects[0].tempos .Select(tempo => $"position: {tempo.position}, tempo: {tempo.bpm}") ); @@ -344,7 +344,7 @@ async void OnMenuImportTracks(object sender, RoutedEventArgs args) { ThemeManager.GetString("dialogs.importtracks.importtempo") + "\n" + tempoString, ThemeManager.GetString("dialogs.importtracks.caption"), MessageBox.MessageBoxButtons.YesNo); - if(result == MessageBox.MessageBoxResult.No){ + if (result == MessageBox.MessageBoxResult.No) { importTempo = false; } break; @@ -421,7 +421,7 @@ async void OnMenuExportDsTo(object sender, RoutedEventArgs e) { for (var i = 0; i < project.parts.Count; i++) { var part = project.parts[i]; if (part is UVoicePart voicePart) { - var savePath = PathManager.Inst.GetPartSavePath(file, voicePart.DisplayName, i)[..^4]+".ds"; + var savePath = PathManager.Inst.GetPartSavePath(file, voicePart.DisplayName, i)[..^4] + ".ds"; DiffSingerScript.SavePart(project, voicePart, savePath); DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"{savePath}.")); } @@ -437,7 +437,7 @@ async void OnMenuExportDsV2To(object sender, RoutedEventArgs e) { for (var i = 0; i < project.parts.Count; i++) { var part = project.parts[i]; if (part is UVoicePart voicePart) { - var savePath = PathManager.Inst.GetPartSavePath(file, voicePart.DisplayName, i)[..^4]+".ds"; + var savePath = PathManager.Inst.GetPartSavePath(file, voicePart.DisplayName, i)[..^4] + ".ds"; DiffSingerScript.SavePart(project, voicePart, savePath, true); DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"{savePath}.")); } @@ -453,7 +453,7 @@ async void OnMenuExportDsV2WithoutPitchTo(object sender, RoutedEventArgs e) { for (var i = 0; i < project.parts.Count; i++) { var part = project.parts[i]; if (part is UVoicePart voicePart) { - var savePath = PathManager.Inst.GetPartSavePath(file, voicePart.DisplayName, i)[..^4]+".ds"; + var savePath = PathManager.Inst.GetPartSavePath(file, voicePart.DisplayName, i)[..^4] + ".ds"; DiffSingerScript.SavePart(project, voicePart, savePath, true, false); DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"{savePath}.")); } @@ -503,6 +503,18 @@ async void OnMenuExportMidi(object sender, RoutedEventArgs e) { } } + void OnMenuVstTerminal(object sender, RoutedEventArgs args) { + VstTerminalViewModel dataContext; + dataContext = new VstTerminalViewModel(); + var dialog = new VstTerminalDialog() { + DataContext = dataContext + }; + dialog.ShowDialog(this); + if (dialog.Position.Y < 0) { + dialog.Position = dialog.Position.WithY(0); + } + } + private async Task WarnToSave(UProject project) { if (string.IsNullOrEmpty(project.FilePath)) { await MessageBox.Show( @@ -537,8 +549,8 @@ void OnMenuSingers(object sender, RoutedEventArgs args) { /// If the user haven't selected a singer for the track, or the singer specified in ustx project doesn't exist, return null. /// Otherwise, return the singer. /// - public USinger? TrackSingerIfFound(UTrack track){ - if(track.Singer?.Found ?? false){ + public USinger? TrackSingerIfFound(UTrack track) { + if (track.Singer?.Found ?? false) { return track.Singer; } return null; @@ -609,7 +621,7 @@ async void OnMenuInstallSinger(object sender, RoutedEventArgs args) { } catch (Exception e) { Log.Error(e, $"Failed to install singer {file}"); MessageCustomizableException mce; - if(e is MessageCustomizableException){ + if (e is MessageCustomizableException) { mce = (MessageCustomizableException)e; } else { mce = new MessageCustomizableException($"Failed to install singer {file}", $": {file}", e); @@ -618,7 +630,7 @@ async void OnMenuInstallSinger(object sender, RoutedEventArgs args) { } } - async void OnMenuInstallDependency(object sender, RoutedEventArgs args){ + async void OnMenuInstallDependency(object sender, RoutedEventArgs args) { var file = await FilePicker.OpenFile( this, "menu.tools.dependency.install", FilePicker.OUDEP); if (file == null) { @@ -734,7 +746,7 @@ private void LayoutSplit(double? x, double? y) { Width = x != null ? wa.Size.Width * x.Value : wa.Size.Width; Height = (y != null ? wa.Size.Height * y.Value : wa.Size.Height) - titleBarHeight; if (pianoRollWindow != null) { - pianoRollWindow.Position = new PixelPoint(x != null ? (int) Width : 0, y != null ? (int) (Height + (OS.IsMacOS() ? 25 : titleBarHeight)) : 0); + pianoRollWindow.Position = new PixelPoint(x != null ? (int)Width : 0, y != null ? (int)(Height + (OS.IsMacOS() ? 25 : titleBarHeight)) : 0); pianoRollWindow.Width = x != null ? wa.Size.Width - Width : wa.Size.Width; pianoRollWindow.Height = (y != null ? wa.Size.Height - (Height + titleBarHeight) : wa.Size.Height) - titleBarHeight; } @@ -745,7 +757,7 @@ void OnKeyDown(object sender, KeyEventArgs args) { return; } var tracksVm = viewModel.TracksViewModel; - if (args.KeyModifiers == KeyModifiers.None){ + if (args.KeyModifiers == KeyModifiers.None) { args.Handled = true; switch (args.Key) { case Key.Delete: viewModel.TracksViewModel.DeleteSelectedParts(); break; @@ -834,7 +846,7 @@ async void OnDrop(object? sender, DragEventArgs args) { } string file = storageItem.Path.LocalPath; var ext = Path.GetExtension(file); - if (ext == ".ustx" || ext == ".ust" || ext == ".vsqx" || ext==".ufdata") { + if (ext == ".ustx" || ext == ".ust" || ext == ".vsqx" || ext == ".ufdata") { if (!DocManager.Inst.ChangesSaved && !await AskIfSaveAndContinue()) { return; } @@ -852,7 +864,7 @@ async void OnDrop(object? sender, DragEventArgs args) { _ = await MessageBox.ShowError(this, new MessageCustomizableException("Failed to import midi", "", e)); } } else if (ext == ".zip" || ext == ".rar" || ext == ".uar") { - try{ + try { var setup = new SingerSetupDialog() { DataContext = new SingerSetupViewModel() { ArchiveFilePath = file, @@ -865,7 +877,7 @@ async void OnDrop(object? sender, DragEventArgs args) { } catch (Exception e) { Log.Error(e, $"Failed to install singer {file}"); MessageCustomizableException mce; - if(e is MessageCustomizableException){ + if (e is MessageCustomizableException) { mce = (MessageCustomizableException)e; } else { mce = new MessageCustomizableException($"Failed to install singer {file}", $": {file}", e); @@ -936,7 +948,7 @@ public void VScrollPointerWheelChanged(object sender, PointerWheelEventArgs args public void TimelinePointerWheelChanged(object sender, PointerWheelEventArgs args) { var control = (Control)sender; - var position = args.GetCurrentPoint((Visual) sender).Position; + var position = args.GetCurrentPoint((Visual)sender).Position; var size = control.Bounds.Size; position = position.WithX(position.X / size.Width).WithY(position.Y / size.Height); viewModel.TracksViewModel.OnXZoomed(position, 0.1 * args.Delta.Y); @@ -1039,7 +1051,7 @@ public void PartsCanvasPointerPressed(object sender, PointerPressedEventArgs arg } public void PartsCanvasPointerMoved(object sender, PointerEventArgs args) { - var control = (Control) sender; + var control = (Control)sender; var point = args.GetCurrentPoint(control); if (partEditState != null) { partEditState.Update(point.Pointer, point.Position); @@ -1068,7 +1080,7 @@ public void PartsCanvasPointerReleased(object sender, PointerReleasedEventArgs a if (partEditState.MouseButton != args.InitialPressMouseButton) { return; } - var control = (Control) sender; + var control = (Control)sender; var point = args.GetCurrentPoint(control); partEditState.Update(point.Pointer, point.Position); partEditState.End(point.Pointer, point.Position); @@ -1187,20 +1199,20 @@ async void ReplaceAudio(UPart part) { DocManager.Inst.EndUndoGroup(); } - void Transcribe(UPart part){ + void Transcribe(UPart part) { //Convert audio to notes - if(part is UWavePart wavePart){ - try{ + if (part is UWavePart wavePart) { + try { string text = ThemeManager.GetString("context.part.transcribing"); var msgbox = MessageBox.ShowModal(this, $"{text} {part.name}", text); //Duration of the wave file in seconds int wavDurS = (int)(wavePart.fileDurationMs / 1000.0); var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); - var transcribeTask = Task.Run(()=>{ - using(var some = new Some()){ - return some.Transcribe(DocManager.Inst.Project, wavePart, wavPosS =>{ + var transcribeTask = Task.Run(() => { + using (var some = new Some()) { + return some.Transcribe(DocManager.Inst.Project, wavePart, wavPosS => { //msgbox?.SetText($"{text} {part.name}\n{wavPosS}/{wavDurS}"); - msgbox.SetText(string.Format("{0} {1}\n{2}s / {3}s",text, part.name, wavPosS, wavDurS)); + msgbox.SetText(string.Format("{0} {1}\n{2}s / {3}s", text, part.name, wavPosS, wavDurS)); }); } }); @@ -1213,7 +1225,7 @@ void Transcribe(UPart part){ } var voicePart = task.Result; //Add voicePart into project - if(voicePart != null){ + if (voicePart != null) { var project = DocManager.Inst.Project; var track = new UTrack(project); track.TrackNo = project.tracks.Count; @@ -1350,7 +1362,7 @@ public void OnNext(UCommand cmd, bool isUndo) { MessageBox.CloseLoading(); } } else if (cmd is VoiceColorRemappingNotification voicecolorNotif) { - if(voicecolorNotif.TrackNo < 0 || DocManager.Inst.Project.tracks.Count <= voicecolorNotif.TrackNo) { + if (voicecolorNotif.TrackNo < 0 || DocManager.Inst.Project.tracks.Count <= voicecolorNotif.TrackNo) { ValidateTracksVoiceColor(); } else { UTrack track = DocManager.Inst.Project.tracks[voicecolorNotif.TrackNo]; diff --git a/OpenUtau/Views/VstTerminalDialog.axaml b/OpenUtau/Views/VstTerminalDialog.axaml new file mode 100644 index 000000000..d1bbb1985 --- /dev/null +++ b/OpenUtau/Views/VstTerminalDialog.axaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index f9ba06a68..084dffcff 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -510,13 +510,16 @@ async void OnMenuExportMidi(object sender, RoutedEventArgs e) { } } - void OnMenuDawIntegrationTerminal(object sender, RoutedEventArgs args) { + async void OnMenuDawIntegrationTerminal(object sender, RoutedEventArgs args) { + if (!DocManager.Inst.ChangesSaved && !await AskIfSaveAndContinue()) { + return; + } DawIntegrationTerminalViewModel dataContext; dataContext = new DawIntegrationTerminalViewModel(); var dialog = new DawIntegrationTerminalDialog() { DataContext = dataContext }; - dialog.ShowDialog(this); + await dialog.ShowDialog(this); if (dialog.Position.Y < 0) { dialog.Position = dialog.Position.WithY(0); } From 9875281e31f0c74d2fe0c8a502dfa3cc1130c17d Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sat, 21 Sep 2024 15:13:22 +0900 Subject: [PATCH 14/72] Improve: Resample on mixes update --- DawPlugin/src/plugin.cpp | 150 +++++++++++++++++++++++++++------------ DawPlugin/src/plugin.hpp | 10 ++- 2 files changed, 114 insertions(+), 46 deletions(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index a8e9b9fb4..2722a3b2e 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -184,7 +184,12 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { mixes.push_back(mix); } - this->mixes = mixes; + { + this->requestWrite(); + this->mixes = mixes; + this->doneWriting(); + } + resampleMixes(this->currentSampleRate); } else if (key == "trackNames") { this->trackNames = Structures::deserializeTrackNames(value); syncMapping(); @@ -236,46 +241,35 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, } } - if (this->mixes.size() > 0 && timePosition.playing && - !this->wantReplace.load()) { - this->currentAccesses++; - auto writeFrame = [&](double inFrame, uint32_t outFrame, - uint8_t channelIndex, uint8_t lr, uint32_t mixIndex) { - float mix = 0; - auto &mixVector = mixes[mixIndex]; - auto left = (uint32_t)inFrame; - auto right = left + 1; - auto leftSampleIndex = left * 2 + lr; - auto rightSampleIndex = right * 2 + lr; - auto ratio = inFrame - left; - if (leftSampleIndex < mixVector.size() && - rightSampleIndex < mixVector.size()) { - auto leftSample = mixVector[leftSampleIndex]; - auto rightSample = mixVector[rightSampleIndex]; - mix = leftSample * (1 - ratio) + rightSample * ratio; - - outputs[channelIndex][outFrame] += mix; - } - }; - auto sampleRate = getSampleRate(); - for (uint32_t i = 0; i < frames; ++i) { - auto frame = (i + timePosition.frame) * 44100.0 / sampleRate; + auto sampleRate = getSampleRate(); + if (this->resampledMixes.size() > 0 && timePosition.playing && + !this->writing.load()) { + this->readingCount++; + if (this->currentSampleRate == sampleRate) { for (uint32_t j = 0; j < mixes.size(); ++j) { if (j >= this->outputMap.size()) { break; } - auto &mapping = outputMap[j]; - for (uint32_t k = 0; k < DISTRHO_PLUGIN_NUM_OUTPUTS; ++k) { - if (mapping.first[k]) { - writeFrame(frame, i, k, 0, j); - } - if (mapping.second[k]) { - writeFrame(frame, i, k, 1, j); + const auto &mapping = outputMap[j]; + const auto &left = resampledMixes[j].first; + const auto &right = resampledMixes[j].second; + + for (uint32_t i = 0; i < frames; ++i) { + auto frame = (i + timePosition.frame); + for (uint32_t k = 0; k < DISTRHO_PLUGIN_NUM_OUTPUTS; ++k) { + if (mapping.first[k] && frame < left.size()) { + outputs[k][i] += left[frame]; + } + if (mapping.second[k] && frame < right.size()) { + outputs[k][i] += right[frame]; + } } } } + } else { + resampleMixes(sampleRate); } - this->currentAccesses--; + this->readingCount--; } }; @@ -291,7 +285,9 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, */ void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} -void OpenUtauPlugin::sampleRateChanged(double newSampleRate) {} +void OpenUtauPlugin::sampleRateChanged(double newSampleRate) { + resampleMixes(newSampleRate); +} void OpenUtauPlugin::onAccept(std::shared_ptr self, const asio::error_code &error, asio::ip::tcp::socket socket) { @@ -388,10 +384,6 @@ void OpenUtauPlugin::updatePluginServerFile() { void OpenUtauPlugin::onMessage(const std::string kind, const choc::value::Value payload) { if (kind == "status") { - this->wantReplace.store(true); - while (this->currentAccesses > 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } this->lastSync = std::chrono::system_clock::now(); std::string ustx = payload["ustx"].get(); @@ -405,31 +397,101 @@ void OpenUtauPlugin::onMessage(const std::string kind, setState("trackNames", Structures::serializeTrackNames(trackNames).c_str()); syncMapping(); - this->wantReplace.store(false); } } void OpenUtauPlugin::syncMapping() { + this->requestWrite(); + auto trackNames = this->trackNames; auto outputMap = this->outputMap; if (trackNames.size() < outputMap.size()) { outputMap.resize(trackNames.size()); } else if (trackNames.size() > outputMap.size()) { + bool customized = false; + auto defaultLeft = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); + auto defaultRight = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); + defaultLeft[0] = true; + defaultRight[1] = true; + for (const auto &mapping : outputMap) { + if (mapping.first != defaultLeft || mapping.second != defaultRight) { + customized = true; + break; + } + } for (size_t i = outputMap.size(); i < trackNames.size(); ++i) { - auto index = i % 16; - auto left = index * 2; - auto right = left + 1; auto leftChannel = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); auto rightChannel = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); - leftChannel[left] = true; - rightChannel[right] = true; + if (customized) { + auto index = i % 16; + auto left = index * 2; + auto right = left + 1; + leftChannel[left] = true; + rightChannel[right] = true; + } else { + leftChannel[0] = true; + rightChannel[1] = true; + } + outputMap.push_back({leftChannel, rightChannel}); } } + this->outputMap = outputMap; + this->doneWriting(); setState("mapping", Structures::serializeOutputMap(outputMap).c_str()); } +void OpenUtauPlugin::resampleMixes(double newSampleRate) { + requestWrite(); + + std::vector, std::vector>> resampledMixes; + for (const auto &mix : mixes) { + std::vector resampledLeft; + std::vector resampledRight; + resampledLeft.resize(mix.size() * newSampleRate / 44100.0 / 2 + 1); + resampledRight.resize(mix.size() * newSampleRate / 44100.0 / 2 + 1); + for (size_t i = 0; i < resampledLeft.size(); ++i) { + auto leftSource = i * 44100.0 / newSampleRate * 2; + auto rightSource = leftSource + 1; + auto leftLeftIndex = (size_t)leftSource; + auto rightLeftIndex = (size_t)rightSource; + auto leftRightIndex = leftLeftIndex + 2; + auto rightRightIndex = rightLeftIndex + 2; + auto fraction = leftSource - leftLeftIndex; + if (rightRightIndex < mix.size()) { + resampledLeft[i] = mix[leftLeftIndex] * (1 - fraction) + + mix[leftRightIndex] * fraction; + resampledRight[i] = mix[rightLeftIndex] * (1 - fraction) + + mix[rightRightIndex] * fraction; + } else if (rightLeftIndex < mix.size()) { + resampledLeft[i] = mix[leftLeftIndex]; + resampledRight[i] = mix[rightLeftIndex]; + } else { + resampledLeft[i] = 0; + resampledRight[i] = 0; + } + } + + resampledMixes.push_back({resampledLeft, resampledRight}); + } + this->resampledMixes = resampledMixes; + this->currentSampleRate = newSampleRate; + + doneWriting(); +} + +void OpenUtauPlugin::requestWrite() { + while (this->writing.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + this->writing.store(true); + while (this->readingCount > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} +void OpenUtauPlugin::doneWriting() { this->writing.store(false); } + std::string OpenUtauPlugin::formatMessage(const std::string &kind, const choc::value::ValueView &payload) { diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index aceed8466..adf7b16e6 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -123,13 +123,19 @@ class OpenUtauPlugin : public Plugin { void syncMapping(); void updatePluginServerFile(); + void resampleMixes(double newSampleRate); + void requestWrite(); + void doneWriting(); std::string ustx; std::string uuid; - std::atomic wantReplace = false; - std::atomic currentAccesses = 0; + std::atomic writing = false; + std::atomic readingCount = 0; + std::vector> mixes; + std::vector, std::vector>> resampledMixes; + double currentSampleRate = 44100.0; std::filesystem::path socketPath; From b535b968500937722ca679aab1b86419d29c3306 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sat, 21 Sep 2024 15:18:32 +0900 Subject: [PATCH 15/72] Fix: Add lock --- DawPlugin/src/plugin.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 2722a3b2e..8d563a492 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -482,10 +482,9 @@ void OpenUtauPlugin::resampleMixes(double newSampleRate) { } void OpenUtauPlugin::requestWrite() { - while (this->writing.load()) { + while (this->writing.exchange(true)) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - this->writing.store(true); while (this->readingCount > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } From 63db1aef739ed65be1a0b59d6843387ed5af07c7 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 22 Sep 2024 09:34:17 +0900 Subject: [PATCH 16/72] Improve: Improve locks --- DawPlugin/src/common.cpp | 27 +- DawPlugin/src/common.hpp | 6 +- DawPlugin/src/plugin.cpp | 236 +++++++++++------- DawPlugin/src/plugin.hpp | 19 +- DawPlugin/src/ui.cpp | 57 +++-- OpenUtau.Core/Format/USTx.cs | 10 +- .../DawIntegrationTerminalViewModel.cs | 2 +- 7 files changed, 207 insertions(+), 150 deletions(-) diff --git a/DawPlugin/src/common.cpp b/DawPlugin/src/common.cpp index 099b04877..db5abbcfd 100644 --- a/DawPlugin/src/common.cpp +++ b/DawPlugin/src/common.cpp @@ -42,16 +42,8 @@ Structures::deserializeTrackNames(const std::string &data) { std::string Structures::serializeOutputMap(const OutputMap &outputMap) { choc::value::Value value = choc::value::createEmptyArray(); for (const auto &mapping : outputMap) { - choc::value::Value leftChannel = choc::value::createEmptyArray(); - choc::value::Value rightChannel = choc::value::createEmptyArray(); - for (const auto &channel : mapping.first) { - leftChannel.addArrayElement(channel); - } - for (const auto &channel : mapping.second) { - rightChannel.addArrayElement(channel); - } - value.addArrayElement(leftChannel); - value.addArrayElement(rightChannel); + value.addArrayElement(mapping.first.to_string()); + value.addArrayElement(mapping.second.to_string()); } auto json = choc::json::toString(value); return json; @@ -62,14 +54,13 @@ Structures::deserializeOutputMap(const std::string &data) { auto value = choc::json::parse(data); OutputMap outputMap; for (uint32_t i = 0; i < value.size(); i += 2) { - std::vector leftChannel; - std::vector rightChannel; - for (auto element : value[i]) { - leftChannel.push_back(element.getBool()); - } - for (auto element : value[i + 1]) { - rightChannel.push_back(element.getBool()); - } + auto leftChannelString = std::string(value[i].getString()); + auto leftChannel = + std::bitset(leftChannelString); + auto rightChannelString = std::string(value[i + 1].getString()); + auto rightChannel = + std::bitset(rightChannelString); + outputMap.push_back({leftChannel, rightChannel}); } return outputMap; diff --git a/DawPlugin/src/common.hpp b/DawPlugin/src/common.hpp index ad630fd3e..aa25b8d55 100644 --- a/DawPlugin/src/common.hpp +++ b/DawPlugin/src/common.hpp @@ -1,4 +1,6 @@ #pragma once +#include "DistrhoPluginInfo.h" +#include #include #include @@ -9,7 +11,9 @@ std::vector unBase64ToVector(const std::string &encoded); } // namespace Utils namespace Structures { -using OutputMap = std::vector, std::vector>>; +using OutputMap = + std::vector, + std::bitset>>; std::string serializeTrackNames(const std::vector &trackNames); std::vector deserializeTrackNames(const std::string &data); diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 8d563a492..2a7c791b1 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -12,34 +12,36 @@ #include #include #include +#include #include #include #include // std::jthread *ioThread = nullptr; -std::unique_ptr ioThread; -std::shared_ptr ioContext = nullptr; +namespace Network { +// std::jthread *ioThread = nullptr; +std::shared_ptr ioThread; +std::shared_ptr ioContext; std::shared_ptr getIoContext() { - if (!ioContext) { - ioContext = std::make_shared(); - ioThread = std::make_unique([](std::stop_token st) { + if (ioThread == nullptr) { + if (ioContext == nullptr) { + ioContext = std::make_shared(); + } + ioThread = std::make_shared([](std::stop_token st) { while (!st.stop_requested()) { - try { - ioContext->poll(); - } catch (asio::system_error &e) { - // ignore - } + ioContext->run(); } }); - std::atexit([]() { ioContext->stop(); ioThread->request_stop(); ioThread->join(); }); } + return ioContext; } +} // namespace Network // note: OpenUtau returns 44100Hz, 2ch, 32bit float audio @@ -57,19 +59,26 @@ OpenUtauPlugin::OpenUtauPlugin() this->mixes = std::vector>(); - auto currentTime = std::chrono::system_clock::now(); std::string uuid = uuid::v4::UUID::New().String(); setState("uuid", uuid.c_str()); setState("name", uuid.c_str()); - this->inUse = false; + + this->connected = false; } OpenUtauPlugin::~OpenUtauPlugin() { if (std::filesystem::exists(this->socketPath)) { std::filesystem::remove(this->socketPath); } - if (acceptor) { - acceptor->close(); + + if (this->acceptor != nullptr) { + this->acceptor->close(); + } + if (this->acceptorThread != nullptr) { + this->acceptorThread->request_stop(); + } + while (this->connected) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } @@ -232,7 +241,10 @@ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) { + // Constructor might be called during the initial loading of the plugin, so + // initialize the network in the run method initializeNetwork(); + auto timePosition = this->getTimePosition(); for (uint32_t i = 0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) { @@ -258,10 +270,22 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, auto frame = (i + timePosition.frame); for (uint32_t k = 0; k < DISTRHO_PLUGIN_NUM_OUTPUTS; ++k) { if (mapping.first[k] && frame < left.size()) { - outputs[k][i] += left[frame]; + if (outputs[k][i] > FLT_MAX - left[frame]) { + outputs[k][i] = FLT_MAX; + } else if (outputs[k][i] < -FLT_MAX + left[frame]) { + outputs[k][i] = -FLT_MAX; + } else { + outputs[k][i] += left[frame]; + } } if (mapping.second[k] && frame < right.size()) { - outputs[k][i] += right[frame]; + if (outputs[k][i] > FLT_MAX - right[frame]) { + outputs[k][i] = FLT_MAX; + } else if (outputs[k][i] < -FLT_MAX + right[frame]) { + outputs[k][i] = -FLT_MAX; + } else { + outputs[k][i] += right[frame]; + } } } } @@ -288,51 +312,84 @@ void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} void OpenUtauPlugin::sampleRateChanged(double newSampleRate) { resampleMixes(newSampleRate); } -void OpenUtauPlugin::onAccept(std::shared_ptr self, +void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, const asio::error_code &error, asio::ip::tcp::socket socket) { if (!error) { self->willAccept(); - if (!self->inUse) { - self->inUse = true; - socket.write_some(asio::buffer(formatMessage( - "init", choc::value::createObject("", "ustx", self->ustx)))); - std::string messageBuffer; - char buffer[16 * 1024]; - while (true) { - size_t len; - try { - len = socket.read_some(asio::buffer(buffer)); - } catch (asio::system_error &e) { - break; - } - messageBuffer.append(buffer, len); - - size_t pos; - while ((pos = messageBuffer.find('\n')) != std::string::npos) { - std::string message = messageBuffer.substr(0, pos); - messageBuffer.erase(0, pos + 1); - if (message == "close") { - socket.close(); - self->inUse = false; - return; - } - - size_t sep = message.find(' '); - std::string kind = message.substr(0, sep); - std::string payload = message.substr(sep + 1); - choc::value::Value value = choc::json::parse(payload); + if (!self->connected) { + self->connected = true; + self->acceptorThread = std::make_unique( + [self, socket = std::move(socket)](std::stop_token st) mutable { + socket.write_some(asio::buffer(formatMessage( + "init", choc::value::createObject("", "ustx", self->ustx)))); + std::string messageBuffer; + char buffer[16 * 1024]; + std::promise> readPromise; + auto readFuture = readPromise.get_future(); + bool timeout = false; + while (!st.stop_requested()) { + if (!timeout) { + readPromise = + std::promise>(); + readFuture = readPromise.get_future(); + socket.async_read_some( + asio::buffer(buffer), + [&](const asio::error_code &error, size_t len) { + if (error) { + readPromise.set_value(error); + } else { + readPromise.set_value(len); + } + }); + } + if (readFuture.wait_for(std::chrono::seconds(1)) == + std::future_status::timeout) { + timeout = true; + } else { + timeout = false; + auto result = readFuture.get(); + if (std::holds_alternative(result)) { + break; + } + auto len = std::get(result); + messageBuffer.append(buffer, len); + + size_t pos; + while ((pos = messageBuffer.find('\n')) != std::string::npos) { + std::string message = messageBuffer.substr(0, pos); + messageBuffer.erase(0, pos + 1); + if (message == "close") { + socket.close(); + self->connected = false; + return; + } + + size_t sep = message.find(' '); + std::string kind = message.substr(0, sep); + std::string payload = message.substr(sep + 1); + choc::value::Value value = choc::json::parse(payload); + + self->onMessage(kind, value); + } + } + + auto currentTime = std::chrono::system_clock::now(); + if (currentTime - self->lastPing > std::chrono::seconds(5)) { + socket.write_some(asio::buffer( + formatMessage("ping", choc::value::createObject("")))); + self->lastPing = currentTime; + } + } - self->onMessage(kind, value); - } - } + try { + socket.close(); + } catch (asio::system_error &e) { + // ignore + } - try { - socket.close(); - } catch (asio::system_error &e) { - // ignore - } - self->inUse = false; + self->connected = false; + }); } else { socket.write_some(asio::buffer(formatMessage( "error", @@ -344,24 +401,21 @@ void OpenUtauPlugin::onAccept(std::shared_ptr self, } void OpenUtauPlugin::willAccept() { - acceptor->async_accept(std::bind( - &OpenUtauPlugin::onAccept, std::shared_ptr(this), - std::placeholders::_1, std::placeholders::_2)); + acceptor->async_accept(std::bind(&OpenUtauPlugin::onAccept, this, + std::placeholders::_1, + std::placeholders::_2)); } void OpenUtauPlugin::initializeNetwork() { - // Constructor might be called during the initial loading of the plugin, so - // initialize the ioThread here. - if (!initializedNetwork) { - this->acceptor = std::make_shared( - getIoContext()->get_executor(), + if (!networkInitialized.exchange(true)) { + this->acceptor = std::make_unique( + Network::getIoContext()->get_executor(), asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 0)); - int port = acceptor->local_endpoint().port(); - this->port = port; + auto port = this->acceptor->local_endpoint().port(); + this->port = port; updatePluginServerFile(); - initializedNetwork = true; willAccept(); } } @@ -384,19 +438,20 @@ void OpenUtauPlugin::updatePluginServerFile() { void OpenUtauPlugin::onMessage(const std::string kind, const choc::value::Value payload) { if (kind == "status") { - this->lastSync = std::chrono::system_clock::now(); - - std::string ustx = payload["ustx"].get(); - setState("ustx", ustx.c_str()); - std::string mixesJson = choc::json::toString(payload["mixes"]); + auto _lock = std::lock_guard(this->statusMutex); + auto ustx = payload["ustx"].get(); + auto ustxBase64 = choc::base64::encodeToString(ustx); + setState("ustx", ustxBase64.c_str()); + auto mixesJson = choc::json::toString(payload["mixes"]); setState("mixes", mixesJson.c_str()); std::vector trackNames; for (auto track : payload["trackNames"]) { trackNames.push_back(track.get()); } setState("trackNames", Structures::serializeTrackNames(trackNames).c_str()); - syncMapping(); + + this->lastSync = std::chrono::system_clock::now(); } } @@ -408,30 +463,11 @@ void OpenUtauPlugin::syncMapping() { if (trackNames.size() < outputMap.size()) { outputMap.resize(trackNames.size()); } else if (trackNames.size() > outputMap.size()) { - bool customized = false; - auto defaultLeft = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); - auto defaultRight = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); - defaultLeft[0] = true; - defaultRight[1] = true; - for (const auto &mapping : outputMap) { - if (mapping.first != defaultLeft || mapping.second != defaultRight) { - customized = true; - break; - } - } for (size_t i = outputMap.size(); i < trackNames.size(); ++i) { - auto leftChannel = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); - auto rightChannel = std::vector(DISTRHO_PLUGIN_NUM_OUTPUTS, false); - if (customized) { - auto index = i % 16; - auto left = index * 2; - auto right = left + 1; - leftChannel[left] = true; - rightChannel[right] = true; - } else { - leftChannel[0] = true; - rightChannel[1] = true; - } + auto leftChannel = std::bitset(); + auto rightChannel = std::bitset(); + leftChannel[0] = true; + rightChannel[1] = true; outputMap.push_back({leftChannel, rightChannel}); } @@ -498,6 +534,14 @@ OpenUtauPlugin::formatMessage(const std::string &kind, return std::format("{} {}\n", kind, json); } +bool OpenUtauPlugin::isProcessing() { + if (this->statusMutex.try_lock()) { + this->statusMutex.unlock(); + return false; + } + return true; +} + /* ------------------------------------------------------------------------------------------------------------ * Plugin entry point, called by DPF to create a new plugin instance. */ diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index adf7b16e6..85f297056 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -1,11 +1,10 @@ #pragma once -#include "common.hpp" #include "DistrhoPlugin.hpp" #include "asio.hpp" #include "choc/containers/choc_Value.h" +#include "common.hpp" #include "extra/String.hpp" #include -#include #include #include @@ -25,13 +24,15 @@ class OpenUtauPlugin : public Plugin { ~OpenUtauPlugin() override; int port; - bool inUse; + bool connected; std::string name; std::optional> lastSync; std::vector trackNames; Structures::OutputMap outputMap; + bool isProcessing(); + protected: /* -------------------------------------------------------------------------------------------------------- * Information */ @@ -108,8 +109,7 @@ class OpenUtauPlugin : public Plugin { // ------------------------------------------------------------------------------------------------------- private: - static void onAccept(std::shared_ptr self, - const asio::error_code &error, + static void onAccept(OpenUtauPlugin *self, const asio::error_code &error, asio::ip::tcp::socket socket); void willAccept(); @@ -130,6 +130,8 @@ class OpenUtauPlugin : public Plugin { std::string ustx; std::string uuid; + std::chrono::time_point lastPing; + std::atomic writing = false; std::atomic readingCount = 0; @@ -139,8 +141,11 @@ class OpenUtauPlugin : public Plugin { std::filesystem::path socketPath; - bool initializedNetwork = false; - std::shared_ptr acceptor; + std::atomic networkInitialized = false; + std::unique_ptr acceptor; + std::unique_ptr acceptorThread; + + std::mutex statusMutex; /** Set our plugin class as non-copyable and add a leak detector just in case. diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index 04aa6d925..bce1e344e 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -12,6 +12,10 @@ START_NAMESPACE_DISTRHO int fontSize = 13.f; +// #ff679d +static auto themePinkColor = ImVec4(1.0f, 0.4f, 0.6f, 1.0f); +static auto themeBlueColor = ImVec4(0.3f, 0.7f, 0.9f, 1.0f); + class OpenUtauUI : public UI { public: /** @@ -73,9 +77,9 @@ class OpenUtauUI : public UI { ImGui::Begin("OpenUtau Bridge", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize); - ImGui::TextColored(style.Colors[ImGuiCol_ButtonActive], - "OpenUtau Bridge v%d.%d.%d", Constants::majorVersion, - Constants::minorVersion, Constants::patchVersion); + ImGui::TextColored(themePinkColor, "OpenUtau Bridge v%d.%d.%d", + Constants::majorVersion, Constants::minorVersion, + Constants::patchVersion); ImGui::Separator(); @@ -92,12 +96,12 @@ class OpenUtauUI : public UI { partiallyColoredText( std::format("Plugin identifier: [{} ({})]", plugin->name, plugin->port), - style.Colors[ImGuiCol_ButtonActive]); + themePinkColor); partiallyColoredText( - std::format("Connected: [{}]", plugin->inUse ? "Yes" : "No"), - plugin->inUse ? style.Colors[ImGuiCol_ButtonActive] - : style.Colors[ImGuiCol_TextDisabled]); + std::format("Connected: [{}]", plugin->connected ? "Yes" : "No"), + plugin->connected ? themePinkColor + : style.Colors[ImGuiCol_TextDisabled]); #ifdef DEBUG if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_D))) { @@ -108,26 +112,29 @@ class OpenUtauUI : public UI { } #endif - if (plugin->lastSync) { - auto lastSyncDuration = - std::chrono::duration_cast( - std::chrono::system_clock::now() - *plugin->lastSync) - .count(); - if (lastSyncDuration > 60) { - ImGui::Text("Last sync: %lldm ago", lastSyncDuration / 60); + ImGui::Text("Last sync: "); + ImGui::SameLine(0, 0); + + if (plugin->isProcessing()) { + if (plugin->lastSync) { + auto lastSyncDuration = + std::chrono::duration_cast( + std::chrono::system_clock::now() - *plugin->lastSync) + .count(); + if (lastSyncDuration > 60) { + ImGui::Text("%lldm ago", lastSyncDuration / 60); + } else { + ImGui::TextColored(themePinkColor, "%llds ago", lastSyncDuration); + } } else { - partiallyColoredText( - std::format("Last sync: [{}s ago]", lastSyncDuration), - style.Colors[ImGuiCol_ButtonActive]); + ImGui::TextColored(style.Colors[ImGuiCol_TextDisabled], "N/A"); } - } else { - partiallyColoredText("Last sync: [N/A]", - style.Colors[ImGuiCol_TextDisabled]); + ImGui::TextColored(themeBlueColor, "Processing"); } if (plugin->trackNames.size() > 0) { ImGui::Spacing(); - ImGui::TextColored(style.Colors[ImGuiCol_ButtonActive], "Track Mapping:"); + ImGui::TextColored(themePinkColor, "Track Mapping:"); if (ImGui::BeginTable("##track_mapping", DISTRHO_PLUGIN_NUM_OUTPUTS / 2 + 1, ImGuiTableFlags_Borders)) { @@ -196,10 +203,10 @@ class OpenUtauUI : public UI { ImGui::Text("1. Launch OpenUtau"); partiallyColoredText( "2. Click ['File'] > ['Connect to DAW...'] in OpenUtau", - style.Colors[ImGuiCol_ButtonActive]); + themePinkColor); partiallyColoredText(std::format("3. Select ['{} ({})'] in the list", plugin->name, plugin->port), - style.Colors[ImGuiCol_ButtonActive]); + themePinkColor); } ImGui::End(); @@ -214,7 +221,7 @@ class OpenUtauUI : public UI { ImGui::StyleColorsLight(); ImVec4 *colors = ImGui::GetStyle().Colors; // #ff679d - auto color = ImVec4(1.0f, 0.4f, 0.6f, 1.0f); + auto color = themePinkColor; auto darkColor = ImVec4(0.8f, 0.3f, 0.5f, 1.0f); auto lightColor = ImVec4(1.0f, 0.5f, 0.7f, 1.0f); colors[ImGuiCol_SliderGrab] = color; @@ -228,7 +235,7 @@ class OpenUtauUI : public UI { colors[ImGuiCol_PlotHistogramHovered] = darkColor; // #4ea6ea - auto hoverColor = ImVec4(0.3f, 0.7f, 0.9f, 1.0f); + auto hoverColor = themeBlueColor; colors[ImGuiCol_FrameBgHovered] = ImVec4(hoverColor.w, hoverColor.x, hoverColor.y, 0.20f); colors[ImGuiCol_FrameBgActive] = diff --git a/OpenUtau.Core/Format/USTx.cs b/OpenUtau.Core/Format/USTx.cs index e2127cfd3..277c26822 100644 --- a/OpenUtau.Core/Format/USTx.cs +++ b/OpenUtau.Core/Format/USTx.cs @@ -126,10 +126,16 @@ public static void AutoSave(string filePath, UProject project) { } public static UProject Load(string filePath) { - string text = File.ReadAllText(filePath, Encoding.UTF8); + var text = File.ReadAllText(filePath, Encoding.UTF8); + var project = LoadText(text); + project.FilePath = filePath; + + return project; + } + + public static UProject LoadText(string text) { UProject project = Yaml.DefaultDeserializer.Deserialize(text); AddDefaultExpressions(project); - project.FilePath = filePath; project.Saved = true; project.AfterLoad(); project.ValidateFull(); diff --git a/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs b/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs index ea9d247ec..500aba125 100644 --- a/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs +++ b/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs @@ -34,7 +34,7 @@ public async Task Connect() { var (client, ustx) = await Client.Connect(SelectedServer.Port); if (ustx.Length > 0) { - DocManager.Inst.ExecuteCmd(new LoadProjectNotification(Core.Format.Ustx.Load(ustx))); + DocManager.Inst.ExecuteCmd(new LoadProjectNotification(Core.Format.Ustx.LoadText(ustx))); } DocManager.Inst.dawClient = client; } finally { From b2215d26f8490cac3c7926c192f7996d5f393d77 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 23 Sep 2024 09:35:17 +0900 Subject: [PATCH 17/72] Improve: Send differences only --- .gitmodules | 6 + DawPlugin/CMakeLists.txt | 10 +- DawPlugin/deps/xxhash_cpp | 1 + DawPlugin/deps/yamc | 1 + DawPlugin/src/common.cpp | 41 +- DawPlugin/src/common.hpp | 17 +- DawPlugin/src/plugin.cpp | 513 ++++++++++++------ DawPlugin/src/plugin.hpp | 47 +- DawPlugin/src/ui.cpp | 7 +- .../{Client.cs => DawClient.cs} | 109 ++-- OpenUtau.Core/DawIntegration/DawManager.cs | 143 +++++ OpenUtau.Core/DawIntegration/DawMessages.cs | 143 +++++ .../{ServerFinder.cs => DawServerFinder.cs} | 14 +- OpenUtau.Core/DawIntegration/Messages.cs | 32 -- OpenUtau.Core/DocManager.cs | 72 --- OpenUtau.Core/Format/USTx.cs | 4 +- OpenUtau.Core/Util/Gzip.cs | 28 + .../DawIntegrationTerminalViewModel.cs | 10 +- OpenUtau/ViewModels/MainWindowViewModel.cs | 11 +- OpenUtau/Views/MainWindow.axaml.cs | 7 - compile_flags.txt | 2 + 21 files changed, 845 insertions(+), 373 deletions(-) create mode 160000 DawPlugin/deps/xxhash_cpp create mode 160000 DawPlugin/deps/yamc rename OpenUtau.Core/DawIntegration/{Client.cs => DawClient.cs} (56%) create mode 100644 OpenUtau.Core/DawIntegration/DawManager.cs create mode 100644 OpenUtau.Core/DawIntegration/DawMessages.cs rename OpenUtau.Core/DawIntegration/{ServerFinder.cs => DawServerFinder.cs} (84%) delete mode 100644 OpenUtau.Core/DawIntegration/Messages.cs create mode 100644 OpenUtau.Core/Util/Gzip.cs diff --git a/.gitmodules b/.gitmodules index 3ed4dfba7..5075ce03f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,9 @@ [submodule "DawPlugin/deps/uuid-v4"] path = DawPlugin/deps/uuid-v4 url = https://github.com/rkg82/uuid-v4.git +[submodule "DawPlugin/deps/yamc"] + path = DawPlugin/deps/yamc + url = https://github.com/yohhoy/yamc.git +[submodule "DawPlugin/deps/xxhash_cpp"] + path = DawPlugin/deps/xxhash_cpp + url = https://github.com/RedSpah/xxhash_cpp.git diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 691b42ad0..71c485102 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -38,11 +38,12 @@ dpf_add_plugin( src/ui.cpp deps/dpf_widgets/opengl/DearImGui.cpp) -# Including zlib before DPF causes "find_library" to fail with infinite +# Including other libraries before DPF causes "find_library" to fail with infinite # recursion, so we include it after DPF set(ZLIB_BUILD_EXAMPLES OFF) set(RENAME_ZCONF OFF) add_subdirectory(deps/zlib) +add_subdirectory(deps/xxhash_cpp) target_compile_features(openutau_daw_plugin PUBLIC cxx_std_20) target_include_directories( @@ -55,7 +56,8 @@ target_include_directories( "deps/dpf_widgets" "deps/uuid-v4" "deps/zlib" - "deps/gzip-hpp/include" - "deps/libfar/include") + "deps/xxhash_cpp/include" + "deps/yamc/include" + "deps/gzip-hpp/include") -target_link_libraries(openutau_daw_plugin PRIVATE zlibstatic) +target_link_libraries(openutau_daw_plugin PRIVATE zlibstatic xxhash_cpp) diff --git a/DawPlugin/deps/xxhash_cpp b/DawPlugin/deps/xxhash_cpp new file mode 160000 index 000000000..ad24e1c17 --- /dev/null +++ b/DawPlugin/deps/xxhash_cpp @@ -0,0 +1 @@ +Subproject commit ad24e1c174f855cfdebc22ad4e678fd3fee3b943 diff --git a/DawPlugin/deps/yamc b/DawPlugin/deps/yamc new file mode 160000 index 000000000..4e015a7e8 --- /dev/null +++ b/DawPlugin/deps/yamc @@ -0,0 +1 @@ +Subproject commit 4e015a7e8eb0d61c34e6928676c8c78881a72d73 diff --git a/DawPlugin/src/common.cpp b/DawPlugin/src/common.cpp index db5abbcfd..5fa777786 100644 --- a/DawPlugin/src/common.cpp +++ b/DawPlugin/src/common.cpp @@ -20,23 +20,42 @@ std::vector Utils::unBase64ToVector(const std::string &encoded) { return decoded; } -std::string -Structures::serializeTrackNames(const std::vector &trackNames) { +double Utils::dbToMultiplier(double db) { + return (db <= -24) ? 0 + : (db < -16) ? std::pow(10, (db * 2 + 16) / 20) + : std::pow(10, db / 20); +} + +choc::value::Value Structures::Track::serialize() const { + return choc::value::createObject("", "name", name, "pan", pan, "volume", + volume); +} +Structures::Track +Structures::Track::deserialize(const choc::value::ValueView &value) { + Track track; + track.name = value["name"].get(); + track.pan = value["pan"].get(); + track.volume = value["volume"].get(); + return track; +} + +std::string Structures::serializeTracks(const std::vector &tracks) { choc::value::Value value = choc::value::createEmptyArray(); - for (const auto &track : trackNames) { - value.addArrayElement(track); + for (const auto &track : tracks) { + value.addArrayElement(track.serialize()); } auto json = choc::json::toString(value); return choc::base64::encodeToString(json.data(), json.size()); } -std::vector -Structures::deserializeTrackNames(const std::string &data) { - auto value = choc::json::parse(Utils::unBase64ToString(data)); - std::vector trackNames; - for (auto element : value) { - trackNames.push_back(std::string(element.getString())); +std::vector +Structures::deserializeTracks(const std::string &data) { + auto json = Utils::unBase64ToString(data); + auto value = choc::json::parse(json); + std::vector tracks; + for (const auto &trackValue : value) { + tracks.push_back(Track::deserialize(trackValue)); } - return trackNames; + return tracks; } std::string Structures::serializeOutputMap(const OutputMap &outputMap) { diff --git a/DawPlugin/src/common.hpp b/DawPlugin/src/common.hpp index aa25b8d55..9f5a105cc 100644 --- a/DawPlugin/src/common.hpp +++ b/DawPlugin/src/common.hpp @@ -1,5 +1,6 @@ #pragma once #include "DistrhoPluginInfo.h" +#include "choc/containers/choc_Value.h" #include #include #include @@ -8,17 +9,29 @@ namespace Utils { std::vector gunzip(const char *data, size_t size); std::string unBase64ToString(const std::string &encoded); std::vector unBase64ToVector(const std::string &encoded); + +double dbToMultiplier(double db); } // namespace Utils namespace Structures { +class Track { +public: + std::string name; + double pan; + double volume; + + choc::value::Value serialize() const; + static Track deserialize(const choc::value::ValueView &value); +}; using OutputMap = std::vector, std::bitset>>; -std::string serializeTrackNames(const std::vector &trackNames); -std::vector deserializeTrackNames(const std::string &data); +std::string serializeTracks(const std::vector &tracks); +std::vector deserializeTracks(const std::string &data); std::string serializeOutputMap(const OutputMap &outputMap); OutputMap deserializeOutputMap(const std::string &data); + } // namespace Structures namespace Constants { diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 2a7c791b1..93b4e2ddc 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -1,4 +1,5 @@ #include "plugin.hpp" +#include "DistrhoDetails.hpp" #include "DistrhoPlugin.hpp" #include "DistrhoPluginInfo.h" #include "asio.hpp" @@ -13,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -45,7 +48,18 @@ std::shared_ptr getIoContext() { // note: OpenUtau returns 44100Hz, 2ch, 32bit float audio -START_NAMESPACE_DISTRHO +choc::value::Value Part::serialize() const { + return choc::value::createObject("", "trackNo", trackNo, "startMs", startMs, + "endMs", endMs, "audioHash", (int64_t)hash); +} +Part Part::deserialize(const choc::value::ValueView &value) { + Part part; + part.trackNo = value["trackNo"].get(); + part.startMs = value["startMs"].get(); + part.endMs = value["endMs"].get(); + part.hash = value["audioHash"].get(); + return part; +} // ----------------------------------------------------------------------------------------------------------- OpenUtauPlugin::OpenUtauPlugin() @@ -57,16 +71,20 @@ OpenUtauPlugin::OpenUtauPlugin() return; } - this->mixes = std::vector>(); - std::string uuid = uuid::v4::UUID::New().String(); setState("uuid", uuid.c_str()); setState("name", uuid.c_str()); this->connected = false; + + initializeNetwork(); } OpenUtauPlugin::~OpenUtauPlugin() { + if (this->isDummyInstance()) { + return; + } + if (std::filesystem::exists(this->socketPath)) { std::filesystem::remove(this->socketPath); } @@ -86,9 +104,9 @@ OpenUtauPlugin::~OpenUtauPlugin() { * Information */ /** - Get the plugin label. - This label is a short restricted name consisting of only _, a-z, A-Z - and 0-9 characters. + Get the plugin label. + This label is a short restricted name consisting + of only _, a-z, A-Z and 0-9 characters. */ const char *OpenUtauPlugin::getLabel() const { return "OpenUtau"; } @@ -121,16 +139,23 @@ void OpenUtauPlugin::initState(uint32_t index, State &state) { case 1: state.key = "ustx"; state.label = "USTx"; + state.hints = kStateIsBase64Blob; break; case 2: - state.key = "mixes"; - state.label = "Mixes"; + state.key = "audios"; + state.label = "Audios"; + state.hints = kStateIsBase64Blob; break; case 3: - state.key = "trackNames"; - state.label = "Track Names"; + state.key = "parts"; + state.label = "Parts"; break; case 4: + state.key = "tracks"; + state.label = "Tracks"; + state.hints = kStateIsBase64Blob; + break; + case 5: state.key = "mapping"; state.label = "Output Mapping"; break; @@ -148,18 +173,26 @@ String OpenUtauPlugin::getState(const char *rawKey) const { } else if (key == "ustx") { std::string encoded = choc::base64::encodeToString(ustx); return String(encoded.c_str()); - } else if (key == "mixes") { - choc::value::Value value = choc::value::createEmptyArray(); - for (const auto &mix : mixes) { + } else if (key == "audios") { + choc::value::Value value = choc::value::createObject(""); + for (const auto &[audioHash, audio] : audioBuffers) { std::string compressed = - gzip::compress((char *)mix.data(), mix.size() * sizeof(float)); + gzip::compress((char *)audio.data(), audio.size() * sizeof(float)); std::string encoded = choc::base64::encodeToString(compressed); - value.addArrayElement(encoded); + value.setMember(std::to_string(audioHash), encoded); } return String(choc::json::toString(value).c_str()); - } else if (key == "trackNames") { - return String(Structures::serializeTrackNames(trackNames).c_str()); + } else if (key == "parts") { + choc::value::Value value = choc::value::createEmptyArray(); + for (const auto &[trackNo, parts] : parts) { + for (const auto &part : parts) { + value.addArrayElement(part.serialize()); + } + } + return String(choc::json::toString(value).c_str()); + } else if (key == "tracks") { + return String(Structures::serializeTracks(tracks).c_str()); } else if (key == "mapping") { return String(Structures::serializeOutputMap(outputMap).c_str()); } @@ -175,32 +208,45 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { this->uuid = value; } else if (key == "ustx") { this->ustx = Utils::unBase64ToString(value); - } else if (key == "mixes") { - choc::value::Value jsonValue = choc::json::parse(std::string(value)); - std::vector> mixes; - for (choc::value::ValueView encodedValue : jsonValue) { - std::string encoded(encodedValue.getString()); - if (encoded.length() == 0) { - mixes.push_back(std::vector()); - continue; + } else if (key == "audios") { + choc::value::Value audioValue = choc::json::parse(value); + std::map> audioBuffers; + choc::value::ValueView(audioValue) + .visitObjectMembers( + [&](std::string_view key, const choc::value::ValueView &value) { + auto hash = std::stoul(std::string(key)); + std::string encoded = value.get(); + auto decoded = Utils::unBase64ToVector(encoded); + auto decompressed = + Utils::gunzip((char *)decoded.data(), decoded.size()); + std::vector audio((float *)decompressed.data(), + (float *)decompressed.data() + + decompressed.size() / sizeof(float)); + audioBuffers[hash] = audio; + }); + + { + auto _lock = std::lock_guard(this->audioBuffersMutex); + this->audioBuffers = audioBuffers; + } + this->requestResampleMixes(this->currentSampleRate); + } else if (key == "parts") { + choc::value::Value partsValue = choc::json::parse(value); + std::map> parts; + for (const auto &partValue : partsValue) { + Part part = Part::deserialize(partValue); + if (parts.find(part.trackNo) == parts.end()) { + parts[part.trackNo] = std::vector(); } - std::vector decoded = Utils::unBase64ToVector(encoded); - std::vector decompressed = - Utils::gunzip((char *)decoded.data(), decoded.size()); - std::vector mix((float *)decompressed.data(), - (float *)decompressed.data() + - decompressed.size() / sizeof(float)); - mixes.push_back(mix); + parts[part.trackNo].push_back(part); } - { - this->requestWrite(); - this->mixes = mixes; - this->doneWriting(); + auto _lock = std::lock_guard(this->partsMutex); + this->parts = parts; } - resampleMixes(this->currentSampleRate); - } else if (key == "trackNames") { - this->trackNames = Structures::deserializeTrackNames(value); + this->requestResampleMixes(this->currentSampleRate); + } else if (key == "tracks") { + this->tracks = Structures::deserializeTracks(value); syncMapping(); } else if (key == "mapping") { this->outputMap = Structures::deserializeOutputMap(value); @@ -225,8 +271,9 @@ uint32_t OpenUtauPlugin::getVersion() const { * Init */ /** - Initialize the audio port @a index.@n - This function will be called once, shortly after the plugin is created. + Initialize the audio port @a index.@n + This function will be called once, shortly after + the plugin is created. */ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, AudioPort &port) { @@ -241,9 +288,6 @@ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) { - // Constructor might be called during the initial loading of the plugin, so - // initialize the network in the run method - initializeNetwork(); auto timePosition = this->getTimePosition(); @@ -254,46 +298,62 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, } auto sampleRate = getSampleRate(); - if (this->resampledMixes.size() > 0 && timePosition.playing && - !this->writing.load()) { - this->readingCount++; + auto lock = std::shared_lock(this->mixMutex, std::defer_lock); + if (this->mixes.size() > 0 && timePosition.playing && lock.try_lock()) { if (this->currentSampleRate == sampleRate) { for (uint32_t j = 0; j < mixes.size(); ++j) { if (j >= this->outputMap.size()) { break; } + if (j >= this->tracks.size()) { + break; + } const auto &mapping = outputMap[j]; - const auto &left = resampledMixes[j].first; - const auto &right = resampledMixes[j].second; + const auto &left = mixes[j].first; + const auto &right = mixes[j].second; + + const auto &track = tracks[j]; for (uint32_t i = 0; i < frames; ++i) { auto frame = (i + timePosition.frame); + if (frame >= left.size()) { + break; + } + if (frame >= right.size()) { + break; + } + auto fadedLeft = left[frame] * Utils::dbToMultiplier(track.volume); + auto fadedRight = right[frame] * Utils::dbToMultiplier(track.volume); + if (track.pan < 0) { + fadedRight *= 1 + (track.pan / 100.0); + } else if (track.pan > 0) { + fadedLeft *= 1 - (track.pan / 100.0); + } for (uint32_t k = 0; k < DISTRHO_PLUGIN_NUM_OUTPUTS; ++k) { if (mapping.first[k] && frame < left.size()) { - if (outputs[k][i] > FLT_MAX - left[frame]) { + if (outputs[k][i] > FLT_MAX - fadedLeft) { outputs[k][i] = FLT_MAX; - } else if (outputs[k][i] < -FLT_MAX + left[frame]) { + } else if (outputs[k][i] < -FLT_MAX + fadedLeft) { outputs[k][i] = -FLT_MAX; } else { - outputs[k][i] += left[frame]; + outputs[k][i] += fadedLeft; } } if (mapping.second[k] && frame < right.size()) { - if (outputs[k][i] > FLT_MAX - right[frame]) { + if (outputs[k][i] > FLT_MAX - fadedRight) { outputs[k][i] = FLT_MAX; - } else if (outputs[k][i] < -FLT_MAX + right[frame]) { + } else if (outputs[k][i] < -FLT_MAX + fadedRight) { outputs[k][i] = -FLT_MAX; } else { - outputs[k][i] += right[frame]; + outputs[k][i] += fadedRight; } } } } } } else { - resampleMixes(sampleRate); + requestResampleMixes(sampleRate); } - this->readingCount--; } }; @@ -301,16 +361,17 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, * Callbacks (optional) */ /** - Optional callback to inform the plugin about a buffer size change. - This function will only be called when the plugin is deactivated. - @note This value is only a hint! - Hosts might call run() with a higher or lower number of - frames. + Optional callback to inform the plugin about a + buffer size change. This function will only be called when the plugin is + deactivated. + @note This value is only a hint! + Hosts might + call run() with a higher or lower number of frames. */ void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} void OpenUtauPlugin::sampleRateChanged(double newSampleRate) { - resampleMixes(newSampleRate); + requestResampleMixes(newSampleRate); } void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, const asio::error_code &error, @@ -320,9 +381,8 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, if (!self->connected) { self->connected = true; self->acceptorThread = std::make_unique( - [self, socket = std::move(socket)](std::stop_token st) mutable { - socket.write_some(asio::buffer(formatMessage( - "init", choc::value::createObject("", "ustx", self->ustx)))); + [self, socket = std::make_shared( + std::move(socket))](std::stop_token st) mutable { std::string messageBuffer; char buffer[16 * 1024]; std::promise> readPromise; @@ -333,7 +393,7 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, readPromise = std::promise>(); readFuture = readPromise.get_future(); - socket.async_read_some( + socket->async_read_some( asio::buffer(buffer), [&](const asio::error_code &error, size_t len) { if (error) { @@ -343,6 +403,9 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, } }); } + if (st.stop_requested()) { + break; + } if (readFuture.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) { timeout = true; @@ -360,30 +423,75 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, std::string message = messageBuffer.substr(0, pos); messageBuffer.erase(0, pos + 1); if (message == "close") { - socket.close(); + socket->close(); self->connected = false; return; } size_t sep = message.find(' '); - std::string kind = message.substr(0, sep); + std::string header = message.substr(0, sep); std::string payload = message.substr(sep + 1); + + size_t firstColon = header.find(':'); + + std::string messageType = header.substr(0, firstColon); choc::value::Value value = choc::json::parse(payload); - self->onMessage(kind, value); + if (messageType == "request") { + size_t secondColon = header.find(':', firstColon + 1); + + std::string messageId = header.substr( + firstColon + 1, secondColon - firstColon - 1); + std::string requestType = header.substr(secondColon + 1); + + self->threads[messageId] = + std::jthread([self, socket, messageId, requestType, + value](std::stop_token st) mutable { + choc::value::Value responseObj = + choc::value::createObject(""); + try { + auto response = self->onRequest(requestType, value); + responseObj.setMember("success", true); + responseObj.setMember("data", response); + } catch (std::exception &e) { + responseObj.setMember("success", false); + responseObj.setMember("error", e.what()); + } + + auto responseString = formatMessage( + std::format("response:{}", messageId), + responseObj); + socket->write_some(asio::buffer(responseString)); + + self->threads[messageId].detach(); + self->threads.erase(messageId); + }); + } else if (messageType == "notification") { + std::string notificationType = + header.substr(firstColon + 1); + auto messageId = uuid::v4::UUID::New().String(); + self->threads[messageId] = + std::jthread([self, socket, messageId, notificationType, + value](std::stop_token st) mutable { + self->onNotification(notificationType, value); + + self->threads[messageId].detach(); + self->threads.erase(messageId); + }); + } } } auto currentTime = std::chrono::system_clock::now(); if (currentTime - self->lastPing > std::chrono::seconds(5)) { - socket.write_some(asio::buffer( + socket->write_some(asio::buffer( formatMessage("ping", choc::value::createObject("")))); self->lastPing = currentTime; } } try { - socket.close(); + socket->close(); } catch (asio::system_error &e) { // ignore } @@ -391,10 +499,6 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, self->connected = false; }); } else { - socket.write_some(asio::buffer(formatMessage( - "error", - choc::value::createObject("", "message", - "Plugin is connected to another client")))); socket.close(); } } @@ -407,17 +511,14 @@ void OpenUtauPlugin::willAccept() { } void OpenUtauPlugin::initializeNetwork() { - if (!networkInitialized.exchange(true)) { - this->acceptor = std::make_unique( - Network::getIoContext()->get_executor(), - asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), - 0)); - - auto port = this->acceptor->local_endpoint().port(); - this->port = port; - updatePluginServerFile(); - willAccept(); - } + this->acceptor = std::make_unique( + Network::getIoContext()->get_executor(), + asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 0)); + + auto port = this->acceptor->local_endpoint().port(); + this->port = port; + updatePluginServerFile(); + willAccept(); } void OpenUtauPlugin::updatePluginServerFile() { @@ -435,98 +536,196 @@ void OpenUtauPlugin::updatePluginServerFile() { this->socketPath = socketPath; } -void OpenUtauPlugin::onMessage(const std::string kind, - const choc::value::Value payload) { - if (kind == "status") { - auto _lock = std::lock_guard(this->statusMutex); +choc::value::Value OpenUtauPlugin::onRequest(const std::string kind, + const choc::value::Value payload) { + if (kind == "init") { + choc::value::Value response = + choc::value::createObject("", "ustx", this->ustx); + return response; + } else if (kind == "updatePartLayout") { + auto _lock = std::lock_guard(this->partMutex); + std::map> parts; + std::vector flatParts; + std::set hashes; + for (const auto &part : payload["parts"]) { + flatParts.push_back(Part::deserialize(part)); + } + for (const auto &part : flatParts) { + if (parts.find(part.trackNo) == parts.end()) { + parts[part.trackNo] = std::vector(); + } + parts[part.trackNo].push_back(part); + hashes.insert(part.hash); + } + { + auto _lock = std::lock_guard(this->partsMutex); + this->parts = parts; + } + std::set toRemove; + std::set toAdd; + for (const auto &hash : this->audioBuffers) { + if (hashes.find(hash.first) == hashes.end()) { + toRemove.insert(hash.first); + } + } + for (const auto &hash : hashes) { + if (this->audioBuffers.find(hash) == this->audioBuffers.end()) { + toAdd.insert(hash); + } + } + for (const auto &hash : toRemove) { + this->audioBuffers.erase(hash); + } + + choc::value::Value response = choc::value::createObject(""); + auto missingAudios = choc::value::createEmptyArray(); + for (const auto &hash : toAdd) { + missingAudios.addArrayElement(std::to_string(hash)); + } + response.setMember("missingAudios", missingAudios); + + this->requestResampleMixes(this->currentSampleRate); + + return response; + } + + throw std::runtime_error("Unknown request type"); +} +void OpenUtauPlugin::onNotification(const std::string kind, + const choc::value::Value payload) { + if (kind == "updateUstx") { auto ustx = payload["ustx"].get(); auto ustxBase64 = choc::base64::encodeToString(ustx); setState("ustx", ustxBase64.c_str()); - auto mixesJson = choc::json::toString(payload["mixes"]); - setState("mixes", mixesJson.c_str()); - std::vector trackNames; - for (auto track : payload["trackNames"]) { - trackNames.push_back(track.get()); + } else if (kind == "updateTracks") { + auto _lock = std::lock_guard(this->tracksMutex); + auto tracks = std::vector(); + for (const auto &track : payload["tracks"]) { + tracks.push_back(Structures::Track::deserialize(track)); } - setState("trackNames", Structures::serializeTrackNames(trackNames).c_str()); + + this->tracks = tracks; syncMapping(); + } else if (kind == "updateAudio") { + { + auto _lock = std::lock_guard(this->audioBuffersMutex); + + std::map> audioBuffers; + payload["audios"].visitObjectMembers( + [&](std::string_view key, const choc::value::ValueView &value) { + auto hash = std::stoul(std::string(key)); + std::string encoded = value.get(); + auto decoded = Utils::unBase64ToVector(encoded); + auto decompressed = + Utils::gunzip((char *)decoded.data(), decoded.size()); + std::vector audio((float *)decompressed.data(), + (float *)decompressed.data() + + decompressed.size() / sizeof(float)); + audioBuffers[hash] = audio; + }); - this->lastSync = std::chrono::system_clock::now(); + this->audioBuffers = audioBuffers; + } + + this->requestResampleMixes(this->currentSampleRate); } } void OpenUtauPlugin::syncMapping() { - this->requestWrite(); - - auto trackNames = this->trackNames; - auto outputMap = this->outputMap; - if (trackNames.size() < outputMap.size()) { - outputMap.resize(trackNames.size()); - } else if (trackNames.size() > outputMap.size()) { - for (size_t i = outputMap.size(); i < trackNames.size(); ++i) { - auto leftChannel = std::bitset(); - auto rightChannel = std::bitset(); - leftChannel[0] = true; - rightChannel[1] = true; - - outputMap.push_back({leftChannel, rightChannel}); + { + auto _lock = std::unique_lock(this->mixMutex); + + auto tracks = this->tracks; + auto outputMap = this->outputMap; + if (tracks.size() < outputMap.size()) { + outputMap.resize(tracks.size()); + } else if (tracks.size() > outputMap.size()) { + for (size_t i = outputMap.size(); i < tracks.size(); ++i) { + auto leftChannel = std::bitset(); + auto rightChannel = std::bitset(); + leftChannel[0] = true; + rightChannel[1] = true; + + outputMap.push_back({leftChannel, rightChannel}); + } } - } - this->outputMap = outputMap; - this->doneWriting(); + this->outputMap = outputMap; + } setState("mapping", Structures::serializeOutputMap(outputMap).c_str()); } +void OpenUtauPlugin::requestResampleMixes(double newSampleRate) { + auto uuid = uuid::v4::UUID::New().String(); + this->threads[uuid] = std::jthread([this, uuid, newSampleRate]() { + this->resampleMixes(newSampleRate); + this->threads[uuid].detach(); + this->threads.erase(uuid); + }); +} + void OpenUtauPlugin::resampleMixes(double newSampleRate) { - requestWrite(); + auto _lock = std::unique_lock(this->mixMutex); + auto _lock2 = std::shared_lock(this->audioBuffersMutex); + auto _lock3 = std::shared_lock(this->partsMutex); - std::vector, std::vector>> resampledMixes; - for (const auto &mix : mixes) { + std::vector, std::vector>> mixes; + for (const auto &parts : this->parts) { std::vector resampledLeft; std::vector resampledRight; - resampledLeft.resize(mix.size() * newSampleRate / 44100.0 / 2 + 1); - resampledRight.resize(mix.size() * newSampleRate / 44100.0 / 2 + 1); - for (size_t i = 0; i < resampledLeft.size(); ++i) { - auto leftSource = i * 44100.0 / newSampleRate * 2; - auto rightSource = leftSource + 1; - auto leftLeftIndex = (size_t)leftSource; - auto rightLeftIndex = (size_t)rightSource; - auto leftRightIndex = leftLeftIndex + 2; - auto rightRightIndex = rightLeftIndex + 2; - auto fraction = leftSource - leftLeftIndex; - if (rightRightIndex < mix.size()) { - resampledLeft[i] = mix[leftLeftIndex] * (1 - fraction) + - mix[leftRightIndex] * fraction; - resampledRight[i] = mix[rightLeftIndex] * (1 - fraction) + - mix[rightRightIndex] * fraction; - } else if (rightLeftIndex < mix.size()) { - resampledLeft[i] = mix[leftLeftIndex]; - resampledRight[i] = mix[rightLeftIndex]; - } else { - resampledLeft[i] = 0; - resampledRight[i] = 0; + auto maxRightMs = std::max_element( + parts.second.begin(), parts.second.end(), + [](const Part &a, const Part &b) { return a.endMs < b.endMs; }); + resampledLeft.resize((size_t)(maxRightMs->endMs / 1000.0 * newSampleRate) + + 1); + resampledRight.resize((size_t)(maxRightMs->endMs / 1000.0 * newSampleRate) + + 1); + for (const auto &part : parts.second) { + auto startFrame = (size_t)(part.startMs / 1000.0 * newSampleRate); + auto endFrame = (size_t)(part.endMs / 1000.0 * newSampleRate); + auto rate = 44100.0 / newSampleRate; + if (audioBuffers.find(part.hash) == audioBuffers.end()) { + continue; + } + auto &buffer = audioBuffers[part.hash]; + + for (size_t i = startFrame; i < endFrame; ++i) { + auto frame = (size_t)(i * rate); + auto leftFrameLeft = frame * 2; + auto leftFrameRight = frame * 2 + 2; + auto rightFrameLeft = frame * 2 + 1; + auto rightFrameRight = frame * 2 + 3; + auto lerp = (i * rate) - frame; + if (rightFrameRight >= buffer.size()) { + break; + } + auto left = + (1 - lerp) * buffer[leftFrameLeft] + lerp * buffer[leftFrameRight]; + auto right = (1 - lerp) * buffer[rightFrameLeft] + + lerp * buffer[rightFrameRight]; + if (resampledLeft[i] > FLT_MAX - left) { + resampledLeft[i] = FLT_MAX; + } else if (resampledLeft[i] < -FLT_MAX + left) { + resampledLeft[i] = -FLT_MAX; + } else { + resampledLeft[i] += left; + } + if (resampledRight[i] > FLT_MAX - right) { + resampledRight[i] = FLT_MAX; + } else if (resampledRight[i] < -FLT_MAX + right) { + resampledRight[i] = -FLT_MAX; + } else { + resampledRight[i] += right; + } } } - resampledMixes.push_back({resampledLeft, resampledRight}); + mixes.push_back({resampledLeft, resampledRight}); } - this->resampledMixes = resampledMixes; + this->mixes = mixes; this->currentSampleRate = newSampleRate; - - doneWriting(); } -void OpenUtauPlugin::requestWrite() { - while (this->writing.exchange(true)) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - while (this->readingCount > 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } -} -void OpenUtauPlugin::doneWriting() { this->writing.store(false); } - std::string OpenUtauPlugin::formatMessage(const std::string &kind, const choc::value::ValueView &payload) { @@ -535,8 +734,8 @@ OpenUtauPlugin::formatMessage(const std::string &kind, } bool OpenUtauPlugin::isProcessing() { - if (this->statusMutex.try_lock()) { - this->statusMutex.unlock(); + if (this->partMutex.try_lock()) { + this->partMutex.unlock(); return false; } return true; @@ -545,8 +744,8 @@ bool OpenUtauPlugin::isProcessing() { /* ------------------------------------------------------------------------------------------------------------ * Plugin entry point, called by DPF to create a new plugin instance. */ +START_NAMESPACE_DISTRHO Plugin *createPlugin() { return new OpenUtauPlugin(); } +END_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- - -END_NAMESPACE_DISTRHO diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index 85f297056..a287970f1 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -1,21 +1,36 @@ #pragma once #include "DistrhoPlugin.hpp" +#include "alternate_shared_mutex.hpp" #include "asio.hpp" #include "choc/containers/choc_Value.h" #include "common.hpp" #include "extra/String.hpp" +#include "yamc_rwlock_sched.hpp" #include +#include #include +#include #include // note: OpenUtau returns 44100Hz, 2ch, 32bit float audio -START_NAMESPACE_DISTRHO +using AudioHash = uint32_t; +class Part { +public: + int trackNo; + double startMs; + double endMs; + + AudioHash hash; + + static Part deserialize(const choc::value::ValueView &value); + choc::value::Value serialize() const; +}; // ----------------------------------------------------------------------------------------------------------- /** - Plugin to show how to get some basic information sent to the UI. + Plugin to show how to get somebasic information sent to the UI. */ class OpenUtauPlugin : public Plugin { public: @@ -28,7 +43,7 @@ class OpenUtauPlugin : public Plugin { std::string name; std::optional> lastSync; - std::vector trackNames; + std::vector tracks; Structures::OutputMap outputMap; bool isProcessing(); @@ -116,36 +131,42 @@ class OpenUtauPlugin : public Plugin { void initializeNetwork(); - void onMessage(const std::string kind, const choc::value::Value payload); + choc::value::Value onRequest(const std::string kind, + const choc::value::Value payload); + void onNotification(const std::string kind, const choc::value::Value payload); static std::string formatMessage(const std::string &kind, const choc::value::ValueView &payload); void syncMapping(); void updatePluginServerFile(); + void requestResampleMixes(double newSampleRate); void resampleMixes(double newSampleRate); - void requestWrite(); - void doneWriting(); std::string ustx; std::string uuid; std::chrono::time_point lastPing; - std::atomic writing = false; - std::atomic readingCount = 0; + yamc::alternate::basic_shared_mutex audioBuffersMutex; + std::map> audioBuffers; - std::vector> mixes; - std::vector, std::vector>> resampledMixes; + yamc::alternate::basic_shared_mutex partsMutex; + std::map> parts; + + yamc::alternate::basic_shared_mutex mixMutex; + std::vector, std::vector>> mixes; double currentSampleRate = 44100.0; std::filesystem::path socketPath; - std::atomic networkInitialized = false; std::unique_ptr acceptor; std::unique_ptr acceptorThread; - std::mutex statusMutex; + std::unordered_map threads; + + yamc::alternate::basic_shared_mutex tracksMutex; + std::mutex partMutex; /** Set our plugin class as non-copyable and add a leak detector just in case. @@ -153,5 +174,3 @@ class OpenUtauPlugin : public Plugin { DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenUtauPlugin) }; // ----------------------------------------------------------------------------------------------------------- - -END_NAMESPACE_DISTRHO diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index bce1e344e..306ff2522 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -116,6 +116,8 @@ class OpenUtauUI : public UI { ImGui::SameLine(0, 0); if (plugin->isProcessing()) { + ImGui::TextColored(themeBlueColor, "Processing"); + } else { if (plugin->lastSync) { auto lastSyncDuration = std::chrono::duration_cast( @@ -129,10 +131,9 @@ class OpenUtauUI : public UI { } else { ImGui::TextColored(style.Colors[ImGuiCol_TextDisabled], "N/A"); } - ImGui::TextColored(themeBlueColor, "Processing"); } - if (plugin->trackNames.size() > 0) { + if (plugin->tracks.size() > 0) { ImGui::Spacing(); ImGui::TextColored(themePinkColor, "Track Mapping:"); if (ImGui::BeginTable("##track_mapping", @@ -151,7 +152,7 @@ class OpenUtauUI : public UI { ImGui::TableNextRow(); for (int lr = 0; lr < 2; lr++) { ImGui::TableNextColumn(); - ImGui::Text("%s: %s", plugin->trackNames[i].c_str(), + ImGui::Text("%s: %s", plugin->tracks[i].name.c_str(), lr == 0 ? "L" : "R"); for (int j = 0; j < DISTRHO_PLUGIN_NUM_OUTPUTS; j += 2) { ImGui::TableNextColumn(); diff --git a/OpenUtau.Core/DawIntegration/Client.cs b/OpenUtau.Core/DawIntegration/DawClient.cs similarity index 56% rename from OpenUtau.Core/DawIntegration/Client.cs rename to OpenUtau.Core/DawIntegration/DawClient.cs index e4b7783de..fce3e7717 100644 --- a/OpenUtau.Core/DawIntegration/Client.cs +++ b/OpenUtau.Core/DawIntegration/DawClient.cs @@ -11,24 +11,26 @@ using NAudio.Wave; using Newtonsoft.Json; using System.IO.Compression; +using NumSharp.Utilities; namespace OpenUtau.Core.DawIntegration { - public class Client { + public class DawClient { static int VERSION = 1; - private readonly int port; - private TcpClient tcpClient; + public readonly DawServer server; + private readonly TcpClient tcpClient; private Stream? stream; private Task? receiver; private CancellationTokenSource? cancellationTokenSource; private Dictionary> handlers = new Dictionary>(); private Dictionary> onetimeHandlers = new Dictionary>(); + readonly SemaphoreSlim writerSemaphore = new SemaphoreSlim(1, 1); - private Client(int port) { - this.port = port; + private DawClient(DawServer server) { + this.server = server; tcpClient = new TcpClient(); } - ~Client() { + ~DawClient() { tcpClient.Close(); cancellationTokenSource?.Cancel(); } @@ -48,7 +50,7 @@ await Task.Run(async () => { onetimeHandlers["disconnect"](""); break; } - currentMessageBuffer = currentMessageBuffer.Concat(buffer).ToArray(); + currentMessageBuffer = currentMessageBuffer.Concat(buffer.Slice(0, bytesRead)).ToArray(); while (currentMessageBuffer.Contains((byte)'\n')) { int index = Array.IndexOf(currentMessageBuffer, (byte)'\n'); string message = Encoding.UTF8.GetString(currentMessageBuffer.Take(index).ToArray()); @@ -73,72 +75,75 @@ await Task.Run(async () => { }); } - public static async Task<(Client, string)> Connect(int port) { - var client = new Client(port); - await client.tcpClient.ConnectAsync("127.0.0.1", port); + public static async Task<(DawClient, string)> Connect(DawServer server) { + var client = new DawClient(server); + await client.tcpClient.ConnectAsync("127.0.0.1", server.Port); client.stream = client.tcpClient.GetStream(); client.cancellationTokenSource = new CancellationTokenSource(); client.receiver = client.StartReceiver(client.cancellationTokenSource.Token); - var tcs = new TaskCompletionSource(); - client.RegisterOnetimeListener("init", (string message) => { - tcs.SetResult( - JsonConvert.DeserializeObject(message)! - ); - }); - client.RegisterOnetimeListener("error", (string message) => { - var messageContent = JsonConvert.DeserializeObject(message); - tcs.SetException( - new Exception($"Connection refused: {messageContent!.message}") - ); - }); - var timeoutCanceller = new CancellationTokenSource(); timeoutCanceller.CancelAfter(TimeSpan.FromSeconds(5)); - timeoutCanceller.Token.Register(() => tcs.TrySetCanceled()); - var initMessage = await tcs.Task; + var initMessage = await client.SendRequest(new InitRequest(), timeoutCanceller.Token); return (client, initMessage.ustx); } - public async Task SendStatus(UProject project, List 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()); - } + private async Task SendMessage(string header, DawMessage data) { + if (stream == null) { + throw new Exception("stream is null"); + } + await writerSemaphore.WaitAsync(); + try { + await stream.WriteAsync(Encoding.UTF8.GetBytes($"{header} {JsonConvert.SerializeObject(data)}\n")); + } finally { + writerSemaphore.Release(); + } + } + public async Task SendRequest(DawDawRequest data, + CancellationToken? token = null) where T : DawDawResponse { + if (stream == null) { + throw new Exception("stream is null"); + } + var uuid = Guid.NewGuid().ToString(); + + var tcs = new TaskCompletionSource>(); + token?.Register(() => tcs.TrySetCanceled()); + RegisterOnetimeListener($"response:{uuid}", (string message) => { + tcs.SetResult( + JsonConvert.DeserializeObject>(message)! + ); }); + await SendMessage($"request:{uuid}:{data.kind}", data); - var message = new UpdateStatusMessage( - ustx, - project.tracks.Select((t) => t.TrackName).ToList(), - base64Mixes.ToList() - ); + var result = await tcs.Task; + if (!result.success) { + throw new Exception($"DAW returned error to request {data.kind}: {result.error!}"); + } + if (result.data == null) { + throw new Exception("Unreachable: result.success && result.data == null"); + } - await SendMessage("status", message); + return result.data; } - - private async Task SendMessage(string kind, object json) { + public async Task SendNotification(DawDawNotification data, + CancellationToken? token = null) { if (stream == null) { throw new Exception("stream is null"); } - await stream.WriteAsync(Encoding.UTF8.GetBytes($"{kind} {JsonConvert.SerializeObject(json)}\n")); + await SendMessage($"notification:{data.kind}", data); + } + + public void RegisterNotification(string kind, Action handler) where T : DawOuNotification { + handlers[kind] = (string message) => { + handler(JsonConvert.DeserializeObject(message)!); + }; } - public void RegisterListener(string kind, Action handler) { + private void RegisterListener(string kind, Action handler) { handlers[kind] = handler; } - public void RegisterOnetimeListener(string kind, Action handler) { + private void RegisterOnetimeListener(string kind, Action handler) { onetimeHandlers[kind] = handler; } } diff --git a/OpenUtau.Core/DawIntegration/DawManager.cs b/OpenUtau.Core/DawIntegration/DawManager.cs new file mode 100644 index 000000000..ebf925856 --- /dev/null +++ b/OpenUtau.Core/DawIntegration/DawManager.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using K4os.Hash.xxHash; +using OpenUtau.Core.Render; +using OpenUtau.Core.Ustx; +using OpenUtau.Core.Util; +using Serilog; + +namespace OpenUtau.Core.DawIntegration { + public class DawManager : SingletonBase, ICmdSubscriber { + public DawClient? dawClient = null; + CancellationTokenSource? debounceCancellation = null; + CancellationTokenSource renderCancellation = null; + + private DawManager() { + DocManager.Inst.AddSubscriber(this); + } + + public void OnNext(UCommand cmd, bool isUndo) { + if (cmd is UNotification) { + return; + } + debounceCancellation?.Cancel(); + debounceCancellation = new CancellationTokenSource(); + + Task.Delay(TimeSpan.FromSeconds(5), debounceCancellation.Token) + .ContinueWith(async task => { + if (task.IsCompletedSuccessfully) { + await UpdateUstx(); + await UpdateTracks(); + await UpdateAudio(); + } + }); + } + + internal bool isDawClientLocked = false; + + private async Task UpdateUstx() { + if (dawClient == null) { + return; + } + + Log.Information("Updating ustx in DAW..."); + + try { + var ustx = Format.Ustx.FromProject(DocManager.Inst.Project); + await dawClient.SendNotification( + new UpdateUstxNotification(ustx) + ); + Log.Information("Sent ustx to DAW."); + } catch (Exception e) { + Log.Error(e, "Failed to send ustx to DAW."); + } + } + private async Task UpdateTracks() { + if (dawClient == null) { + return; + } + + Log.Information("Updating tracks in DAW..."); + + try { + await dawClient.SendNotification( + new UpdateTracksNotification( + DocManager.Inst.Project.tracks.Select(track => new UpdateTracksNotification.Track( + track.TrackName, + track.Volume, + track.Pan + )).ToList() + ) + ); + Log.Information("Sent tracks to DAW."); + } catch (Exception e) { + Log.Error(e, "Failed to send tracks to DAW."); + } + } + + + private async Task UpdateAudio() { + if (dawClient == null) { + return; + } + try { + var readyParts = DocManager.Inst.Project.parts.Where(part => part is UVoicePart uPart && uPart.Mix != null) + .Select(part => (part as UVoicePart)!) + .ToList(); + + Log.Information("Rendering prerenders for DAW..."); + var buffers = readyParts.Select(part => { + double startMs = DocManager.Inst.Project.timeAxis.TickPosToMsPos(part.position); + double endMs = DocManager.Inst.Project.timeAxis.TickPosToMsPos(part.position + part.duration); + int samplePos = (int)(startMs * 44100 / 1000) * 2; + int sampleCount = (int)((endMs - startMs) * 44100 / 1000) * 2; + var floatBuffer = new float[sampleCount]; + part.Mix.Mix(samplePos, floatBuffer, 0, sampleCount); + var byteBuffer = new byte[floatBuffer.Length * 4]; + Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length); + + var hash = XXH32.DigestOf(byteBuffer); + + return (part, startMs, endMs, byteBuffer, hash); + }); + Log.Information("Sending part layout to DAW..."); + var missingAudios = await dawClient.SendRequest( + new UpdatePartLayoutRequest( + buffers.Select(buffer => new UpdatePartLayoutRequest.Part( + buffer.part.trackNo, + buffer.startMs, + buffer.endMs, + buffer.hash + )).ToList() + ) + ); + Log.Information("Sent part layout to DAW."); + + if (missingAudios.missingAudios.Count > 0) { + Log.Information($"DAW requested {missingAudios.missingAudios.Count} missing audios."); + var buffersDict = buffers.ToDictionary(buffer => buffer.hash); + var audios = new Dictionary(); + foreach (var audioHash in missingAudios.missingAudios) { + var buffer = buffersDict[audioHash]; + + var compressed = Gzip.Compress(buffer.byteBuffer); + audios[audioHash] = Convert.ToBase64String(compressed); + } + + await dawClient.SendNotification( + new UpdateAudioNotification(audios) + ); + Log.Information("Sent missing audios to DAW."); + } else { + Log.Information("Audios in DAW are up to date."); + } + } catch (Exception e) { + Log.Error(e, "Failed to send status to DAW."); + } + } + } +} diff --git a/OpenUtau.Core/DawIntegration/DawMessages.cs b/OpenUtau.Core/DawIntegration/DawMessages.cs new file mode 100644 index 000000000..919ac53e8 --- /dev/null +++ b/OpenUtau.Core/DawIntegration/DawMessages.cs @@ -0,0 +1,143 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace OpenUtau.Core.DawIntegration { + public abstract class DawMessage { + } + + /// + /// OpenUtau to DAW notification. + /// Does not require a response. + /// + public abstract class DawDawNotification : DawMessage { + [JsonIgnore] + public abstract string kind { get; } + } + /// + /// DAW to OpenUtau notification. + /// + public abstract class DawOuNotification : DawMessage { + } + /// + /// OpenUtau to DAW request. + /// + public abstract class DawDawRequest : DawMessage { + [JsonIgnore] + public abstract string kind { get; } + } + /// + /// OpenUtau to DAW response. + /// + public class DawDawResponse : DawMessage { + } + + /// + /// DAW to OpenUtau request. + /// + public abstract class DawOuRequest : DawMessage { + } + /// + /// DAW to OpenUtau response. + /// + public class DawOuResponse : DawMessage { + } + + public class DawResult : DawMessage where T : DawDawResponse { + public bool success; + public T? data; + public string? error; + + public DawResult(bool success, T? data, string? error) { + this.success = success; + this.data = data; + this.error = error; + } + } + + + public class InitRequest : DawDawRequest { + public InitRequest() { + } + public override string kind => "init"; + } + public class InitResponse : DawDawResponse { + public string ustx; + + public InitResponse(string ustx) { + this.ustx = ustx; + } + } + public class UpdateUstxNotification : DawDawNotification { + public string ustx; + + public UpdateUstxNotification(string ustx) { + this.ustx = ustx; + } + + public override string kind => "updateUstx"; + } + public class UpdatePartLayoutRequest : DawDawRequest { + public List parts; + + public override string kind => "updatePartLayout"; + + public UpdatePartLayoutRequest(List parts) { + this.parts = parts; + } + + public class Part { + public int trackNo; + public double startMs; + public double endMs; + public uint audioHash; + + public Part(int trackNo, double startMs, double endMs, uint audioHash) { + this.trackNo = trackNo; + this.startMs = startMs; + this.endMs = endMs; + this.audioHash = audioHash; + } + } + } + + public class UpdatePartLayoutResponse : DawDawResponse { + public List missingAudios; + + public UpdatePartLayoutResponse(List missingAudios) { + this.missingAudios = missingAudios; + } + + } + + public class UpdateTracksNotification : DawDawNotification { + public List tracks; + + public override string kind => "updateTracks"; + + public UpdateTracksNotification(List tracks) { + this.tracks = tracks; + } + + public class Track { + public string name; + public double volume; + public double pan; + + public Track(string name, double volume, double pan) { + this.name = name; + this.volume = volume; + this.pan = pan; + } + } + } + + public class UpdateAudioNotification : DawDawNotification { + public Dictionary audios; + + public override string kind => "updateAudio"; + + public UpdateAudioNotification(Dictionary audios) { + this.audios = audios; + } + } +} diff --git a/OpenUtau.Core/DawIntegration/ServerFinder.cs b/OpenUtau.Core/DawIntegration/DawServerFinder.cs similarity index 84% rename from OpenUtau.Core/DawIntegration/ServerFinder.cs rename to OpenUtau.Core/DawIntegration/DawServerFinder.cs index f10f2e6a2..9030c0fbc 100644 --- a/OpenUtau.Core/DawIntegration/ServerFinder.cs +++ b/OpenUtau.Core/DawIntegration/DawServerFinder.cs @@ -6,26 +6,26 @@ using Newtonsoft.Json; namespace OpenUtau.Core.DawIntegration { - public class ServerFinder { + public class DawServerFinder { private static string getServerPath() { var temp = Path.GetTempPath(); return $"{temp}OpenUtau/PluginServers"; } - public static async Task> FindServers() { + public static async Task> FindServers() { var path = getServerPath(); if (!Directory.Exists(path)) { - return new List(); + return new List(); } var di = new DirectoryInfo(path); var files = di.GetFiles("*.json"); - var servers = new List(); + var servers = new List(); foreach (FileInfo file in files) { try { var json = await File.ReadAllTextAsync(file.FullName); - var server = JsonConvert.DeserializeObject(json); + var server = JsonConvert.DeserializeObject(json); if (server != null && CheckPortUsing(server.Port)) { servers.Add(server); continue; @@ -60,12 +60,12 @@ private static bool CheckPortUsing(int port) { } } - public class Server { + public class DawServer { public int Port { get; } public string Name { get; } [JsonConstructor] - Server(int port, string name) { + DawServer(int port, string name) { Port = port; Name = name; } diff --git a/OpenUtau.Core/DawIntegration/Messages.cs b/OpenUtau.Core/DawIntegration/Messages.cs deleted file mode 100644 index 0d0e198eb..000000000 --- a/OpenUtau.Core/DawIntegration/Messages.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; - -namespace OpenUtau.Core.DawIntegration { - public class DawMessage { - } - - public class InitMessage : DawMessage { - public string ustx; - - public InitMessage(string ustx) { - this.ustx = ustx; - } - } - public class ErrorMessage : DawMessage { - public string message; - - public ErrorMessage(string message) { - this.message = message; - } - } - public class UpdateStatusMessage : DawMessage { - public string ustx; - public List trackNames; - public List mixes; - - public UpdateStatusMessage(string ustx, List trackNames, List mixes) { - this.ustx = ustx; - this.trackNames = trackNames; - this.mixes = mixes; - } - } -} diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 31ac3721f..ad99fe96a 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -9,7 +9,6 @@ using OpenUtau.Api; using OpenUtau.Classic; using OpenUtau.Core.Lib; -using OpenUtau.Core.Render; using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; using Serilog; @@ -22,34 +21,6 @@ public struct ValidateOptions { public bool SkipPhoneme; } - internal struct DawUpdatePoint : IEquatable { - 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() { Project = new UProject(); @@ -69,7 +40,6 @@ public class DocManager : SingletonBase { public List PartsClipboard { get; set; } public List NotesClipboard { get; set; } internal PhonemizerRunner PhonemizerRunner { get; private set; } - public DawIntegration.Client? dawClient { get; set; } public void Initialize() { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler((sender, args) => { @@ -210,48 +180,6 @@ public void AutoSave() { } - internal bool isDawClientLocked = false; - internal DawUpdatePoint? lastDawStatusUpdateCheckPoint = null; - internal DawUpdatePoint? lastDawStatusUpdatePoint = null; - - 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; - } - if (lastDawStatusUpdatePoint == currentPoint) { - Log.Information("Already sent status, skipping sending status to DAW."); - return; - } - lastDawStatusUpdatePoint = currentPoint; - 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)) { diff --git a/OpenUtau.Core/Format/USTx.cs b/OpenUtau.Core/Format/USTx.cs index 277c26822..6fb9721eb 100644 --- a/OpenUtau.Core/Format/USTx.cs +++ b/OpenUtau.Core/Format/USTx.cs @@ -95,7 +95,7 @@ public static UProject Create() { public static void Save(string filePath, UProject project) { try { - var ustx = CreateUstx(project); + var ustx = FromProject(project); project.FilePath = filePath; File.WriteAllText(filePath, ustx, Encoding.UTF8); } catch (Exception ex) { @@ -104,7 +104,7 @@ public static void Save(string filePath, UProject project) { } } - public static string CreateUstx(UProject project) { + public static string FromProject(UProject project) { project.ustxVersion = kUstxVersion; project.BeforeSave(); var ustx = Yaml.DefaultSerializer.Serialize(project); diff --git a/OpenUtau.Core/Util/Gzip.cs b/OpenUtau.Core/Util/Gzip.cs new file mode 100644 index 000000000..48ffe2037 --- /dev/null +++ b/OpenUtau.Core/Util/Gzip.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenUtau.Core.Util { + public static class Gzip + { + public static byte[] Compress(byte[] data) { + using (var compressedStream = new System.IO.MemoryStream()) { + using (var zipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Compress)) { + zipStream.Write(data, 0, data.Length); + } + return compressedStream.ToArray(); + } + } + + public static byte[] Decompress(byte[] data) { + using (var compressedStream = new System.IO.MemoryStream(data)) { + using (var zipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Decompress)) { + using (var resultStream = new System.IO.MemoryStream()) { + zipStream.CopyTo(resultStream); + return resultStream.ToArray(); + } + } + } + } + } +} diff --git a/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs b/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs index 500aba125..3fc038ae5 100644 --- a/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs +++ b/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs @@ -6,16 +6,16 @@ namespace OpenUtau.App.ViewModels { public class DawIntegrationTerminalViewModel : ViewModelBase { - [Reactive] public Server? SelectedServer { get; set; } = null; + [Reactive] public DawServer? SelectedServer { get; set; } = null; [Reactive] public bool CanConnect { get; set; } = true; - public ObservableCollectionExtended ServerList { get; set; } = new ObservableCollectionExtended(); + public ObservableCollectionExtended ServerList { get; set; } = new ObservableCollectionExtended(); public DawIntegrationTerminalViewModel() { Task.Run(() => RefreshServerList()); } public async Task RefreshServerList() { - var servers = await ServerFinder.FindServers(); + var servers = await DawServerFinder.FindServers(); ServerList.Load(servers); if (servers.Count == 0) { @@ -31,12 +31,12 @@ public async Task Connect() { } try { CanConnect = false; - var (client, ustx) = await Client.Connect(SelectedServer.Port); + var (client, ustx) = await DawClient.Connect(SelectedServer); if (ustx.Length > 0) { DocManager.Inst.ExecuteCmd(new LoadProjectNotification(Core.Format.Ustx.LoadText(ustx))); } - DocManager.Inst.dawClient = client; + DawManager.Inst.dawClient = client; } finally { CanConnect = true; } diff --git a/OpenUtau/ViewModels/MainWindowViewModel.cs b/OpenUtau/ViewModels/MainWindowViewModel.cs index 1cccde093..994f9ac26 100644 --- a/OpenUtau/ViewModels/MainWindowViewModel.cs +++ b/OpenUtau/ViewModels/MainWindowViewModel.cs @@ -8,6 +8,7 @@ using DynamicData.Binding; using OpenUtau.App.Views; using OpenUtau.Core; +using OpenUtau.Core.DawIntegration; using OpenUtau.Core.Ustx; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -28,14 +29,14 @@ public class MainWindowViewModel : ViewModelBase, ICmdSubscriber { public bool ExtendToFrame => OS.IsMacOS(); public string Title { get { - var baseTitle = !ProjectSaved - ? $"{AppVersion}" - : $"{(DocManager.Inst.ChangesSaved ? "" : "*")}{AppVersion} [{DocManager.Inst.Project.FilePath}]"; + if (DawManager.Inst.dawClient == null) { + var baseTitle = !ProjectSaved + ? $"{AppVersion}" + : $"{(DocManager.Inst.ChangesSaved ? "" : "*")}{AppVersion} [{DocManager.Inst.Project.FilePath}]"; - if (DocManager.Inst.dawClient == null) { return baseTitle; } else { - return $"{baseTitle} (Attached to DAW)"; + return $"{AppVersion} [{DawManager.Inst.dawClient.server.Name}] (Attached to DAW)"; } } } diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 084dffcff..404ef4363 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -40,7 +40,6 @@ public partial class MainWindow : Window, ICmdSubscriber { private PartEditState? partEditState; private readonly DispatcherTimer timer; private readonly DispatcherTimer autosaveTimer; - private readonly DispatcherTimer dawUpdateTimer; private bool forceClose; private bool shouldOpenPartsContextMenu; @@ -82,12 +81,6 @@ public MainWindow() { (sender, args) => DocManager.Inst.AutoSave()); autosaveTimer.Start(); - dawUpdateTimer = new DispatcherTimer( - TimeSpan.FromSeconds(1), - DispatcherPriority.Normal, - (sender, args) => Task.Run(() => DocManager.Inst.UpdateDaw())); - dawUpdateTimer.Start(); - PartRenameCommand = ReactiveCommand.Create(part => RenamePart(part)); PartGotoFileCommand = ReactiveCommand.Create(part => GotoFile(part)); PartReplaceAudioCommand = ReactiveCommand.Create(part => ReplaceAudio(part)); diff --git a/compile_flags.txt b/compile_flags.txt index 5fecb1c6a..115e87528 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -9,4 +9,6 @@ -I./DawPlugin/deps/gzip-hpp/include -I./DawPlugin/deps/libfar/include -I./DawPlugin/deps/uuid-v4 +-I./DawPlugin/deps/xxhash_cpp/include +-I./DawPlugin/deps/yamc/include -I./DawPlugin/deps/ From 15ddf1829cc9796a8e8e289e677c4592467f9d4e Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 23 Sep 2024 13:15:48 +0900 Subject: [PATCH 18/72] Fix: Fix position --- DawPlugin/src/plugin.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 93b4e2ddc..a308df12a 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -95,6 +95,12 @@ OpenUtauPlugin::~OpenUtauPlugin() { if (this->acceptorThread != nullptr) { this->acceptorThread->request_stop(); } + for (auto &[_, thread] : this->threads) { + thread.request_stop(); + } + for (auto &[_, thread] : this->threads) { + thread.join(); + } while (this->connected) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } @@ -586,6 +592,10 @@ choc::value::Value OpenUtauPlugin::onRequest(const std::string kind, this->requestResampleMixes(this->currentSampleRate); + if (toAdd.size() == 0) { + this->lastSync = std::chrono::system_clock::now(); + } + return response; } @@ -627,6 +637,7 @@ void OpenUtauPlugin::onNotification(const std::string kind, this->audioBuffers = audioBuffers; } + this->lastSync = std::chrono::system_clock::now(); this->requestResampleMixes(this->currentSampleRate); } } @@ -670,17 +681,17 @@ void OpenUtauPlugin::resampleMixes(double newSampleRate) { auto _lock3 = std::shared_lock(this->partsMutex); std::vector, std::vector>> mixes; - for (const auto &parts : this->parts) { + for (const auto &[_, parts] : this->parts) { std::vector resampledLeft; std::vector resampledRight; - auto maxRightMs = std::max_element( - parts.second.begin(), parts.second.end(), + auto maxEndMs = std::max_element( + parts.begin(), parts.end(), [](const Part &a, const Part &b) { return a.endMs < b.endMs; }); - resampledLeft.resize((size_t)(maxRightMs->endMs / 1000.0 * newSampleRate) + + resampledLeft.resize((size_t)(maxEndMs->endMs / 1000.0 * newSampleRate) + 1); - resampledRight.resize((size_t)(maxRightMs->endMs / 1000.0 * newSampleRate) + + resampledRight.resize((size_t)(maxEndMs->endMs / 1000.0 * newSampleRate) + 1); - for (const auto &part : parts.second) { + for (const auto &part : parts) { auto startFrame = (size_t)(part.startMs / 1000.0 * newSampleRate); auto endFrame = (size_t)(part.endMs / 1000.0 * newSampleRate); auto rate = 44100.0 / newSampleRate; @@ -690,12 +701,12 @@ void OpenUtauPlugin::resampleMixes(double newSampleRate) { auto &buffer = audioBuffers[part.hash]; for (size_t i = startFrame; i < endFrame; ++i) { - auto frame = (size_t)(i * rate); + auto frame = (size_t)((i - startFrame) * rate); auto leftFrameLeft = frame * 2; auto leftFrameRight = frame * 2 + 2; auto rightFrameLeft = frame * 2 + 1; auto rightFrameRight = frame * 2 + 3; - auto lerp = (i * rate) - frame; + auto lerp = ((i - startFrame) * rate) - frame; if (rightFrameRight >= buffer.size()) { break; } @@ -734,11 +745,11 @@ OpenUtauPlugin::formatMessage(const std::string &kind, } bool OpenUtauPlugin::isProcessing() { - if (this->partMutex.try_lock()) { - this->partMutex.unlock(); - return false; + if (!this->mixMutex.try_lock()) { + return true; } - return true; + this->mixMutex.unlock(); + return false; } /* ------------------------------------------------------------------------------------------------------------ From 4911be1e2d11fab5cdc54e7207bdc175d26c9f1c Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 23 Sep 2024 14:45:42 +0900 Subject: [PATCH 19/72] Fix: Fix deadlock --- .editorconfig | 114 +++++---- DawPlugin/src/plugin.cpp | 270 +++++++++++---------- DawPlugin/src/plugin.hpp | 2 +- OpenUtau.Core/Commands/Notifications.cs | 3 + OpenUtau.Core/DawIntegration/DawClient.cs | 26 +- OpenUtau.Core/DawIntegration/DawManager.cs | 46 ++-- OpenUtau.Core/PlaybackManager.cs | 4 +- OpenUtau.Core/Util/Debounce.cs | 23 ++ OpenUtau/ViewModels/MainWindowViewModel.cs | 9 +- OpenUtau/Views/MainWindow.axaml | 3 +- OpenUtau/Views/MainWindow.axaml.cs | 5 + 11 files changed, 314 insertions(+), 191 deletions(-) create mode 100644 OpenUtau.Core/Util/Debounce.cs diff --git a/.editorconfig b/.editorconfig index a219353e4..433a8f65d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ indent_style = space # Code files [*.{cs,csx,vb,vbx}] -indent_size = 4 +indent_size = 4 insert_final_newline = true charset = utf-8-bom guidelines = 100 @@ -18,74 +18,83 @@ guidelines = 100 # Organize usings dotnet_sort_system_directives_first = true # this. preferences -dotnet_style_qualification_for_field = false:silent -dotnet_style_qualification_for_property = false:silent -dotnet_style_qualification_for_method = false:silent -dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members = true:silent dotnet_style_predefined_type_for_member_access = true:silent # Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent # Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent -dotnet_style_readonly_field = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion # Expression-level preferences -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent dotnet_prefer_inferred_tuple_names = true:suggestion dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_auto_properties = true:silent -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent ############################### # Naming Conventions # ############################### # Style Definitions dotnet_naming_style.pascal_case_style.capitalization = pascal_case # Use PascalCase for constant fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_accessibilities = * dotnet_naming_symbols.constant_fields.required_modifiers = const +tab_width = 4 +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion ############################### # C# Coding Conventions # ############################### [*.cs] # var preferences -csharp_style_var_for_built_in_types = true:silent -csharp_style_var_when_type_is_apparent = true:silent -csharp_style_var_elsewhere = true:silent +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent # Expression-bodied members -csharp_style_expression_bodied_methods = false:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_operators = false:silent -csharp_style_expression_bodied_properties = true:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent # Pattern matching preferences -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion # Null-checking preferences -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion # Expression-level preferences -csharp_prefer_braces = true:silent -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_prefer_simple_default_expression = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion ############################### # C# Formatting Rules # ############################### @@ -100,7 +109,7 @@ csharp_new_line_between_query_expression_clauses = false # Indentation preferences csharp_indent_case_contents = true csharp_indent_switch_labels = true -csharp_indent_labels = flush_left +csharp_indent_labels = flush_left # Space preferences csharp_space_after_cast = false csharp_space_after_keywords_in_control_flow_statements = true @@ -109,13 +118,36 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_after_colon_in_inheritance_clause = true -csharp_space_around_binary_operators = before_and_after +csharp_space_around_binary_operators = before_and_after csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_empty_parameter_list_parentheses = false # Wrapping preferences csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_static_anonymous_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion ############################### # VB Coding Conventions # ############################### diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index a308df12a..a6b742be5 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -49,15 +49,25 @@ std::shared_ptr getIoContext() { // note: OpenUtau returns 44100Hz, 2ch, 32bit float audio choc::value::Value Part::serialize() const { - return choc::value::createObject("", "trackNo", trackNo, "startMs", startMs, - "endMs", endMs, "audioHash", (int64_t)hash); + auto obj = choc::value::createObject("", "trackNo", trackNo, "startMs", + startMs, "endMs", endMs); + if (hash.has_value()) { + // choc cannot set uint32_t, so we need to cast it to int64_t + obj.setMember("audioHash", (int64_t)hash.value()); + } + + return obj; } Part Part::deserialize(const choc::value::ValueView &value) { Part part; part.trackNo = value["trackNo"].get(); part.startMs = value["startMs"].get(); part.endMs = value["endMs"].get(); - part.hash = value["audioHash"].get(); + if (value.hasObjectMember("audioHash")) { + part.hash = value["audioHash"].get(); + } else { + part.hash = std::nullopt; + } return part; } @@ -110,9 +120,9 @@ OpenUtauPlugin::~OpenUtauPlugin() { * Information */ /** - Get the plugin label. - This label is a short restricted name consisting - of only _, a-z, A-Z and 0-9 characters. + Get the plugin + label. This label is a short restricted name consisting of only _, a-z, A-Z + and 0-9 characters. */ const char *OpenUtauPlugin::getLabel() const { return "OpenUtau"; } @@ -252,7 +262,10 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { } this->requestResampleMixes(this->currentSampleRate); } else if (key == "tracks") { - this->tracks = Structures::deserializeTracks(value); + { + auto _lock = std::lock_guard(this->tracksMutex); + this->tracks = Structures::deserializeTracks(value); + } syncMapping(); } else if (key == "mapping") { this->outputMap = Structures::deserializeOutputMap(value); @@ -277,9 +290,9 @@ uint32_t OpenUtauPlugin::getVersion() const { * Init */ /** - Initialize the audio port @a index.@n - This function will be called once, shortly after - the plugin is created. + Initialize the + audio port @a index.@n This function will be called once, shortly after the + plugin is created. */ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, AudioPort &port) { @@ -367,12 +380,12 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, * Callbacks (optional) */ /** - Optional callback to inform the plugin about a - buffer size change. This function will only be called when the plugin is - deactivated. - @note This value is only a hint! - Hosts might - call run() with a higher or lower number of frames. + Optional callback + to inform the plugin about a buffer size change. This function will only be + called when the plugin is deactivated. + @note This value + is only a hint! Hosts might call run() with a higher or lower number of + frames. */ void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} @@ -391,111 +404,113 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, std::move(socket))](std::stop_token st) mutable { std::string messageBuffer; char buffer[16 * 1024]; - std::promise> readPromise; + std::promise readPromise; auto readFuture = readPromise.get_future(); bool timeout = false; - while (!st.stop_requested()) { - if (!timeout) { - readPromise = - std::promise>(); - readFuture = readPromise.get_future(); - socket->async_read_some( - asio::buffer(buffer), - [&](const asio::error_code &error, size_t len) { - if (error) { - readPromise.set_value(error); - } else { - readPromise.set_value(len); - } - }); - } - if (st.stop_requested()) { - break; - } - if (readFuture.wait_for(std::chrono::seconds(1)) == - std::future_status::timeout) { - timeout = true; - } else { - timeout = false; - auto result = readFuture.get(); - if (std::holds_alternative(result)) { + try { + while (!st.stop_requested()) { + if (!timeout) { + readPromise = std::promise(); + readFuture = readPromise.get_future(); + socket->async_read_some( + asio::buffer(buffer), + [&](const asio::error_code &error, size_t len) { + if (error) { + readPromise.set_exception( + std::make_exception_ptr(error)); + } else { + readPromise.set_value(len); + } + }); + } + if (st.stop_requested()) { break; } - auto len = std::get(result); - messageBuffer.append(buffer, len); - - size_t pos; - while ((pos = messageBuffer.find('\n')) != std::string::npos) { - std::string message = messageBuffer.substr(0, pos); - messageBuffer.erase(0, pos + 1); - if (message == "close") { - socket->close(); - self->connected = false; - return; - } - - size_t sep = message.find(' '); - std::string header = message.substr(0, sep); - std::string payload = message.substr(sep + 1); - - size_t firstColon = header.find(':'); - - std::string messageType = header.substr(0, firstColon); - choc::value::Value value = choc::json::parse(payload); - - if (messageType == "request") { - size_t secondColon = header.find(':', firstColon + 1); - - std::string messageId = header.substr( - firstColon + 1, secondColon - firstColon - 1); - std::string requestType = header.substr(secondColon + 1); - - self->threads[messageId] = - std::jthread([self, socket, messageId, requestType, - value](std::stop_token st) mutable { - choc::value::Value responseObj = - choc::value::createObject(""); - try { - auto response = self->onRequest(requestType, value); - responseObj.setMember("success", true); - responseObj.setMember("data", response); - } catch (std::exception &e) { - responseObj.setMember("success", false); - responseObj.setMember("error", e.what()); - } - - auto responseString = formatMessage( - std::format("response:{}", messageId), - responseObj); - socket->write_some(asio::buffer(responseString)); - - self->threads[messageId].detach(); - self->threads.erase(messageId); - }); - } else if (messageType == "notification") { - std::string notificationType = - header.substr(firstColon + 1); - auto messageId = uuid::v4::UUID::New().String(); - self->threads[messageId] = - std::jthread([self, socket, messageId, notificationType, - value](std::stop_token st) mutable { - self->onNotification(notificationType, value); - - self->threads[messageId].detach(); - self->threads.erase(messageId); - }); + if (readFuture.wait_for(std::chrono::seconds(1)) == + std::future_status::timeout) { + timeout = true; + } else { + timeout = false; + auto result = readFuture.get(); + auto len = result; + messageBuffer.append(buffer, len); + + size_t pos; + while ((pos = messageBuffer.find('\n')) != + std::string::npos) { + std::string message = messageBuffer.substr(0, pos); + messageBuffer.erase(0, pos + 1); + if (message == "close") { + socket->close(); + self->connected = false; + return; + } + + size_t sep = message.find(' '); + std::string header = message.substr(0, sep); + std::string payload = message.substr(sep + 1); + + size_t firstColon = header.find(':'); + + std::string messageType = header.substr(0, firstColon); + choc::value::Value value = choc::json::parse(payload); + + if (messageType == "request") { + size_t secondColon = header.find(':', firstColon + 1); + + std::string messageId = header.substr( + firstColon + 1, secondColon - firstColon - 1); + std::string requestType = header.substr(secondColon + 1); + + self->threads[messageId] = + std::jthread([self, socket, messageId, requestType, + value](std::stop_token st) mutable { + choc::value::Value responseObj = + choc::value::createObject(""); + try { + auto response = + self->onRequest(requestType, value); + responseObj.setMember("success", true); + responseObj.setMember("data", response); + } catch (std::exception &e) { + responseObj.setMember("success", false); + responseObj.setMember("error", e.what()); + } + + auto responseString = formatMessage( + std::format("response:{}", messageId), + responseObj); + socket->write_some(asio::buffer(responseString)); + + self->threads[messageId].detach(); + self->threads.erase(messageId); + }); + } else if (messageType == "notification") { + std::string notificationType = + header.substr(firstColon + 1); + auto messageId = uuid::v4::UUID::New().String(); + self->threads[messageId] = std::jthread( + [self, socket, messageId, notificationType, + value](std::stop_token st) mutable { + self->onNotification(notificationType, value); + + self->threads[messageId].detach(); + self->threads.erase(messageId); + }); + } } } - } - auto currentTime = std::chrono::system_clock::now(); - if (currentTime - self->lastPing > std::chrono::seconds(5)) { - socket->write_some(asio::buffer( - formatMessage("ping", choc::value::createObject("")))); - self->lastPing = currentTime; + auto currentTime = std::chrono::system_clock::now(); + if (currentTime - self->lastPing > std::chrono::seconds(5)) { + socket->write_some(asio::buffer( + formatMessage("ping", choc::value::createObject("")))); + self->lastPing = currentTime; + } } + } catch (asio::system_error &e) { + // ignore } - try { socket->close(); } catch (asio::system_error &e) { @@ -561,7 +576,9 @@ choc::value::Value OpenUtauPlugin::onRequest(const std::string kind, parts[part.trackNo] = std::vector(); } parts[part.trackNo].push_back(part); - hashes.insert(part.hash); + if (part.hash.has_value()) { + hashes.insert(part.hash.value()); + } } { auto _lock = std::lock_guard(this->partsMutex); @@ -608,19 +625,21 @@ void OpenUtauPlugin::onNotification(const std::string kind, auto ustxBase64 = choc::base64::encodeToString(ustx); setState("ustx", ustxBase64.c_str()); } else if (kind == "updateTracks") { - auto _lock = std::lock_guard(this->tracksMutex); - auto tracks = std::vector(); - for (const auto &track : payload["tracks"]) { - tracks.push_back(Structures::Track::deserialize(track)); - } + { + auto _lock = std::unique_lock(this->tracksMutex); + auto tracks = std::vector(); + for (const auto &track : payload["tracks"]) { + tracks.push_back(Structures::Track::deserialize(track)); + } - this->tracks = tracks; + this->tracks = tracks; + } syncMapping(); } else if (kind == "updateAudio") { { - auto _lock = std::lock_guard(this->audioBuffersMutex); + auto _lock = std::unique_lock(this->audioBuffersMutex); - std::map> audioBuffers; + auto audioBuffers = this->audioBuffers; payload["audios"].visitObjectMembers( [&](std::string_view key, const choc::value::ValueView &value) { auto hash = std::stoul(std::string(key)); @@ -644,7 +663,7 @@ void OpenUtauPlugin::onNotification(const std::string kind, void OpenUtauPlugin::syncMapping() { { - auto _lock = std::unique_lock(this->mixMutex); + auto _lock = std::shared_lock(this->tracksMutex); auto tracks = this->tracks; auto outputMap = this->outputMap; @@ -692,13 +711,16 @@ void OpenUtauPlugin::resampleMixes(double newSampleRate) { resampledRight.resize((size_t)(maxEndMs->endMs / 1000.0 * newSampleRate) + 1); for (const auto &part : parts) { + if (!part.hash.has_value()) { + continue; + } auto startFrame = (size_t)(part.startMs / 1000.0 * newSampleRate); auto endFrame = (size_t)(part.endMs / 1000.0 * newSampleRate); auto rate = 44100.0 / newSampleRate; - if (audioBuffers.find(part.hash) == audioBuffers.end()) { + if (audioBuffers.find(part.hash.value()) == audioBuffers.end()) { continue; } - auto &buffer = audioBuffers[part.hash]; + auto &buffer = audioBuffers[part.hash.value()]; for (size_t i = startFrame; i < endFrame; ++i) { auto frame = (size_t)((i - startFrame) * rate); diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index a287970f1..94ffeb81a 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -21,7 +21,7 @@ class Part { double startMs; double endMs; - AudioHash hash; + std::optional hash; static Part deserialize(const choc::value::ValueView &value); choc::value::Value serialize() const; diff --git a/OpenUtau.Core/Commands/Notifications.cs b/OpenUtau.Core/Commands/Notifications.cs index 5fc3f9e3c..4df4b5b6a 100644 --- a/OpenUtau.Core/Commands/Notifications.cs +++ b/OpenUtau.Core/Commands/Notifications.cs @@ -244,4 +244,7 @@ public NotePresetChangedNotification() { public class DawConnectedNotification : UNotification { public override string ToString() => $"Connected to DAW."; } + public class DawDisconnectedNotification : UNotification { + public override string ToString() => $"Disconnected from DAW."; + } } diff --git a/OpenUtau.Core/DawIntegration/DawClient.cs b/OpenUtau.Core/DawIntegration/DawClient.cs index fce3e7717..18a49aefd 100644 --- a/OpenUtau.Core/DawIntegration/DawClient.cs +++ b/OpenUtau.Core/DawIntegration/DawClient.cs @@ -6,11 +6,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using OpenUtau.Core.SignalChain; -using OpenUtau.Core.Ustx; using NAudio.Wave; using Newtonsoft.Json; -using System.IO.Compression; using NumSharp.Utilities; namespace OpenUtau.Core.DawIntegration { @@ -47,7 +44,6 @@ await Task.Run(async () => { try { bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token); } catch (SocketException) { - onetimeHandlers["disconnect"](""); break; } currentMessageBuffer = currentMessageBuffer.Concat(buffer.Slice(0, bytesRead)).ToArray(); @@ -72,6 +68,8 @@ await Task.Run(async () => { break; } } + + OnDisconnect(); }); } @@ -146,5 +144,25 @@ private void RegisterListener(string kind, Action handler) { private void RegisterOnetimeListener(string kind, Action handler) { onetimeHandlers[kind] = handler; } + + public void Disconnect() { + tcpClient.Close(); + cancellationTokenSource?.Cancel(); + OnDisconnect(); + } + + private void OnDisconnect() { + foreach ((var key, var handler) in onetimeHandlers) { + if (key.StartsWith("response")) { + handler( + JsonConvert.SerializeObject(new DawResult( + false, null, "Disconnected" + )) + ); + } + } + + DocManager.Inst.ExecuteCmd(new DawDisconnectedNotification()); + } } } diff --git a/OpenUtau.Core/DawIntegration/DawManager.cs b/OpenUtau.Core/DawIntegration/DawManager.cs index ebf925856..77a5602f2 100644 --- a/OpenUtau.Core/DawIntegration/DawManager.cs +++ b/OpenUtau.Core/DawIntegration/DawManager.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using K4os.Hash.xxHash; -using OpenUtau.Core.Render; using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; using Serilog; @@ -13,28 +11,44 @@ namespace OpenUtau.Core.DawIntegration { public class DawManager : SingletonBase, ICmdSubscriber { public DawClient? dawClient = null; - CancellationTokenSource? debounceCancellation = null; - CancellationTokenSource renderCancellation = null; + CancellationTokenSource? renderCancellation = null; + private Debounce sendLayoutDebounce = new Debounce(); + private Debounce sendAudioDebounce = new Debounce(); private DawManager() { DocManager.Inst.AddSubscriber(this); } public void OnNext(UCommand cmd, bool isUndo) { - if (cmd is UNotification) { + if (cmd is UNotification && !( + cmd is DawConnectedNotification || + cmd is PartRenderedNotification || + cmd is VolumeChangeNotification || + cmd is PanChangeNotification + )) { return; } - debounceCancellation?.Cancel(); - debounceCancellation = new CancellationTokenSource(); - - Task.Delay(TimeSpan.FromSeconds(5), debounceCancellation.Token) - .ContinueWith(async task => { - if (task.IsCompletedSuccessfully) { - await UpdateUstx(); - await UpdateTracks(); - await UpdateAudio(); - } - }); + + sendLayoutDebounce.Do(TimeSpan.FromSeconds(1), async () => { + await UpdateUstx(); + await UpdateTracks(); + }); + sendAudioDebounce.Do(TimeSpan.FromSeconds(5), async () => { + await UpdateAudio(); + }); + } + + public async Task Disconnect() { + if (this.dawClient == null) { + return; + } + await UpdateUstx(); + await UpdateTracks(); + await UpdateAudio(); + + var dawClient = this.dawClient; + this.dawClient = null; + dawClient.Disconnect(); } internal bool isDawClientLocked = false; diff --git a/OpenUtau.Core/PlaybackManager.cs b/OpenUtau.Core/PlaybackManager.cs index a0b369094..8f9478635 100644 --- a/OpenUtau.Core/PlaybackManager.cs +++ b/OpenUtau.Core/PlaybackManager.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using NAudio.Wave; using NAudio.Wave.SampleProviders; +using OpenUtau.Core.DawIntegration; using OpenUtau.Core.Render; using OpenUtau.Core.SignalChain; using OpenUtau.Core.Ustx; @@ -241,7 +242,8 @@ public void OnNext(UCommand cmd, bool isUndo) { DocManager.Inst.ExecuteCmd(new SetPlayPosTickNotification(0)); } if (cmd is PreRenderNotification || cmd is LoadProjectNotification) { - if (Util.Preferences.Default.PreRender) { + // Always prerender when it's connected to daw + if (Util.Preferences.Default.PreRender || DawManager.Inst.dawClient != null) { SchedulePreRender(); } } diff --git a/OpenUtau.Core/Util/Debounce.cs b/OpenUtau.Core/Util/Debounce.cs new file mode 100644 index 000000000..622be9894 --- /dev/null +++ b/OpenUtau.Core/Util/Debounce.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenUtau.Core.Util { + public class Debounce { + CancellationTokenSource? cancellation = null; + + public void Do(TimeSpan timeSpan, Func callback) { + cancellation?.Cancel(); + cancellation = new CancellationTokenSource(); + + Task.Delay(TimeSpan.FromSeconds(5), cancellation.Token) + .ContinueWith(async task => { + if (task.IsCompletedSuccessfully) { + await callback(); + } + }); + } + } +} diff --git a/OpenUtau/ViewModels/MainWindowViewModel.cs b/OpenUtau/ViewModels/MainWindowViewModel.cs index 994f9ac26..d2b85f0ab 100644 --- a/OpenUtau/ViewModels/MainWindowViewModel.cs +++ b/OpenUtau/ViewModels/MainWindowViewModel.cs @@ -29,17 +29,18 @@ public class MainWindowViewModel : ViewModelBase, ICmdSubscriber { public bool ExtendToFrame => OS.IsMacOS(); public string Title { get { - if (DawManager.Inst.dawClient == null) { + if (IsConnectedToDaw) { + return $"{AppVersion} [{DawManager.Inst.dawClient!.server.Name}] (Attached to DAW)"; + } else { var baseTitle = !ProjectSaved ? $"{AppVersion}" : $"{(DocManager.Inst.ChangesSaved ? "" : "*")}{AppVersion} [{DocManager.Inst.Project.FilePath}]"; return baseTitle; - } else { - return $"{AppVersion} [{DawManager.Inst.dawClient.server.Name}] (Attached to DAW)"; } } } + public bool IsConnectedToDaw => DawManager.Inst.dawClient != null; [Reactive] public PlaybackViewModel PlaybackViewModel { get; set; } [Reactive] public TracksViewModel TracksViewModel { get; set; } [Reactive] public ReactiveCommand? OpenRecentCommand { get; private set; } @@ -351,6 +352,8 @@ public void OnNext(UCommand cmd, bool isUndo) { Core.Util.Preferences.AddRecentFileIfEnabled(loadProject.project.FilePath); } else if (cmd is SaveProjectNotification saveProject) { Core.Util.Preferences.AddRecentFileIfEnabled(saveProject.Path); + } else if (cmd is DawConnectedNotification ||cmd is DawDisconnectedNotification) { + this.RaisePropertyChanged(nameof(IsConnectedToDaw)); } this.RaisePropertyChanged(nameof(Title)); } diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index fd6bcd759..cd1e75655 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -66,7 +66,8 @@ - + + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 404ef4363..65d8cc241 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -18,6 +18,7 @@ using OpenUtau.Classic; using OpenUtau.Core; using OpenUtau.Core.Analysis.Some; +using OpenUtau.Core.DawIntegration; using OpenUtau.Core.DiffSinger; using OpenUtau.Core.Format; using OpenUtau.Core.Ustx; @@ -517,6 +518,10 @@ async void OnMenuDawIntegrationTerminal(object sender, RoutedEventArgs args) { dialog.Position = dialog.Position.WithY(0); } } + async void OnMenuDisconnectFromDaw(object sender, RoutedEventArgs args) { + await DawManager.Inst.Disconnect(); + } + private async Task WarnToSave(UProject project) { if (string.IsNullOrEmpty(project.FilePath)) { From be2b56299cfc1983b59cdd268038d84ae2ba8ddd Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 23 Sep 2024 17:54:44 +0900 Subject: [PATCH 20/72] Fix: Fix around locking --- DawPlugin/CMakeSettings.json | 12 +++++++++++- DawPlugin/src/DistrhoPluginInfo.h | 8 ++++++++ DawPlugin/src/plugin.cpp | 31 ++++++++++++++----------------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/DawPlugin/CMakeSettings.json b/DawPlugin/CMakeSettings.json index 8f8d80d9a..5a03c8d7c 100644 --- a/DawPlugin/CMakeSettings.json +++ b/DawPlugin/CMakeSettings.json @@ -9,6 +9,16 @@ "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "-DCMAKE_BUILD_TYPE=Debug", "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Visual Studio 17 2022 Win64", + "configurationType": "Release", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DCMAKE_BUILD_TYPE=Release", + "ctestCommandArgs": "" } ] -} \ No newline at end of file +} diff --git a/DawPlugin/src/DistrhoPluginInfo.h b/DawPlugin/src/DistrhoPluginInfo.h index eb8e2ee1d..2244f7837 100644 --- a/DawPlugin/src/DistrhoPluginInfo.h +++ b/DawPlugin/src/DistrhoPluginInfo.h @@ -2,11 +2,19 @@ #define DISTRHO_PLUGIN_INFO_H_INCLUDED #define DISTRHO_PLUGIN_BRAND "stakira" +#ifdef DEBUG +#define DISTRHO_PLUGIN_NAME "OpenUtau Bridge (Debug) +#else #define DISTRHO_PLUGIN_NAME "OpenUtau Bridge" +#endif #define DISTRHO_PLUGIN_URI "https://github.com/stakira/OpenUtau/" #define DISTRHO_PLUGIN_BRAND_ID Stak +#ifdef DEBUG +#define DISTRHO_PLUGIN_UNIQUE_ID OpUD +#else #define DISTRHO_PLUGIN_UNIQUE_ID OpUt +#endif #define DISTRHO_PLUGIN_HAS_UI 1 #define DISTRHO_PLUGIN_IS_SYNTH 1 diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index a6b742be5..0ad46e09b 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -120,9 +120,9 @@ OpenUtauPlugin::~OpenUtauPlugin() { * Information */ /** - Get the plugin - label. This label is a short restricted name consisting of only _, a-z, A-Z - and 0-9 characters. + Get the plugin + label. This label is a short restricted name consisting of only _, a-z, + A-Z and 0-9 characters. */ const char *OpenUtauPlugin::getLabel() const { return "OpenUtau"; } @@ -262,11 +262,8 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { } this->requestResampleMixes(this->currentSampleRate); } else if (key == "tracks") { - { - auto _lock = std::lock_guard(this->tracksMutex); - this->tracks = Structures::deserializeTracks(value); - } - syncMapping(); + auto _lock = std::lock_guard(this->tracksMutex); + this->tracks = Structures::deserializeTracks(value); } else if (key == "mapping") { this->outputMap = Structures::deserializeOutputMap(value); } @@ -290,9 +287,9 @@ uint32_t OpenUtauPlugin::getVersion() const { * Init */ /** - Initialize the - audio port @a index.@n This function will be called once, shortly after the - plugin is created. + Initialize the + audio port @a index.@n This function will be called once, shortly after + the plugin is created. */ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, AudioPort &port) { @@ -380,12 +377,12 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, * Callbacks (optional) */ /** - Optional callback - to inform the plugin about a buffer size change. This function will only be - called when the plugin is deactivated. - @note This value - is only a hint! Hosts might call run() with a higher or lower number of - frames. + Optional callback + to inform the plugin about a buffer size change. This function will only + be called when the plugin is deactivated. + @note This value + is only a hint! Hosts might call run() with a higher or lower number of + frames. */ void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} From f7ca0f008b4d06bbc01e9e0bf55c5411a8dcae8c Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 19:30:30 +0900 Subject: [PATCH 21/72] Fix: Fix crash on disconnect --- DawPlugin/CMakeLists.txt | 26 ++++++++++++++++---------- DawPlugin/src/DistrhoPluginInfo.h | 2 +- DawPlugin/src/plugin.cpp | 19 +++++++++++++------ DawPlugin/src/ui.cpp | 9 +++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 71c485102..6e94bbdae 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -2,8 +2,18 @@ cmake_minimum_required(VERSION 3.24) set(DPF_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/DPF") +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(PROJECT_NAME openutau_daw_plugin_debug) +elseif(CMAKE_BUILD_TYPE STREQUAL "Release") + set(PROJECT_NAME openutau_daw_plugin) +elseif(NOT CMAKE_BUILD_TYPE) + message(FATAL_ERROR "Build type not set") +else() + message(FATAL_ERROR "Unknown build type: ${CMAKE_BUILD_TYPE}") +endif() + include(./deps/dpf/cmake/DPF-plugin.cmake) -project(openutau_daw_plugin) +project(${PROJECT_NAME}) # MSVC only: Use UTF-8 code page if(MSVC) @@ -14,18 +24,14 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DDEBUG) add_definitions(-DDPF_DEBUG) elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - -elseif(NOT CMAKE_BUILD_TYPE) - message(FATAL_ERROR "Build type not set") -else() - message(FATAL_ERROR "Unknown build type: ${CMAKE_BUILD_TYPE}") + # nothing atm endif() # Windows 10 add_definitions(-D_WIN32_WINNT=0x0A00) dpf_add_plugin( - openutau_daw_plugin + ${PROJECT_NAME} TARGETS vst3 UI_TYPE @@ -44,10 +50,10 @@ set(ZLIB_BUILD_EXAMPLES OFF) set(RENAME_ZCONF OFF) add_subdirectory(deps/zlib) add_subdirectory(deps/xxhash_cpp) -target_compile_features(openutau_daw_plugin PUBLIC cxx_std_20) +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) target_include_directories( - openutau_daw_plugin + ${PROJECT_NAME} PUBLIC "src" "deps" "deps/DPF/dgl" @@ -60,4 +66,4 @@ target_include_directories( "deps/yamc/include" "deps/gzip-hpp/include") -target_link_libraries(openutau_daw_plugin PRIVATE zlibstatic xxhash_cpp) +target_link_libraries(${PROJECT_NAME} PRIVATE zlibstatic xxhash_cpp) diff --git a/DawPlugin/src/DistrhoPluginInfo.h b/DawPlugin/src/DistrhoPluginInfo.h index 2244f7837..0fa2ae297 100644 --- a/DawPlugin/src/DistrhoPluginInfo.h +++ b/DawPlugin/src/DistrhoPluginInfo.h @@ -3,7 +3,7 @@ #define DISTRHO_PLUGIN_BRAND "stakira" #ifdef DEBUG -#define DISTRHO_PLUGIN_NAME "OpenUtau Bridge (Debug) +#define DISTRHO_PLUGIN_NAME "OpenUtau Bridge (Debug)" #else #define DISTRHO_PLUGIN_NAME "OpenUtau Bridge" #endif diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 0ad46e09b..db56a4680 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -63,10 +63,11 @@ Part Part::deserialize(const choc::value::ValueView &value) { part.trackNo = value["trackNo"].get(); part.startMs = value["startMs"].get(); part.endMs = value["endMs"].get(); - if (value.hasObjectMember("audioHash")) { - part.hash = value["audioHash"].get(); - } else { + auto audioHash = value["audioHash"]; + if (audioHash.isVoid()) { part.hash = std::nullopt; + } else { + part.hash = (uint32_t)audioHash.get(); } return part; } @@ -124,7 +125,13 @@ OpenUtauPlugin::~OpenUtauPlugin() { label. This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters. */ -const char *OpenUtauPlugin::getLabel() const { return "OpenUtau"; } +const char *OpenUtauPlugin::getLabel() const { +#ifdef DEBUG + return "OpenUtau_Debug"; +#else + return "OpenUtau"; +#endif +} /** Get an extensive comment/description about the plugin. @@ -413,8 +420,8 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, asio::buffer(buffer), [&](const asio::error_code &error, size_t len) { if (error) { - readPromise.set_exception( - std::make_exception_ptr(error)); + readPromise.set_exception( + std::make_exception_ptr(error)); } else { readPromise.set_value(len); } diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index 306ff2522..64f33949b 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -77,7 +77,12 @@ class OpenUtauUI : public UI { ImGui::Begin("OpenUtau Bridge", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize); - ImGui::TextColored(themePinkColor, "OpenUtau Bridge v%d.%d.%d", + ImGui::TextColored(themePinkColor, +#ifdef DEBUG + "OpenUtau Bridge v%d.%d.%d (Debug)", +#else + "OpenUtau Bridge v%d.%d.%d", +#endif Constants::majorVersion, Constants::minorVersion, Constants::patchVersion); @@ -112,7 +117,7 @@ class OpenUtauUI : public UI { } #endif - ImGui::Text("Last sync: "); + ImGui::Text("Last audio sync: "); ImGui::SameLine(0, 0); if (plugin->isProcessing()) { From 0b62231d1fbc538182fcf5a6ea27fd420f36f5df Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 20:08:53 +0900 Subject: [PATCH 22/72] Delete: Delete xxhash --- .gitmodules | 15 ++++++--------- DawPlugin/CMakeLists.txt | 4 +--- DawPlugin/deps/xxhash_cpp | 1 - DawPlugin/src/plugin.cpp | 1 - compile_flags.txt | 2 -- 5 files changed, 7 insertions(+), 16 deletions(-) delete mode 160000 DawPlugin/deps/xxhash_cpp diff --git a/.gitmodules b/.gitmodules index 5075ce03f..59d4f1e95 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,18 +10,15 @@ [submodule "DawPlugin/deps/choc"] path = DawPlugin/deps/choc url = https://github.com/Tracktion/choc -[submodule "DawPlugin/deps/zlib"] - path = DawPlugin/deps/zlib - url = https://github.com/madler/zlib.git -[submodule "DawPlugin/deps/gzip-hpp"] - path = DawPlugin/deps/gzip-hpp - url = https://github.com/mapbox/gzip-hpp.git [submodule "DawPlugin/deps/uuid-v4"] path = DawPlugin/deps/uuid-v4 url = https://github.com/rkg82/uuid-v4.git [submodule "DawPlugin/deps/yamc"] path = DawPlugin/deps/yamc url = https://github.com/yohhoy/yamc.git -[submodule "DawPlugin/deps/xxhash_cpp"] - path = DawPlugin/deps/xxhash_cpp - url = https://github.com/RedSpah/xxhash_cpp.git +[submodule "DawPlugin/deps/zlib"] + path = DawPlugin/deps/zlib + url = https://github.com/madler/zlib.git +[submodule "DawPlugin/deps/gzip-hpp"] + path = DawPlugin/deps/gzip-hpp + url = https://github.com/mapbox/gzip-hpp.git diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 6e94bbdae..8068d6108 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -49,7 +49,6 @@ dpf_add_plugin( set(ZLIB_BUILD_EXAMPLES OFF) set(RENAME_ZCONF OFF) add_subdirectory(deps/zlib) -add_subdirectory(deps/xxhash_cpp) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) target_include_directories( @@ -62,8 +61,7 @@ target_include_directories( "deps/dpf_widgets" "deps/uuid-v4" "deps/zlib" - "deps/xxhash_cpp/include" "deps/yamc/include" "deps/gzip-hpp/include") -target_link_libraries(${PROJECT_NAME} PRIVATE zlibstatic xxhash_cpp) +target_link_libraries(${PROJECT_NAME} PRIVATE zlibstatic) diff --git a/DawPlugin/deps/xxhash_cpp b/DawPlugin/deps/xxhash_cpp deleted file mode 160000 index ad24e1c17..000000000 --- a/DawPlugin/deps/xxhash_cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ad24e1c174f855cfdebc22ad4e678fd3fee3b943 diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index db56a4680..946bfb524 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -8,7 +8,6 @@ #include "choc/text/choc_JSON.h" #include "common.hpp" #include "extra/String.hpp" -#include "gzip/compress.hpp" #include "uuid/v4/uuid.h" #include #include diff --git a/compile_flags.txt b/compile_flags.txt index 115e87528..f1612995f 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -7,8 +7,6 @@ -I./DawPlugin/deps/dpf_widgets -I./DawPlugin/deps/zlib -I./DawPlugin/deps/gzip-hpp/include --I./DawPlugin/deps/libfar/include -I./DawPlugin/deps/uuid-v4 --I./DawPlugin/deps/xxhash_cpp/include -I./DawPlugin/deps/yamc/include -I./DawPlugin/deps/ From 871ec732a76be331055b124af8621ac5e6e12b8e Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 20:36:50 +0900 Subject: [PATCH 23/72] Add: Add workflow to build vst3 --- .github/workflows/build.yml | 26 ++- DawPlugin/.editorconfig | 232 ---------------------- DawPlugin/README.md | 12 ++ DawPlugin/src/DistrhoPluginInfo.h | 3 +- DawPlugin/src/plugin.cpp | 69 +------ DawPlugin/src/ui.cpp | 17 -- OpenUtau.Core/DawIntegration/DawClient.cs | 12 +- OpenUtau/Strings/Strings.axaml | 4 +- OpenUtau/Views/MainWindow.axaml.cs | 8 + 9 files changed, 71 insertions(+), 312 deletions(-) delete mode 100644 DawPlugin/.editorconfig create mode 100644 DawPlugin/README.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c64d623fb..6a6f5b641 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,6 +41,8 @@ jobs: steps: # Setup - uses: actions/checkout@v4 + with: + submodules: recursive - uses: actions/setup-dotnet@v4 with: dotnet-version: "6.0.x" @@ -56,7 +58,7 @@ jobs: - name: Test run: dotnet test OpenUtau.Test - # Build + # Build: Main - name: Restore run: dotnet restore OpenUtau -r ${{ matrix.arch.rid }} @@ -64,6 +66,21 @@ jobs: run: dotnet publish OpenUtau -c Release -r ${{ matrix.arch.rid }} --self-contained true -o bin/${{ matrix.arch.name }}/ if: ${{ matrix.arch.os != 'osx' }} + # Build: Daw Plugin + - name: Build Daw Plugin + run: | + cd ./DawPlugin + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake --build build --config Release + if: ${{ matrix.arch.cpu == 'x64' }} + + - name: Package Daw Plugin + shell: bash + run: | + # mkdir -p DawPlugin/build/bin/openutau_daw_plugin.vst3 + cd DawPlugin/build/bin + 7z a openutau_daw_plugin.vst3.zip openutau_daw_plugin.vst3 + # Create Zip - name: DirectML shell: cmd @@ -127,6 +144,12 @@ jobs: path: OpenUtau-osx-${{ matrix.arch.name }}.dmg if: ${{ !inputs.release && matrix.arch.os == 'osx' }} + - uses: actions/upload-artifact@v4 + with: + name: openutau_daw_plugin.vst3.zip + path: DawPlugin/build/bin/openutau_daw_plugin.vst3.zip + if: ${{ !inputs.release && matrix.arch.cpu == 'x64' }} + # Appcast - name: Appcast Windows shell: cmd @@ -156,4 +179,5 @@ jobs: files: | appcast.${{ matrix.arch.name }}*.xml OpenUtau-${{ matrix.arch.name }}.* + DawPlugin/build/bin/openutau_daw_plugin.vst3.zip if: ${{ inputs.release }} diff --git a/DawPlugin/.editorconfig b/DawPlugin/.editorconfig deleted file mode 100644 index d5c8ebddf..000000000 --- a/DawPlugin/.editorconfig +++ /dev/null @@ -1,232 +0,0 @@ -# 上位ディレクトリから .editorconfig 設定を継承する場合は、以下の行を削除します -root = true - -# C# ファイル -[*.cs] - -#### コア EditorConfig オプション #### - -# インデントと間隔 -indent_size = 4 -indent_style = space -tab_width = 4 - -# 改行設定 -end_of_line = crlf -insert_final_newline = false - -#### .NET コーディング規則 #### - -# using の整理 -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false -file_header_template = unset - -# this. と Me. の設定 -dotnet_style_qualification_for_event = false -dotnet_style_qualification_for_field = false -dotnet_style_qualification_for_method = false -dotnet_style_qualification_for_property = false - -# 言語キーワードと BCL の種類の設定 -dotnet_style_predefined_type_for_locals_parameters_members = true -dotnet_style_predefined_type_for_member_access = true - -# かっこの設定 -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_operators = never_if_unnecessary -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity - -# 修飾子設定 -dotnet_style_require_accessibility_modifiers = for_non_interface_members - -# 式レベルの設定 -dotnet_style_coalesce_expression = true -dotnet_style_collection_initializer = true -dotnet_style_explicit_tuple_names = true -dotnet_style_namespace_match_folder = true -dotnet_style_null_propagation = true -dotnet_style_object_initializer = true -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true -dotnet_style_prefer_collection_expression = when_types_loosely_match -dotnet_style_prefer_compound_assignment = true -dotnet_style_prefer_conditional_expression_over_assignment = true -dotnet_style_prefer_conditional_expression_over_return = true -dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed -dotnet_style_prefer_inferred_anonymous_type_member_names = true -dotnet_style_prefer_inferred_tuple_names = true -dotnet_style_prefer_is_null_check_over_reference_equality_method = true -dotnet_style_prefer_simplified_boolean_expressions = true -dotnet_style_prefer_simplified_interpolation = true - -# フィールド設定 -dotnet_style_readonly_field = true - -# パラメーターの設定 -dotnet_code_quality_unused_parameters = all - -# 抑制の設定 -dotnet_remove_unnecessary_suppression_exclusions = none - -# 改行設定 -dotnet_style_allow_multiple_blank_lines_experimental = true -dotnet_style_allow_statement_immediately_after_block_experimental = true - -#### C# コーディング規則 #### - -# var を優先 -csharp_style_var_elsewhere = false -csharp_style_var_for_built_in_types = false -csharp_style_var_when_type_is_apparent = false - -# 式のようなメンバー -csharp_style_expression_bodied_accessors = true -csharp_style_expression_bodied_constructors = false -csharp_style_expression_bodied_indexers = true -csharp_style_expression_bodied_lambdas = true -csharp_style_expression_bodied_local_functions = false -csharp_style_expression_bodied_methods = false -csharp_style_expression_bodied_operators = false -csharp_style_expression_bodied_properties = true - -# パターン マッチング設定 -csharp_style_pattern_matching_over_as_with_null_check = true -csharp_style_pattern_matching_over_is_with_cast_check = true -csharp_style_prefer_extended_property_pattern = true -csharp_style_prefer_not_pattern = true -csharp_style_prefer_pattern_matching = true -csharp_style_prefer_switch_expression = true - -# Null チェック設定 -csharp_style_conditional_delegate_call = true - -# 修飾子設定 -csharp_prefer_static_anonymous_function = true -csharp_prefer_static_local_function = true -csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async -csharp_style_prefer_readonly_struct = true -csharp_style_prefer_readonly_struct_member = true - -# コード ブロックの設定 -csharp_prefer_braces = true -csharp_prefer_simple_using_statement = true -csharp_style_namespace_declarations = block_scoped -csharp_style_prefer_method_group_conversion = true -csharp_style_prefer_primary_constructors = true -csharp_style_prefer_top_level_statements = true - -# 式レベルの設定 -csharp_prefer_simple_default_expression = true -csharp_style_deconstructed_variable_declaration = true -csharp_style_implicit_object_creation_when_type_is_apparent = true -csharp_style_inlined_variable_declaration = true -csharp_style_prefer_index_operator = true -csharp_style_prefer_local_over_anonymous_function = true -csharp_style_prefer_null_check_over_type_check = true -csharp_style_prefer_range_operator = true -csharp_style_prefer_tuple_swap = true -csharp_style_prefer_utf8_string_literals = true -csharp_style_throw_expression = true -csharp_style_unused_value_assignment_preference = discard_variable -csharp_style_unused_value_expression_statement_preference = discard_variable - -# 'using' ディレクティブの基本設定 -csharp_using_directive_placement = outside_namespace - -# 改行設定 -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true -csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true -csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true -csharp_style_allow_embedded_statements_on_same_line_experimental = true - -#### C# 書式ルール #### - -# 改行設定 -csharp_new_line_before_catch = true -csharp_new_line_before_else = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all -csharp_new_line_between_query_expression_clauses = true - -# インデント設定 -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_labels = one_less_than_current -csharp_indent_switch_labels = true - -# スペース設定 -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# 折り返しの設定 -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -#### 命名スタイル #### - -# 名前付けルール - -dotnet_naming_rule.interface_should_be_i_で始まる.severity = suggestion -dotnet_naming_rule.interface_should_be_i_で始まる.symbols = interface -dotnet_naming_rule.interface_should_be_i_で始まる.style = i_で始まる - -dotnet_naming_rule.型_should_be_パスカル_ケース.severity = suggestion -dotnet_naming_rule.型_should_be_パスカル_ケース.symbols = 型 -dotnet_naming_rule.型_should_be_パスカル_ケース.style = パスカル_ケース - -dotnet_naming_rule.フィールド以外のメンバー_should_be_パスカル_ケース.severity = suggestion -dotnet_naming_rule.フィールド以外のメンバー_should_be_パスカル_ケース.symbols = フィールド以外のメンバー -dotnet_naming_rule.フィールド以外のメンバー_should_be_パスカル_ケース.style = パスカル_ケース - -# 記号の仕様 - -dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = - -dotnet_naming_symbols.型.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.型.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.型.required_modifiers = - -dotnet_naming_symbols.フィールド以外のメンバー.applicable_kinds = property, event, method -dotnet_naming_symbols.フィールド以外のメンバー.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.フィールド以外のメンバー.required_modifiers = - -# 命名スタイル - -dotnet_naming_style.パスカル_ケース.required_prefix = -dotnet_naming_style.パスカル_ケース.required_suffix = -dotnet_naming_style.パスカル_ケース.word_separator = -dotnet_naming_style.パスカル_ケース.capitalization = pascal_case - -dotnet_naming_style.i_で始まる.required_prefix = I -dotnet_naming_style.i_で始まる.required_suffix = -dotnet_naming_style.i_で始まる.word_separator = -dotnet_naming_style.i_で始まる.capitalization = pascal_case diff --git a/DawPlugin/README.md b/DawPlugin/README.md new file mode 100644 index 000000000..db793bdb5 --- /dev/null +++ b/DawPlugin/README.md @@ -0,0 +1,12 @@ +# Building + +1. Install CMake +2. `cd` to `DawPlugin` folder. +3. Run `cmake -B build -S . -DCMAKE_BUILD_TYPE=Debug` to generate build files. +4. Run `cmake --build build` to build the plugin. +5. Add `path/to/OpenUtau/DawPlugin/build/bin` to your DAW's plugin search path. + +Notes: +- `-DCMAKE_BUILD_TYPE=Debug` can be replaced with `-DCMAKE_BUILD_TYPE=Release` for a release build. +- When you're using Visual Studio, your plugin will be built in `build/x64-Debug` or `build/x64-Release` instead. +- This plugin is very unstable! Don't forget to save your work often. diff --git a/DawPlugin/src/DistrhoPluginInfo.h b/DawPlugin/src/DistrhoPluginInfo.h index 0fa2ae297..7adc230a7 100644 --- a/DawPlugin/src/DistrhoPluginInfo.h +++ b/DawPlugin/src/DistrhoPluginInfo.h @@ -26,9 +26,8 @@ #define DISTRHO_PLUGIN_WANT_FULL_STATE 1 #define DISTRHO_UI_DEFAULT_WIDTH 1024 #define DISTRHO_UI_DEFAULT_HEIGHT 256 -#define DISTRHO_UI_FILE_BROWSER 0 +#define DISTRHO_UI_FILE_BROWSER 1 #define DISTRHO_UI_USER_RESIZABLE 1 -// TODO: Don't use direct access #define DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 1 #define DISTRHO_UI_USE_CUSTOM 1 diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 946bfb524..c055d51e9 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -8,6 +8,7 @@ #include "choc/text/choc_JSON.h" #include "common.hpp" #include "extra/String.hpp" +#include "gzip/compress.hpp" #include "uuid/v4/uuid.h" #include #include @@ -19,9 +20,7 @@ #include #include -// std::jthread *ioThread = nullptr; namespace Network { -// std::jthread *ioThread = nullptr; std::shared_ptr ioThread; std::shared_ptr ioContext; std::shared_ptr getIoContext() { @@ -116,37 +115,20 @@ OpenUtauPlugin::~OpenUtauPlugin() { } } -/* -------------------------------------------------------------------------------------------------------- - * Information */ - -/** - Get the plugin - label. This label is a short restricted name consisting of only _, a-z, - A-Z and 0-9 characters. - */ -const char *OpenUtauPlugin::getLabel() const { +const char *OpenUtauPlugin::getLabel() const { #ifdef DEBUG - return "OpenUtau_Debug"; + return "OpenUtau_Debug"; #else - return "OpenUtau"; + return "OpenUtau"; #endif } -/** - Get an extensive comment/description about the plugin. - */ const char *OpenUtauPlugin::getDescription() const { - return "Plugin to show how to get some basic information sent to the UI."; + return "Bridge between OpenUtau and your DAW"; } -/** - Get the plugin author/maker. - */ const char *OpenUtauPlugin::getMaker() const { return "stakira"; } -/** - Get the plugin homepage. - */ const char *OpenUtauPlugin::getHomePage() const { return "https://github.com/stakira/OpenUtau/"; } @@ -275,28 +257,13 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { } } -/** - Get the plugin license name (a single line of text). - For commercial plugins this should return some short copyright information. - */ -const char *OpenUtauPlugin::getLicense() const { return "ISC"; } +const char *OpenUtauPlugin::getLicense() const { return "MIT"; } -/** - Get the plugin version, in hexadecimal. - */ uint32_t OpenUtauPlugin::getVersion() const { return d_version(Constants::majorVersion, Constants::minorVersion, Constants::patchVersion); } -/* -------------------------------------------------------------------------------------------------------- - * Init */ - -/** - Initialize the - audio port @a index.@n This function will be called once, shortly after - the plugin is created. - */ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, AudioPort &port) { port.groupId = index / 2; @@ -305,9 +272,6 @@ void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, port.name = String(name.c_str()); } -/* -------------------------------------------------------------------------------------------------------- - * Audio/MIDI Processing */ - void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) { @@ -379,17 +343,6 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, } }; -/* -------------------------------------------------------------------------------------------------------- - * Callbacks (optional) */ - -/** - Optional callback - to inform the plugin about a buffer size change. This function will only - be called when the plugin is deactivated. - @note This value - is only a hint! Hosts might call run() with a higher or lower number of - frames. - */ void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} void OpenUtauPlugin::sampleRateChanged(double newSampleRate) { @@ -419,8 +372,9 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, asio::buffer(buffer), [&](const asio::error_code &error, size_t len) { if (error) { - readPromise.set_exception( - std::make_exception_ptr(error)); + readPromise.set_exception( + std::make_exception_ptr( + error)); } else { readPromise.set_value(len); } @@ -507,7 +461,7 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, auto currentTime = std::chrono::system_clock::now(); if (currentTime - self->lastPing > std::chrono::seconds(5)) { socket->write_some(asio::buffer( - formatMessage("ping", choc::value::createObject("")))); + formatMessage("notification:ping", choc::value::createObject("")))); self->lastPing = currentTime; } } @@ -777,8 +731,7 @@ bool OpenUtauPlugin::isProcessing() { return false; } -/* ------------------------------------------------------------------------------------------------------------ - * Plugin entry point, called by DPF to create a new plugin instance. */ +// ------------------------------------------------------------------------------------------------------------ START_NAMESPACE_DISTRHO Plugin *createPlugin() { return new OpenUtauPlugin(); } diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index 64f33949b..3af02984e 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -18,11 +18,6 @@ static auto themeBlueColor = ImVec4(0.3f, 0.7f, 0.9f, 1.0f); class OpenUtauUI : public UI { public: - /** - UI class constructor. - The UI should be initialized to a default state that matches the plugin - side. - */ OpenUtauUI() : UI(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT), resizeHandle(this) { @@ -50,22 +45,10 @@ class OpenUtauUI : public UI { protected: // ---------------------------------------------------------------------------------------------------------------- - // DSP/Plugin Callbacks - - /** - A parameter has changed on the plugin side.@n - This is called by the host to inform the UI about parameter changes. - */ void parameterChanged(uint32_t, float) override {} void stateChanged(const char *, const char *) override {} - // ---------------------------------------------------------------------------------------------------------------- - // Widget Callbacks - - /** - ImGui specific onDisplay function. - */ void onImGuiDisplay() override { ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(ImVec2(getWidth(), getHeight())); diff --git a/OpenUtau.Core/DawIntegration/DawClient.cs b/OpenUtau.Core/DawIntegration/DawClient.cs index 18a49aefd..bce62ef00 100644 --- a/OpenUtau.Core/DawIntegration/DawClient.cs +++ b/OpenUtau.Core/DawIntegration/DawClient.cs @@ -9,6 +9,7 @@ using NAudio.Wave; using Newtonsoft.Json; using NumSharp.Utilities; +using Serilog; namespace OpenUtau.Core.DawIntegration { public class DawClient { @@ -60,7 +61,7 @@ await Task.Run(async () => { onetimeHandlers[kind](content); onetimeHandlers.Remove(kind); } else { - Console.WriteLine($"Unhandled message: {kind}"); + Log.Warning($"Unhandled message: {kind}"); } } @@ -84,6 +85,8 @@ await Task.Run(async () => { var timeoutCanceller = new CancellationTokenSource(); timeoutCanceller.CancelAfter(TimeSpan.FromSeconds(5)); var initMessage = await client.SendRequest(new InitRequest(), timeoutCanceller.Token); + + client.RegisterNotification("ping", (_) => { }); return (client, initMessage.ustx); } @@ -92,11 +95,18 @@ private async Task SendMessage(string header, DawMessage data) { throw new Exception("stream is null"); } await writerSemaphore.WaitAsync(); + var disconnected = false; try { await stream.WriteAsync(Encoding.UTF8.GetBytes($"{header} {JsonConvert.SerializeObject(data)}\n")); + } catch (SocketException) { + disconnected = true; } finally { writerSemaphore.Release(); } + + if (disconnected) { + Disconnect(); + } } public async Task SendRequest(DawDawRequest data, CancellationToken? token = null) where T : DawDawResponse { diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 11e5a2a6b..630fda11e 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -556,7 +556,9 @@ General Refresh Connect - (Attached to DAW) + Connected to DAW + Disconnected from DAW + (Connected to DAW) Segoe UI,San Francisco,Helvetica Neue diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 65d8cc241..222a41292 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -1380,6 +1380,14 @@ public void OnNext(UCommand cmd, bool isUndo) { VoiceColorRemapping(track, oldColors, newColors); } } + } else if (cmd is DawConnectedNotification) { + DocManager.Inst.ExecuteCmd(new ProgressBarNotification(1, + ThemeManager.GetString("dawintegration.status.connected") + )); + } else if (cmd is DawDisconnectedNotification) { + DocManager.Inst.ExecuteCmd(new ErrorMessageNotification( + ThemeManager.GetString("dawintegration.status.disconnected") + )); } } } From 3aeaf8f770f04f7d0be3fcd0b5ec040684e55673 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 20:42:35 +0900 Subject: [PATCH 24/72] Fix: Fix reference --- OpenUtau/Views/DawIntegrationTerminalDialog.axaml.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/OpenUtau/Views/DawIntegrationTerminalDialog.axaml.cs b/OpenUtau/Views/DawIntegrationTerminalDialog.axaml.cs index 589837b00..74b064df4 100644 --- a/OpenUtau/Views/DawIntegrationTerminalDialog.axaml.cs +++ b/OpenUtau/Views/DawIntegrationTerminalDialog.axaml.cs @@ -1,17 +1,8 @@ using System; -using System.ServiceModel; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; using Avalonia.Controls; using Avalonia.Interactivity; -using Avalonia.Threading; -using NetSparkleUpdater.Enums; using OpenUtau.App.ViewModels; using OpenUtau.Core; -using OpenUtau.Core.DawIntegration; -using OpenUtau.Core.Util; -using Serilog; namespace OpenUtau.App.Views { public partial class DawIntegrationTerminalDialog : Window { From 0748d752ee12b7957fe8ebe3236ccf689895a96e Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 20:46:27 +0900 Subject: [PATCH 25/72] Fix: Fix condition --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a6f5b641..c71d71a70 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,7 +72,7 @@ jobs: cd ./DawPlugin cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.arch.cpu == 'x64' }} + if: ${{ matrix.arch.arch == 'x64' }} - name: Package Daw Plugin shell: bash @@ -80,6 +80,7 @@ jobs: # mkdir -p DawPlugin/build/bin/openutau_daw_plugin.vst3 cd DawPlugin/build/bin 7z a openutau_daw_plugin.vst3.zip openutau_daw_plugin.vst3 + if: ${{ matrix.arch.arch == 'x64' }} # Create Zip - name: DirectML @@ -148,7 +149,7 @@ jobs: with: name: openutau_daw_plugin.vst3.zip path: DawPlugin/build/bin/openutau_daw_plugin.vst3.zip - if: ${{ !inputs.release && matrix.arch.cpu == 'x64' }} + if: ${{ !inputs.release && matrix.arch.arch == 'x64' }} # Appcast - name: Appcast Windows From 0d0b309c8f6d136f33c7abfb1bf5e5feb77ebce5 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 20:56:33 +0900 Subject: [PATCH 26/72] Fix: Fix num of states --- .gitmodules | 4 ++-- DawPlugin/src/plugin.cpp | 2 +- compile_flags.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index 59d4f1e95..47f6c56ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ -[submodule "DawPlugin/deps/DPF"] - path = DawPlugin/deps/DPF +[submodule "DawPlugin/deps/dpf"] + path = DawPlugin/deps/dpf url = https://github.com/distrho/DPF.git [submodule "DawPlugin/deps/asio"] path = DawPlugin/deps/asio diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index c055d51e9..bdf9d18e4 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -72,7 +72,7 @@ Part Part::deserialize(const choc::value::ValueView &value) { // ----------------------------------------------------------------------------------------------------------- OpenUtauPlugin::OpenUtauPlugin() - : Plugin(0, 0, 5) + : Plugin(0, 0, 6) { diff --git a/compile_flags.txt b/compile_flags.txt index f1612995f..505ec2821 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,8 +1,8 @@ -xc++ -std=c++20 -I./DawPlugin/src --I./DawPlugin/deps/DPF/distrho --I./DawPlugin/deps/DPF/dgl +-I./DawPlugin/deps/dpf/distrho +-I./DawPlugin/deps/dpf/dgl -I./DawPlugin/deps/asio/asio/include -I./DawPlugin/deps/dpf_widgets -I./DawPlugin/deps/zlib From d913f3511bac48cdf0b638c02f41f009542818d4 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 20:59:44 +0900 Subject: [PATCH 27/72] Fix: Add workaround for MacOS --- DawPlugin/src/plugin.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index bdf9d18e4..bee55d4f3 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -8,7 +8,6 @@ #include "choc/text/choc_JSON.h" #include "common.hpp" #include "extra/String.hpp" -#include "gzip/compress.hpp" #include "uuid/v4/uuid.h" #include #include @@ -20,6 +19,14 @@ #include #include +#if CHOC_OSX +// Workaround for zlib +// https://github.com/Blosc/python-blosc/issues/229#issuecomment-676819560 +#include +#endif + +#include "gzip/compress.hpp" + namespace Network { std::shared_ptr ioThread; std::shared_ptr ioContext; From 6f497097dbd929e3b8b85e172844c93f3dd47f7e Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 21:00:36 +0900 Subject: [PATCH 28/72] Fix: Fix casing --- DawPlugin/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 8068d6108..51c975fd5 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.24) -set(DPF_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/DPF") +set(DPF_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/dpf") if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(PROJECT_NAME openutau_daw_plugin_debug) @@ -55,8 +55,8 @@ target_include_directories( ${PROJECT_NAME} PUBLIC "src" "deps" - "deps/DPF/dgl" - "deps" + "deps/dpf/dgl" + "deps/dpf/distrho" "deps/asio/asio/include" "deps/dpf_widgets" "deps/uuid-v4" From a1f11249de6cd36bf3b4869cb253a2c81692b3c2 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 21:04:58 +0900 Subject: [PATCH 29/72] Fix: Fix rename --- DawPlugin/deps/{DPF => dpf} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename DawPlugin/deps/{DPF => dpf} (100%) diff --git a/DawPlugin/deps/DPF b/DawPlugin/deps/dpf similarity index 100% rename from DawPlugin/deps/DPF rename to DawPlugin/deps/dpf From dd6abbfb75b46a2806e50ace5903a9ea9ac1ef0d Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 21:10:13 +0900 Subject: [PATCH 30/72] Fix: Fix name overlap --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c71d71a70..95a6f1c8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: run: | # mkdir -p DawPlugin/build/bin/openutau_daw_plugin.vst3 cd DawPlugin/build/bin - 7z a openutau_daw_plugin.vst3.zip openutau_daw_plugin.vst3 + 7z a openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip openutau_daw_plugin.vst3 if: ${{ matrix.arch.arch == 'x64' }} # Create Zip @@ -147,8 +147,8 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: openutau_daw_plugin.vst3.zip - path: DawPlugin/build/bin/openutau_daw_plugin.vst3.zip + name: openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip + path: DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip if: ${{ !inputs.release && matrix.arch.arch == 'x64' }} # Appcast @@ -180,5 +180,5 @@ jobs: files: | appcast.${{ matrix.arch.name }}*.xml OpenUtau-${{ matrix.arch.name }}.* - DawPlugin/build/bin/openutau_daw_plugin.vst3.zip + DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip if: ${{ inputs.release }} From ae5f07356257dbe7c17b86d54f068d6615d13821 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 21:15:47 +0900 Subject: [PATCH 31/72] Fix: Fix zlib building on macos or ubuntu --- .github/workflows/build.yml | 5 +++++ DawPlugin/CMakeLists.txt | 5 +++++ DawPlugin/src/plugin.cpp | 15 ++++----------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95a6f1c8d..e02e150ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,11 @@ jobs: if: ${{ matrix.arch.os != 'osx' }} # Build: Daw Plugin + - name: Install OpenGL + run: | + sudo apt-get install libgl1-mesa-dev + if: ${{ github.os == 'ubuntu-latest' }} + - name: Build Daw Plugin run: | cd ./DawPlugin diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 51c975fd5..45a126a59 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -20,6 +20,11 @@ if(MSVC) add_compile_options("/utf-8") endif() +# MacOS only: use -Wno-implicit-function-declaration for zlib +if(APPLE) + add_compile_options("-Wno-implicit-function-declaration") +endif() + if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DDEBUG) add_definitions(-DDPF_DEBUG) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index bee55d4f3..b3b39aa33 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -6,8 +6,9 @@ #include "choc/containers/choc_Value.h" #include "choc/memory/choc_Base64.h" #include "choc/text/choc_JSON.h" +#include "gzip/compress.hpp" #include "common.hpp" -#include "extra/String.hpp" +#include "dpf/distrho/extra/String.hpp" #include "uuid/v4/uuid.h" #include #include @@ -19,14 +20,6 @@ #include #include -#if CHOC_OSX -// Workaround for zlib -// https://github.com/Blosc/python-blosc/issues/229#issuecomment-676819560 -#include -#endif - -#include "gzip/compress.hpp" - namespace Network { std::shared_ptr ioThread; std::shared_ptr ioContext; @@ -467,8 +460,8 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, auto currentTime = std::chrono::system_clock::now(); if (currentTime - self->lastPing > std::chrono::seconds(5)) { - socket->write_some(asio::buffer( - formatMessage("notification:ping", choc::value::createObject("")))); + socket->write_some(asio::buffer(formatMessage( + "notification:ping", choc::value::createObject("")))); self->lastPing = currentTime; } } From 9cad3d23fa47d88f81c685582fa8d7abe8a43ae1 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 21:23:41 +0900 Subject: [PATCH 32/72] Fix: Fix build args --- .github/workflows/build.yml | 2 +- DawPlugin/CMakeLists.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e02e150ce..5e83383f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,7 +70,7 @@ jobs: - name: Install OpenGL run: | sudo apt-get install libgl1-mesa-dev - if: ${{ github.os == 'ubuntu-latest' }} + if: ${{ matrix.arch.os == 'linux' }} - name: Build Daw Plugin run: | diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 45a126a59..6d54388cd 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -15,9 +15,12 @@ endif() include(./deps/dpf/cmake/DPF-plugin.cmake) project(${PROJECT_NAME}) -# MSVC only: Use UTF-8 code page +# MSVC only: +# - Use UTF-8 code page +# - Make asio use Windows 10 APIs if(MSVC) add_compile_options("/utf-8") + add_definitions(-D_WIN32_WINNT=0x0A00) endif() # MacOS only: use -Wno-implicit-function-declaration for zlib @@ -32,9 +35,6 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release") # nothing atm endif() -# Windows 10 -add_definitions(-D_WIN32_WINNT=0x0A00) - dpf_add_plugin( ${PROJECT_NAME} TARGETS From 3df9dc2496744f71ae8314dec08677917984c40f Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 25 Sep 2024 21:39:35 +0900 Subject: [PATCH 33/72] Fix: Fix compiler version --- .github/workflows/build.yml | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e83383f2..17105e994 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,17 +67,36 @@ jobs: if: ${{ matrix.arch.os != 'osx' }} # Build: Daw Plugin - - name: Install OpenGL + + - name: Build Daw Plugin (Windows) run: | - sudo apt-get install libgl1-mesa-dev - if: ${{ matrix.arch.os == 'linux' }} + cd ./DawPlugin + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake --build build --config Release + if: ${{ matrix.arch.name == 'win-x64' }} - - name: Build Daw Plugin + - name: "Setup XCode 15.4" + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.4' + if: ${{ matrix.arch.os == 'osx' }} + - name: Build Daw Plugin (Mac) run: | cd ./DawPlugin + sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.arch.arch == 'x64' }} + if: ${{ matrix.arch.os == 'osx' }} + + - name: Build Daw Plugin (Linux) + run: | + cd ./DawPlugin + sudo apt-get install libgl1-mesa-dev gcc-14 g++-14 + export CC=gcc-14 + export CXX=g++-14 + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake --build build --config Release + if: ${{ matrix.arch.os == 'linux' }} - name: Package Daw Plugin shell: bash From 5c3d9c0c42169eef82ddd05c2d47898ab1e615d5 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 25 Sep 2024 21:58:02 +0900 Subject: [PATCH 34/72] Fix: Use gcc 13 --- .github/workflows/build.yml | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17105e994..49760a47e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,13 +30,13 @@ jobs: fail-fast: false matrix: arch: - - { name: win-x64, rid: win-x64, arch: x64, os: win, runs-on: windows-latest } - - { name: win-x86, rid: win-x86, arch: x86, os: win, runs-on: windows-latest } - - { name: win-arm64, rid: win-arm64, arch: arm64, os: win, runs-on: windows-latest } - - { name: osx-x64, rid: osx.10.14-x64, arch: x64, os: osx, runs-on: macos-13 } - - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13 } - - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-latest } - - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-latest } + - { name: win-x64, rid: win-x64, arch: x64, os: win, runs-on: windows-latest, daw-plugin: true } + - { name: win-x86, rid: win-x86, arch: x86, os: win, runs-on: windows-latest, daw-plugin: false } + - { name: win-arm64, rid: win-arm64, arch: arm64, os: win, runs-on: windows-latest, daw-plugin: false } + - { name: osx-x64, rid: osx.10.14-x64, arch: x64, os: osx, runs-on: macos-13, daw-plugin: false } + - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13, daw-plugin: false } + - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-latest, daw-plugin: true } + - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-latest, daw-plugin: false } steps: # Setup @@ -73,30 +73,26 @@ jobs: cd ./DawPlugin cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.arch.name == 'win-x64' }} + if: ${{ matrix.daw-plugin && matrix.arch.os == 'win' }} - - name: "Setup XCode 15.4" - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '15.4' - if: ${{ matrix.arch.os == 'osx' }} - name: Build Daw Plugin (Mac) run: | cd ./DawPlugin sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.arch.os == 'osx' }} + if: ${{ matrix.daw-plugin && matrix.arch.os == 'osx' }} - name: Build Daw Plugin (Linux) run: | cd ./DawPlugin - sudo apt-get install libgl1-mesa-dev gcc-14 g++-14 - export CC=gcc-14 - export CXX=g++-14 + sudo apt-get install libgl1-mesa-dev gcc-13 g++-13 + + export CC=gcc-13 + export CXX=g++-3 cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.arch.os == 'linux' }} + if: ${{ matrix.daw-plugin && matrix.arch.os == 'linux' }} - name: Package Daw Plugin shell: bash @@ -104,7 +100,7 @@ jobs: # mkdir -p DawPlugin/build/bin/openutau_daw_plugin.vst3 cd DawPlugin/build/bin 7z a openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip openutau_daw_plugin.vst3 - if: ${{ matrix.arch.arch == 'x64' }} + if: ${{ matrix.daw-plugin }} # Create Zip - name: DirectML From 5457c307d0ac648e948b464bdeadbf07c8a6c1d6 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 25 Sep 2024 22:03:59 +0900 Subject: [PATCH 35/72] Fix: Fix condition --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49760a47e..8e17c8d20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -169,7 +169,7 @@ jobs: with: name: openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip path: DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip - if: ${{ !inputs.release && matrix.arch.arch == 'x64' }} + if: ${{ !inputs.release && matrix.daw-plugin }} # Appcast - name: Appcast Windows From 5dc5da19c06760a271ed919b7e80e39440606fb2 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 25 Sep 2024 22:18:01 +0900 Subject: [PATCH 36/72] Delete: Delete compile_flags.txt --- compile_flags.txt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 compile_flags.txt diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index 505ec2821..000000000 --- a/compile_flags.txt +++ /dev/null @@ -1,12 +0,0 @@ --xc++ --std=c++20 --I./DawPlugin/src --I./DawPlugin/deps/dpf/distrho --I./DawPlugin/deps/dpf/dgl --I./DawPlugin/deps/asio/asio/include --I./DawPlugin/deps/dpf_widgets --I./DawPlugin/deps/zlib --I./DawPlugin/deps/gzip-hpp/include --I./DawPlugin/deps/uuid-v4 --I./DawPlugin/deps/yamc/include --I./DawPlugin/deps/ From 7e3ebca4c6a6c52d1edde66c477229b9cb25cdaa Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 25 Sep 2024 22:18:51 +0900 Subject: [PATCH 37/72] Fix: Fix matrix reference --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e17c8d20..02acddb29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: cd ./DawPlugin cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.daw-plugin && matrix.arch.os == 'win' }} + if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'win' }} - name: Build Daw Plugin (Mac) run: | @@ -81,7 +81,7 @@ jobs: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.daw-plugin && matrix.arch.os == 'osx' }} + if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} - name: Build Daw Plugin (Linux) run: | @@ -92,7 +92,7 @@ jobs: export CXX=g++-3 cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release - if: ${{ matrix.daw-plugin && matrix.arch.os == 'linux' }} + if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' }} - name: Package Daw Plugin shell: bash @@ -100,7 +100,7 @@ jobs: # mkdir -p DawPlugin/build/bin/openutau_daw_plugin.vst3 cd DawPlugin/build/bin 7z a openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip openutau_daw_plugin.vst3 - if: ${{ matrix.daw-plugin }} + if: ${{ matrix.arch.daw-plugin }} # Create Zip - name: DirectML @@ -169,7 +169,7 @@ jobs: with: name: openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip path: DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip - if: ${{ !inputs.release && matrix.daw-plugin }} + if: ${{ !inputs.release && matrix.arch.daw-plugin }} # Appcast - name: Appcast Windows From 8986d9f48bda661133ffc36cdd3f91102405dd52 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 25 Sep 2024 22:25:48 +0900 Subject: [PATCH 38/72] Fix: Build gcc14 --- .github/workflows/build.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02acddb29..84f542b2b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,8 +88,16 @@ jobs: cd ./DawPlugin sudo apt-get install libgl1-mesa-dev gcc-13 g++-13 - export CC=gcc-13 - export CXX=g++-3 + # https://askubuntu.com/a/1518433 + wget http://ftp.gnu.org/gnu/gcc/gcc-14.1.0/gcc-14.1.0.tar.gz + tar -xf gcc-14.1.0.tar.gz + cd gcc-14.1.0 + ./configure -v --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --prefix=/usr/local/gcc-14.1.0 --enable-checking=release --enable-languages=c,c++ --disable-multilib --program-suffix=-14.1.0 + make + sudo make install + sudo update-alternatives --install /usr/bin/g++ g++ /usr/local/gcc-14.1.0/bin/g++14.1.0 14 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/local/gcc-14.1.0/bin/gcc14.1.0 14 + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' }} From 6c519e422f05dc049d9684562e51617bbf275249 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 25 Sep 2024 22:49:14 +0900 Subject: [PATCH 39/72] Fix: Fix package list --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84f542b2b..6f0d7499e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: - name: Build Daw Plugin (Linux) run: | cd ./DawPlugin - sudo apt-get install libgl1-mesa-dev gcc-13 g++-13 + sudo apt-get install libgl1-mesa-dev # https://askubuntu.com/a/1518433 wget http://ftp.gnu.org/gnu/gcc/gcc-14.1.0/gcc-14.1.0.tar.gz From 87aaa1dbe84fa7243f82dd58bf8ca86b1e475efc Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 06:10:54 +0900 Subject: [PATCH 40/72] Fix: Add dependency for building GCC --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f0d7499e..1bc2f1cf1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,9 +86,11 @@ jobs: - name: Build Daw Plugin (Linux) run: | cd ./DawPlugin + # Install OpenGL sudo apt-get install libgl1-mesa-dev # https://askubuntu.com/a/1518433 + sudo apt install libmpfr-dev libgmp3-dev libmpc-dev -y wget http://ftp.gnu.org/gnu/gcc/gcc-14.1.0/gcc-14.1.0.tar.gz tar -xf gcc-14.1.0.tar.gz cd gcc-14.1.0 From 87105f76d89d92eca3a33655782e4fe96a6fd338 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 06:30:08 +0900 Subject: [PATCH 41/72] Improve: Cache gcc --- .github/workflows/build.yml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1bc2f1cf1..3f91ffcce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,11 +83,17 @@ jobs: cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} - - name: Build Daw Plugin (Linux) + - name: Cache GCC (Linux) + id: cache-gcc + uses: actions/cache@v4 + with: + path: /usr/local/gcc-14.1.0 + key: gcc-14.1.0 + restore-keys: gcc-14.1.0 + if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' }} + - name: Build GCC (Linux) run: | - cd ./DawPlugin - # Install OpenGL - sudo apt-get install libgl1-mesa-dev + cd /tmp # https://askubuntu.com/a/1518433 sudo apt install libmpfr-dev libgmp3-dev libmpc-dev -y @@ -97,9 +103,18 @@ jobs: ./configure -v --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --prefix=/usr/local/gcc-14.1.0 --enable-checking=release --enable-languages=c,c++ --disable-multilib --program-suffix=-14.1.0 make sudo make install + if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' && steps.cache-gcc.outputs.cache-hit != 'true' }} + - name: Build Daw Plugin (Linux) + run: | + cd ./DawPlugin + + # Configure GCC sudo update-alternatives --install /usr/bin/g++ g++ /usr/local/gcc-14.1.0/bin/g++14.1.0 14 sudo update-alternatives --install /usr/bin/gcc gcc /usr/local/gcc-14.1.0/bin/gcc14.1.0 14 + # Install OpenGL + sudo apt-get install libgl1-mesa-dev + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' }} From 9694df6e185e922d1ae5a352d5f575e19d4df4b1 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 16:25:28 +0900 Subject: [PATCH 42/72] Change: Get gcc from apt --- .github/workflows/build.yml | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3f91ffcce..56c457444 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,34 +83,15 @@ jobs: cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} - - name: Cache GCC (Linux) - id: cache-gcc - uses: actions/cache@v4 - with: - path: /usr/local/gcc-14.1.0 - key: gcc-14.1.0 - restore-keys: gcc-14.1.0 - if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' }} - - name: Build GCC (Linux) - run: | - cd /tmp - - # https://askubuntu.com/a/1518433 - sudo apt install libmpfr-dev libgmp3-dev libmpc-dev -y - wget http://ftp.gnu.org/gnu/gcc/gcc-14.1.0/gcc-14.1.0.tar.gz - tar -xf gcc-14.1.0.tar.gz - cd gcc-14.1.0 - ./configure -v --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --prefix=/usr/local/gcc-14.1.0 --enable-checking=release --enable-languages=c,c++ --disable-multilib --program-suffix=-14.1.0 - make - sudo make install - if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' && steps.cache-gcc.outputs.cache-hit != 'true' }} - name: Build Daw Plugin (Linux) run: | cd ./DawPlugin # Configure GCC - sudo update-alternatives --install /usr/bin/g++ g++ /usr/local/gcc-14.1.0/bin/g++14.1.0 14 - sudo update-alternatives --install /usr/bin/gcc gcc /usr/local/gcc-14.1.0/bin/gcc14.1.0 14 + sudo add-apt-repository universe + sudo apt update -qq + sudo apt install -y gcc-14 g++-14 libpcre3 liblapack-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60 --slave /usr/bin/g++ g++ /usr/bin/g++-14 # Install OpenGL sudo apt-get install libgl1-mesa-dev From 05004fb1a5bbcf21f87cbe6c0722c07746e967bb Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 17:24:10 +0900 Subject: [PATCH 43/72] Change: Use gcc --- .github/workflows/build.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56c457444..9795c5538 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,11 +32,11 @@ jobs: arch: - { name: win-x64, rid: win-x64, arch: x64, os: win, runs-on: windows-latest, daw-plugin: true } - { name: win-x86, rid: win-x86, arch: x86, os: win, runs-on: windows-latest, daw-plugin: false } - - { name: win-arm64, rid: win-arm64, arch: arm64, os: win, runs-on: windows-latest, daw-plugin: false } + - { name: win-arm64, rid: win-arm64, arch: arm64, os: win, runs-on: windows-latest, daw-plugin: true } - { name: osx-x64, rid: osx.10.14-x64, arch: x64, os: osx, runs-on: macos-13, daw-plugin: false } - - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13, daw-plugin: false } - - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-latest, daw-plugin: true } - - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-latest, daw-plugin: false } + - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13, daw-plugin: true } + - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-24.04, daw-plugin: true } + - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-24.04, daw-plugin: true } steps: # Setup @@ -78,7 +78,9 @@ jobs: - name: Build Daw Plugin (Mac) run: | cd ./DawPlugin - sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer + brew install gcc + export CC=gcc + export CXX=g++ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} @@ -88,10 +90,8 @@ jobs: cd ./DawPlugin # Configure GCC - sudo add-apt-repository universe - sudo apt update -qq - sudo apt install -y gcc-14 g++-14 libpcre3 liblapack-dev - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60 --slave /usr/bin/g++ g++ /usr/bin/g++-14 + export CC=gcc + export CXX=g++ # Install OpenGL sudo apt-get install libgl1-mesa-dev From 315e237ccccb32c353dde57a7cca4639e4045d6f Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 17:32:44 +0900 Subject: [PATCH 44/72] Fix: Fix compiler specification --- .github/workflows/build.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9795c5538..949bcfce4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,10 +78,8 @@ jobs: - name: Build Daw Plugin (Mac) run: | cd ./DawPlugin - brew install gcc - export CC=gcc - export CXX=g++ - cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} @@ -89,14 +87,11 @@ jobs: run: | cd ./DawPlugin - # Configure GCC - export CC=gcc - export CXX=g++ - # Install OpenGL sudo apt-get install libgl1-mesa-dev - cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'linux' }} From 40299227912ad844121f8fa4e5da5e6ec4073ce3 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 17:33:45 +0900 Subject: [PATCH 45/72] Change: Build plugin on mac x64 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 949bcfce4..c5b78e603 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: - { name: win-x64, rid: win-x64, arch: x64, os: win, runs-on: windows-latest, daw-plugin: true } - { name: win-x86, rid: win-x86, arch: x86, os: win, runs-on: windows-latest, daw-plugin: false } - { name: win-arm64, rid: win-arm64, arch: arm64, os: win, runs-on: windows-latest, daw-plugin: true } - - { name: osx-x64, rid: osx.10.14-x64, arch: x64, os: osx, runs-on: macos-13, daw-plugin: false } + - { name: osx-x64, rid: osx.10.14-x64, arch: x64, os: osx, runs-on: macos-13, daw-plugin: true } - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13, daw-plugin: true } - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-24.04, daw-plugin: true } - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-24.04, daw-plugin: true } From dee91ec6c5099ed3e0e640e4423a6f0f294100d5 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 17:34:12 +0900 Subject: [PATCH 46/72] Fix: Include thread --- DawPlugin/src/plugin.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index 94ffeb81a..9901a1b8f 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include From 43fe2cd42765a1aebdc147b1243b3f143eae3084 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 17:34:33 +0900 Subject: [PATCH 47/72] Fix: Include cfloat --- DawPlugin/src/plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index b3b39aa33..8b49d3ac3 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -10,6 +10,7 @@ #include "common.hpp" #include "dpf/distrho/extra/String.hpp" #include "uuid/v4/uuid.h" +#include #include #include #include From 2416a21c0fb9b7b55dcc4847696c754abbc1a555 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 18:07:25 +0900 Subject: [PATCH 48/72] Fix: Fix building arguments --- .github/workflows/build.yml | 2 +- DawPlugin/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5b78e603..5fff0fd8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: run: | cd ./DawPlugin cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 6d54388cd..0db326799 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -64,6 +64,7 @@ target_include_directories( "deps/dpf/distrho" "deps/asio/asio/include" "deps/dpf_widgets" + "deps/dpf_widgets/opengl" "deps/uuid-v4" "deps/zlib" "deps/yamc/include" From 1770cde3d65b245e58cea51715cd8b98110a0e44 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 18:17:05 +0900 Subject: [PATCH 49/72] Fix: Fix some compiler differences --- .github/workflows/build.yml | 4 +++- DawPlugin/src/ui.cpp | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fff0fd8f..071b1421d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -78,8 +78,10 @@ jobs: - name: Build Daw Plugin (Mac) run: | cd ./DawPlugin + + brew install llvm cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index 3af02984e..51d3e99ea 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -79,7 +79,11 @@ class OpenUtauUI : public UI { plugin->name = nameBuffer; } else if (!(ImGui::IsItemActive() && ImGui::TempInputIsActive(ImGui::GetActiveID()))) { +#if CHOC_WINDOWS strncpy_s(nameBuffer, plugin->name.c_str(), sizeof(nameBuffer)); +#else + strncpy(nameBuffer, plugin->name.c_str(), sizeof(nameBuffer)); +#endif } partiallyColoredText( @@ -107,14 +111,14 @@ class OpenUtauUI : public UI { ImGui::TextColored(themeBlueColor, "Processing"); } else { if (plugin->lastSync) { - auto lastSyncDuration = + long lastSyncDuration = std::chrono::duration_cast( std::chrono::system_clock::now() - *plugin->lastSync) .count(); if (lastSyncDuration > 60) { - ImGui::Text("%lldm ago", lastSyncDuration / 60); + ImGui::Text("%ldm ago", lastSyncDuration / 60); } else { - ImGui::TextColored(themePinkColor, "%llds ago", lastSyncDuration); + ImGui::TextColored(themePinkColor, "%lds ago", lastSyncDuration); } } else { ImGui::TextColored(style.Colors[ImGuiCol_TextDisabled], "N/A"); From 86d1d59a2a3db023d672fbe6bc58815a3848aad8 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 18:30:40 +0900 Subject: [PATCH 50/72] Fix: Add LDFLAGS --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 071b1421d..05c1f0c8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,6 +80,10 @@ jobs: cd ./DawPlugin brew install llvm + export PATH="/usr/local/opt/llvm/bin:$PATH" + export LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib -lunwind -fexperimental-library" + export CPPFLAGS="-I/usr/local/opt/llvm/include" + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ cmake --build build --config Release From c897709dadf8930a6efaba6e32a7e44aba2d8294 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 18:59:57 +0900 Subject: [PATCH 51/72] Add: Add expermintal-library to other options --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05c1f0c8f..5e9c574e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,10 +82,11 @@ jobs: brew install llvm export PATH="/usr/local/opt/llvm/bin:$PATH" export LDFLAGS="-L/usr/local/opt/llvm/lib/c++ -L/usr/local/opt/llvm/lib -lunwind -fexperimental-library" - export CPPFLAGS="-I/usr/local/opt/llvm/include" + export CPPFLAGS="-I/usr/local/opt/llvm/include -fexperimental-library" cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ + -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ \ + -DCMAKE_CXX_FLAGS="-stdlib=libc++ -fexperimental-library" cmake --build build --config Release if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} From d0f0bd5d58776e43168f5295cd4c8d89cd990c90 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 19:08:59 +0900 Subject: [PATCH 52/72] Add: Add initPortGroup --- DawPlugin/src/plugin.cpp | 14 +++++++--- DawPlugin/src/plugin.hpp | 59 +++++----------------------------------- 2 files changed, 17 insertions(+), 56 deletions(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 8b49d3ac3..537a1d3a6 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -6,9 +6,9 @@ #include "choc/containers/choc_Value.h" #include "choc/memory/choc_Base64.h" #include "choc/text/choc_JSON.h" -#include "gzip/compress.hpp" #include "common.hpp" #include "dpf/distrho/extra/String.hpp" +#include "gzip/compress.hpp" #include "uuid/v4/uuid.h" #include #include @@ -268,9 +268,17 @@ uint32_t OpenUtauPlugin::getVersion() const { void OpenUtauPlugin::initAudioPort(bool input, uint32_t index, AudioPort &port) { port.groupId = index / 2; - port.hints = kPortGroupStereo; auto name = std::format("Channel {}", index / 2 + 1); + auto symbol = + std::format("channel-{}-{}", index / 2, index % 2 == 0 ? "l" : "r"); port.name = String(name.c_str()); + port.symbol = String(symbol.c_str()); +} +void OpenUtauPlugin::initPortGroup(uint32_t groupId, PortGroup &group) { + auto name = std::format("Group {}", groupId + 1); + auto symbol = std::format("group-{}", groupId); + group.symbol = String(symbol.c_str()); + group.name = String(name.c_str()); } void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, @@ -344,8 +352,6 @@ void OpenUtauPlugin::run(const float **inputs, float **outputs, uint32_t frames, } }; -void OpenUtauPlugin::bufferSizeChanged(uint32_t newBufferSize) {} - void OpenUtauPlugin::sampleRateChanged(double newSampleRate) { requestResampleMixes(newSampleRate); } diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index 9901a1b8f..acb4ff735 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -30,9 +30,6 @@ class Part { // ----------------------------------------------------------------------------------------------------------- -/** - Plugin to show how to get somebasic information sent to the UI. - */ class OpenUtauPlugin : public Plugin { public: OpenUtauPlugin(); @@ -50,29 +47,13 @@ class OpenUtauPlugin : public Plugin { bool isProcessing(); protected: - /* -------------------------------------------------------------------------------------------------------- - * Information */ - - /** - Get the plugin label. - This label is a short restricted name consisting of only _, a-z, A-Z - and 0-9 characters. - */ + // -------------------------------------------------------------------------------------------------------- const char *getLabel() const override; - /** - Get an extensive comment/description about the plugin. - */ const char *getDescription() const override; - /** - Get the plugin author/maker. - */ const char *getMaker() const override; - /** - Get the plugin homepage. - */ const char *getHomePage() const override; void initState(uint32_t index, State &state) override; @@ -81,44 +62,20 @@ class OpenUtauPlugin : public Plugin { void setState(const char *key, const char *value) override; - /** - Get the plugin license name (a single line of text). - For commercial plugins this should return some short copyright information. - */ const char *getLicense() const override; - /** - Get the plugin version, in hexadecimal. - */ uint32_t getVersion() const override; - /* -------------------------------------------------------------------------------------------------------- - * Init */ - - /** - Initialize the audio port @a index.@n - This function will be called once, shortly after the plugin is created. - */ + // -------------------------------------------------------------------------------------------------------- void initAudioPort(bool input, uint32_t index, AudioPort &port) override; + void initPortGroup(uint32_t groupId, PortGroup &group) override; - /* -------------------------------------------------------------------------------------------------------- - * Audio/MIDI Processing */ + // -------------------------------------------------------------------------------------------------------- void run(const float **inputs, float **outputs, uint32_t frames, const MidiEvent *midiEvents, uint32_t midiEventCount) override; - ; - - /* -------------------------------------------------------------------------------------------------------- - * Callbacks (optional) */ - /** - Optional callback to inform the plugin about a buffer size change. - This function will only be called when the plugin is deactivated. - @note This value is only a hint! - Hosts might call run() with a higher or lower number of - frames. - */ - void bufferSizeChanged(uint32_t newBufferSize) override; + // -------------------------------------------------------------------------------------------------------- void sampleRateChanged(double newSampleRate) override; @@ -149,7 +106,8 @@ class OpenUtauPlugin : public Plugin { std::chrono::time_point lastPing; - yamc::alternate::basic_shared_mutex audioBuffersMutex; + yamc::alternate::basic_shared_mutex + audioBuffersMutex; std::map> audioBuffers; yamc::alternate::basic_shared_mutex partsMutex; @@ -169,9 +127,6 @@ class OpenUtauPlugin : public Plugin { yamc::alternate::basic_shared_mutex tracksMutex; std::mutex partMutex; - /** - Set our plugin class as non-copyable and add a leak detector just in case. - */ DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenUtauPlugin) }; // ----------------------------------------------------------------------------------------------------------- From 33a8277b094686249ea0e3dda688b0d09761a5f3 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 26 Sep 2024 20:20:00 +0900 Subject: [PATCH 53/72] Change: Change font --- .github/workflows/build.yml | 15 ++++++++++++++- DawPlugin/.gitignore | 2 ++ DawPlugin/CMakeLists.txt | 23 ++++++++++++++++++++++- DawPlugin/src/plugin.cpp | 12 +++++++----- DawPlugin/src/plugin.hpp | 1 + DawPlugin/src/ui.cpp | 36 +++++++++++++++++++++++++++++++++--- 6 files changed, 79 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e9c574e5..305cb3717 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,11 +105,17 @@ jobs: - name: Package Daw Plugin shell: bash run: | - # mkdir -p DawPlugin/build/bin/openutau_daw_plugin.vst3 cd DawPlugin/build/bin 7z a openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip openutau_daw_plugin.vst3 if: ${{ matrix.arch.daw-plugin }} + - name: Package Daw Plugin (Mac) + shell: bash + run: | + cd DawPlugin/build/bin + 7z a openutau_daw_plugin-${{ matrix.arch.name }}.au.zip openutau_daw_plugin.component + if: ${{ matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} + # Create Zip - name: DirectML shell: cmd @@ -179,6 +185,12 @@ jobs: path: DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip if: ${{ !inputs.release && matrix.arch.daw-plugin }} + - uses: actions/upload-artifact@v4 + with: + name: openutau_daw_plugin-${{ matrix.arch.name }}.au.zip + path: DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.au.zip + if: ${{ !inputs.release && matrix.arch.daw-plugin && matrix.arch.os == 'osx' }} + # Appcast - name: Appcast Windows shell: cmd @@ -209,4 +221,5 @@ jobs: appcast.${{ matrix.arch.name }}*.xml OpenUtau-${{ matrix.arch.name }}.* DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.vst3.zip + DawPlugin/build/bin/openutau_daw_plugin-${{ matrix.arch.name }}.au.zip if: ${{ inputs.release }} diff --git a/DawPlugin/.gitignore b/DawPlugin/.gitignore index 52a12e154..b8262d117 100644 --- a/DawPlugin/.gitignore +++ b/DawPlugin/.gitignore @@ -21,3 +21,5 @@ _deps # End of https://www.toptal.com/developers/gitignore/api/cmake build/ +!deps/noto_sans/.gitkeep +deps/noto_sans/* diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 0db326799..ae1272177 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -12,15 +12,33 @@ else() message(FATAL_ERROR "Unknown build type: ${CMAKE_BUILD_TYPE}") endif() +set(NOTO_SANS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/noto_sans") +if(NOT EXISTS "${NOTO_SANS_DIR}/otf") + file(DOWNLOAD + "https://github.com/notofonts/noto-cjk/releases/download/Sans2.004/05_NotoSansCJK-SubsetOTF.zip" + "${NOTO_SANS_DIR}/05_NotoSansCJK-SubsetOTF.zip") + file(ARCHIVE_EXTRACT INPUT "${NOTO_SANS_DIR}/05_NotoSansCJK-SubsetOTF.zip" + DESTINATION "${NOTO_SANS_DIR}/otf") +endif() +if(NOT EXISTS "${NOTO_SANS_DIR}/noto_sans.hpp") + execute_process(COMMAND xxd -i "${NOTO_SANS_DIR}/otf/SubsetOTF/JP/NotoSansJP-Regular.otf" "${NOTO_SANS_DIR}/noto_sans.raw.hpp") + file(READ "${NOTO_SANS_DIR}/noto_sans.raw.hpp" NOTO_SANS_HPP) + string(REGEX REPLACE "unsigned char .+\\[\\]" "const unsigned char notoSansJpRegular[]" NOTO_SANS_HPP "${NOTO_SANS_HPP}") + string(REGEX REPLACE "unsigned int .+_len" "const unsigned int notoSansJpRegularLen" NOTO_SANS_HPP "${NOTO_SANS_HPP}") + file(WRITE "${NOTO_SANS_DIR}/noto_sans.hpp" "${NOTO_SANS_HPP}") +endif() + include(./deps/dpf/cmake/DPF-plugin.cmake) project(${PROJECT_NAME}) # MSVC only: # - Use UTF-8 code page # - Make asio use Windows 10 APIs +# - Enable IME if(MSVC) add_compile_options("/utf-8") add_definitions(-D_WIN32_WINNT=0x0A00) + add_definitions(-DIMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) endif() # MacOS only: use -Wno-implicit-function-declaration for zlib @@ -35,10 +53,13 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release") # nothing atm endif() +# Disable DGL's default font +add_definitions(-DDGL_NO_SHARED_RESOURCES=1) + dpf_add_plugin( ${PROJECT_NAME} TARGETS - vst3 + vst3 au UI_TYPE opengl FILES_COMMON diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 537a1d3a6..999b32eb9 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -660,6 +660,7 @@ void OpenUtauPlugin::requestResampleMixes(double newSampleRate) { void OpenUtauPlugin::resampleMixes(double newSampleRate) { auto _lock = std::unique_lock(this->mixMutex); + this->mixMutexLocked = true; auto _lock2 = std::shared_lock(this->audioBuffersMutex); auto _lock3 = std::shared_lock(this->partsMutex); @@ -721,6 +722,7 @@ void OpenUtauPlugin::resampleMixes(double newSampleRate) { } this->mixes = mixes; this->currentSampleRate = newSampleRate; + this->mixMutexLocked = false; } std::string @@ -731,11 +733,11 @@ OpenUtauPlugin::formatMessage(const std::string &kind, } bool OpenUtauPlugin::isProcessing() { - if (!this->mixMutex.try_lock()) { - return true; - } - this->mixMutex.unlock(); - return false; + // Doing try_lock() and unlock() causes some flickering in the UI, so use + // flag instead. I know it's not the best practice, but this is only for a + // UI, which does not affect the audio processing nor causes some critical + // issues. + return this->mixMutexLocked; } // ------------------------------------------------------------------------------------------------------------ diff --git a/DawPlugin/src/plugin.hpp b/DawPlugin/src/plugin.hpp index acb4ff735..045f279cd 100644 --- a/DawPlugin/src/plugin.hpp +++ b/DawPlugin/src/plugin.hpp @@ -114,6 +114,7 @@ class OpenUtauPlugin : public Plugin { std::map> parts; yamc::alternate::basic_shared_mutex mixMutex; + bool mixMutexLocked = false; std::vector, std::vector>> mixes; double currentSampleRate = 44100.0; diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index 51d3e99ea..9c6e80c80 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -3,6 +3,7 @@ #include "common.hpp" #include "dpf_widgets/generic/ResizeHandle.hpp" #include "dpf_widgets/opengl/DearImGui/imgui.h" +#include "noto_sans/noto_sans.hpp" #include "plugin.hpp" #include @@ -10,7 +11,7 @@ START_NAMESPACE_DISTRHO // -------------------------------------------------------------------------------------------------------------------- -int fontSize = 13.f; +int fontSize = 16.f; // #ff679d static auto themePinkColor = ImVec4(1.0f, 0.4f, 0.6f, 1.0f); @@ -37,8 +38,7 @@ class OpenUtauUI : public UI { resizeHandle.hide(); setTheme(); - - setFontSize(fontSize); + setFont(); } bool showDemoWindow = false; @@ -235,6 +235,36 @@ class OpenUtauUI : public UI { ImVec4(hoverColor.w, hoverColor.x, hoverColor.y, 0.40f); } + // TODO: Implement Chinese font properly + // https://heistak.github.io/your-code-displays-japanese-wrong/ + void setFont() { + const double scaleFactor = getScaleFactor(); + + auto &io = ImGui::GetIO(); + ImFontConfig fc; + fc.FontDataOwnedByAtlas = false; + fc.OversampleH = 1; + fc.OversampleV = 1; + fc.PixelSnapH = true; + + ImFontGlyphRangesBuilder rangeBuilder; + static ImVector ranges; + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesDefault()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesKorean()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesCyrillic()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesVietnamese()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseFull()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesThai()); + rangeBuilder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesGreek()); + rangeBuilder.BuildRanges(&ranges); + + io.Fonts->AddFontFromMemoryTTF((void *)notoSansJpRegular, + notoSansJpRegularLen, fontSize * scaleFactor, + &fc, ranges.Data); + io.Fonts->Build(); + } + void partiallyColoredText(const std::string &text, const ImVec4 &color) { std::string remainingText = text; while (remainingText.size() > 0) { From d41be379b4469f73a4276f8a278cbbfb3f7a8d53 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Thu, 26 Sep 2024 21:13:51 +0900 Subject: [PATCH 54/72] Add: Add CLAP ID --- DawPlugin/src/DistrhoPluginInfo.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DawPlugin/src/DistrhoPluginInfo.h b/DawPlugin/src/DistrhoPluginInfo.h index 7adc230a7..504edefe9 100644 --- a/DawPlugin/src/DistrhoPluginInfo.h +++ b/DawPlugin/src/DistrhoPluginInfo.h @@ -12,8 +12,10 @@ #define DISTRHO_PLUGIN_BRAND_ID Stak #ifdef DEBUG #define DISTRHO_PLUGIN_UNIQUE_ID OpUD +#define DISTRHO_PLUGIN_CLAP_ID "stakira.openutau-bridge-debug" #else #define DISTRHO_PLUGIN_UNIQUE_ID OpUt +#define DISTRHO_PLUGIN_CLAP_ID "stakira.openutau-bridge" #endif #define DISTRHO_PLUGIN_HAS_UI 1 From ed43e45824607c0c355e29192cc3f6537c2db3e3 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 8 Dec 2024 17:08:00 +0900 Subject: [PATCH 55/72] Improve: Improve state --- DawPlugin/src/common.cpp | 1 + DawPlugin/src/plugin.cpp | 69 +++++++++++-------- OpenUtau.Core/DawIntegration/DawManager.cs | 6 +- OpenUtau/OpenUtau.csproj | 6 ++ .../DawIntegrationTerminalViewModel.cs | 2 +- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/DawPlugin/src/common.cpp b/DawPlugin/src/common.cpp index 5fa777786..b2f257fb0 100644 --- a/DawPlugin/src/common.cpp +++ b/DawPlugin/src/common.cpp @@ -2,6 +2,7 @@ #include "choc/memory/choc_Base64.h" #include "choc/text/choc_JSON.h" #include "gzip/decompress.hpp" +#include "gzip/compress.hpp" std::vector Utils::gunzip(const char *data, size_t size) { std::vector decompressed; diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 999b32eb9..1e48c34c1 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -179,15 +180,22 @@ String OpenUtauPlugin::getState(const char *rawKey) const { std::string encoded = choc::base64::encodeToString(ustx); return String(encoded.c_str()); } else if (key == "audios") { - choc::value::Value value = choc::value::createObject(""); - for (const auto &[audioHash, audio] : audioBuffers) { - std::string compressed = - gzip::compress((char *)audio.data(), audio.size() * sizeof(float)); - std::string encoded = choc::base64::encodeToString(compressed); - value.setMember(std::to_string(audioHash), encoded); + std::vector raw; + for (const auto &[hash, audio] : audioBuffers) { + uint32_t hashBytes = hash; + raw.insert(raw.end(), (uint8_t *)&hashBytes, + (uint8_t *)&hashBytes + sizeof(uint32_t)); + uint32_t size = audio.size(); + raw.insert(raw.end(), (uint8_t *)&size, + (uint8_t *)&size + sizeof(uint32_t)); + raw.insert(raw.end(), (uint8_t *)audio.data(), + (uint8_t *)audio.data() + size * sizeof(float)); } + auto compressed = gzip::compress((const char *)raw.data(), raw.size()); + auto compressedString = choc::base64::encodeToString(raw); - return String(choc::json::toString(value).c_str()); + std::string encoded = choc::base64::encodeToString(compressedString); + return String(encoded.c_str()); } else if (key == "parts") { choc::value::Value value = choc::value::createEmptyArray(); for (const auto &[trackNo, parts] : parts) { @@ -214,21 +222,24 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { } else if (key == "ustx") { this->ustx = Utils::unBase64ToString(value); } else if (key == "audios") { - choc::value::Value audioValue = choc::json::parse(value); + auto decoded = Utils::unBase64ToVector(value); + auto decompressed = Utils::gunzip((char *)decoded.data(), decoded.size()); + + uint32_t cursor = 0; std::map> audioBuffers; - choc::value::ValueView(audioValue) - .visitObjectMembers( - [&](std::string_view key, const choc::value::ValueView &value) { - auto hash = std::stoul(std::string(key)); - std::string encoded = value.get(); - auto decoded = Utils::unBase64ToVector(encoded); - auto decompressed = - Utils::gunzip((char *)decoded.data(), decoded.size()); - std::vector audio((float *)decompressed.data(), - (float *)decompressed.data() + - decompressed.size() / sizeof(float)); - audioBuffers[hash] = audio; - }); + while (true) { + if (cursor >= decompressed.size()) { + break; + } + uint32_t hash = *(uint32_t *)&decompressed[cursor]; + cursor += sizeof(uint32_t); + uint32_t size = *(uint32_t *)&decompressed[cursor]; + cursor += sizeof(uint32_t); + std::vector audio((float *)&decompressed[cursor], + (float *)&decompressed[cursor + size]); + cursor += size * sizeof(float); + audioBuffers[hash] = audio; + } { auto _lock = std::lock_guard(this->audioBuffersMutex); @@ -378,12 +389,16 @@ void OpenUtauPlugin::onAccept(OpenUtauPlugin *self, socket->async_read_some( asio::buffer(buffer), [&](const asio::error_code &error, size_t len) { - if (error) { - readPromise.set_exception( - std::make_exception_ptr( - error)); - } else { - readPromise.set_value(len); + try { + if (error) { + readPromise.set_exception( + std::make_exception_ptr( + error)); + } else { + readPromise.set_value(len); + } + } catch (std::exception &e) { + // ignore } }); } diff --git a/OpenUtau.Core/DawIntegration/DawManager.cs b/OpenUtau.Core/DawIntegration/DawManager.cs index 77a5602f2..d52bf80d6 100644 --- a/OpenUtau.Core/DawIntegration/DawManager.cs +++ b/OpenUtau.Core/DawIntegration/DawManager.cs @@ -133,12 +133,12 @@ private async Task UpdateAudio() { if (missingAudios.missingAudios.Count > 0) { Log.Information($"DAW requested {missingAudios.missingAudios.Count} missing audios."); - var buffersDict = buffers.ToDictionary(buffer => buffer.hash); + var buffersDict = buffers.GroupBy(buffer => buffer.hash).ToDictionary(group => group.Key, group => group.First()); var audios = new Dictionary(); foreach (var audioHash in missingAudios.missingAudios) { - var buffer = buffersDict[audioHash]; + var buffer = buffersDict[audioHash].byteBuffer; - var compressed = Gzip.Compress(buffer.byteBuffer); + var compressed = Gzip.Compress(buffer); audios[audioHash] = Convert.ToBase64String(compressed); } diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index 24c0784b3..2830eb6c6 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -49,6 +49,12 @@ + + False + + + False + diff --git a/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs b/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs index 3fc038ae5..faf73022c 100644 --- a/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs +++ b/OpenUtau/ViewModels/DawIntegrationTerminalViewModel.cs @@ -6,7 +6,7 @@ namespace OpenUtau.App.ViewModels { public class DawIntegrationTerminalViewModel : ViewModelBase { - [Reactive] public DawServer? SelectedServer { get; set; } = null; + [Reactive] public DawServer? SelectedServer { get; set; } [Reactive] public bool CanConnect { get; set; } = true; public ObservableCollectionExtended ServerList { get; set; } = new ObservableCollectionExtended(); From 3b2471a49ac957dc9b8381cfec4c514d2b34cd5f Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 8 Dec 2024 17:29:15 +0900 Subject: [PATCH 56/72] Fix: Fix state serialize --- DawPlugin/src/plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 1e48c34c1..cf010c6ef 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -192,7 +192,7 @@ String OpenUtauPlugin::getState(const char *rawKey) const { (uint8_t *)audio.data() + size * sizeof(float)); } auto compressed = gzip::compress((const char *)raw.data(), raw.size()); - auto compressedString = choc::base64::encodeToString(raw); + auto compressedString = choc::base64::encodeToString(compressed); std::string encoded = choc::base64::encodeToString(compressedString); return String(encoded.c_str()); From e7d74f2885e8ee7eb5658997a4c3a19dc4b76d4a Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 9 Dec 2024 15:49:41 +0900 Subject: [PATCH 57/72] Fix: Fix serialization --- DawPlugin/deps/dpf | 2 +- DawPlugin/src/DistrhoPluginInfo.h | 2 +- DawPlugin/src/plugin.cpp | 3 +-- OpenUtau/Views/MainWindow.axaml.cs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/DawPlugin/deps/dpf b/DawPlugin/deps/dpf index 00ae9933a..e2b900e22 160000 --- a/DawPlugin/deps/dpf +++ b/DawPlugin/deps/dpf @@ -1 +1 @@ -Subproject commit 00ae9933a791143c966282a547fa87e82fdf3498 +Subproject commit e2b900e22cc49385d505a6f541b5cdf2ba7fbc6b diff --git a/DawPlugin/src/DistrhoPluginInfo.h b/DawPlugin/src/DistrhoPluginInfo.h index 504edefe9..a74aea860 100644 --- a/DawPlugin/src/DistrhoPluginInfo.h +++ b/DawPlugin/src/DistrhoPluginInfo.h @@ -28,7 +28,7 @@ #define DISTRHO_PLUGIN_WANT_FULL_STATE 1 #define DISTRHO_UI_DEFAULT_WIDTH 1024 #define DISTRHO_UI_DEFAULT_HEIGHT 256 -#define DISTRHO_UI_FILE_BROWSER 1 +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USER_RESIZABLE 1 #define DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 1 diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index cf010c6ef..f357fed76 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -192,9 +192,8 @@ String OpenUtauPlugin::getState(const char *rawKey) const { (uint8_t *)audio.data() + size * sizeof(float)); } auto compressed = gzip::compress((const char *)raw.data(), raw.size()); - auto compressedString = choc::base64::encodeToString(compressed); + auto encoded = choc::base64::encodeToString(compressed); - std::string encoded = choc::base64::encodeToString(compressedString); return String(encoded.c_str()); } else if (key == "parts") { choc::value::Value value = choc::value::createEmptyArray(); diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 0ef818628..bb2faf45e 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -1368,7 +1368,7 @@ public void OnNext(UCommand cmd, bool isUndo) { } } } else if (cmd is DawConnectedNotification) { - DocManager.Inst.ExecuteCmd(new ProgressBarNotification(1, + DocManager.Inst.ExecuteCmd(new ProgressBarNotification(100, ThemeManager.GetString("dawintegration.status.connected") )); } else if (cmd is DawDisconnectedNotification) { From 71240ddbc8cb00c0d2234c32869230f039631954 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 9 Dec 2024 16:37:08 +0900 Subject: [PATCH 58/72] Fix: Fix deserialization --- DawPlugin/src/plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index f357fed76..31c5cbb86 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -234,8 +234,8 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { cursor += sizeof(uint32_t); uint32_t size = *(uint32_t *)&decompressed[cursor]; cursor += sizeof(uint32_t); - std::vector audio((float *)&decompressed[cursor], - (float *)&decompressed[cursor + size]); + std::vector audio(size); + memcpy(audio.data(), &decompressed[cursor], size * sizeof(float)); cursor += size * sizeof(float); audioBuffers[hash] = audio; } From 9f1072137ab0c525609107a7ed88a9e9ec711cbe Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 9 Dec 2024 18:03:00 +0900 Subject: [PATCH 59/72] Migrate: Use zstd instead of gzip --- .gitmodules | 9 +-- DawPlugin/.cmake-format.json | 23 ++++++++ DawPlugin/CMakeLists.txt | 66 ++++++++++------------ DawPlugin/deps/gzip-hpp | 1 - DawPlugin/deps/zlib | 1 - DawPlugin/deps/zstd | 1 + DawPlugin/src/common.cpp | 47 ++++++++++++--- DawPlugin/src/common.hpp | 4 +- DawPlugin/src/plugin.cpp | 26 ++++----- DawPlugin/src/ui.cpp | 10 ++-- OpenUtau.Core/DawIntegration/DawManager.cs | 2 +- OpenUtau.Core/OpenUtau.Core.csproj | 1 + OpenUtau.Core/Util/Gzip.cs | 28 --------- OpenUtau.Core/Util/Zstd.cs | 19 +++++++ 14 files changed, 137 insertions(+), 101 deletions(-) create mode 100644 DawPlugin/.cmake-format.json delete mode 160000 DawPlugin/deps/gzip-hpp delete mode 160000 DawPlugin/deps/zlib create mode 160000 DawPlugin/deps/zstd delete mode 100644 OpenUtau.Core/Util/Gzip.cs create mode 100644 OpenUtau.Core/Util/Zstd.cs diff --git a/.gitmodules b/.gitmodules index 47f6c56ee..3aa5b86f9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,9 +16,6 @@ [submodule "DawPlugin/deps/yamc"] path = DawPlugin/deps/yamc url = https://github.com/yohhoy/yamc.git -[submodule "DawPlugin/deps/zlib"] - path = DawPlugin/deps/zlib - url = https://github.com/madler/zlib.git -[submodule "DawPlugin/deps/gzip-hpp"] - path = DawPlugin/deps/gzip-hpp - url = https://github.com/mapbox/gzip-hpp.git +[submodule "DawPlugin/deps/zstd"] + path = DawPlugin/deps/zstd + url = https://github.com/facebook/zstd diff --git a/DawPlugin/.cmake-format.json b/DawPlugin/.cmake-format.json new file mode 100644 index 000000000..6c5e8a0a2 --- /dev/null +++ b/DawPlugin/.cmake-format.json @@ -0,0 +1,23 @@ +{ + "additional_commands": { + "dpf_add_plugin": { + "pargs": 1, + "kwargs": { + "TARGETS": "*", + "UI_TYPE": 1, + "FILES_DSP": "*", + "FILES_UI": "*", + "FILES_COMMON": "*" + } + }, + "corrosion_import_crate": { + "pargs": 0, + "kwargs": { + "MANIFEST_PATH": "*", + "PROFILE": 1, + "CRATES": "*", + "FEATURES": "*" + } + } + } +} diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index ae1272177..518a70621 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -14,38 +14,40 @@ endif() set(NOTO_SANS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/deps/noto_sans") if(NOT EXISTS "${NOTO_SANS_DIR}/otf") - file(DOWNLOAD - "https://github.com/notofonts/noto-cjk/releases/download/Sans2.004/05_NotoSansCJK-SubsetOTF.zip" - "${NOTO_SANS_DIR}/05_NotoSansCJK-SubsetOTF.zip") + file( + DOWNLOAD + "https://github.com/notofonts/noto-cjk/releases/download/Sans2.004/05_NotoSansCJK-SubsetOTF.zip" + "${NOTO_SANS_DIR}/05_NotoSansCJK-SubsetOTF.zip") file(ARCHIVE_EXTRACT INPUT "${NOTO_SANS_DIR}/05_NotoSansCJK-SubsetOTF.zip" DESTINATION "${NOTO_SANS_DIR}/otf") endif() if(NOT EXISTS "${NOTO_SANS_DIR}/noto_sans.hpp") - execute_process(COMMAND xxd -i "${NOTO_SANS_DIR}/otf/SubsetOTF/JP/NotoSansJP-Regular.otf" "${NOTO_SANS_DIR}/noto_sans.raw.hpp") + execute_process( + COMMAND xxd -i "${NOTO_SANS_DIR}/otf/SubsetOTF/JP/NotoSansJP-Regular.otf" + "${NOTO_SANS_DIR}/noto_sans.raw.hpp") file(READ "${NOTO_SANS_DIR}/noto_sans.raw.hpp" NOTO_SANS_HPP) - string(REGEX REPLACE "unsigned char .+\\[\\]" "const unsigned char notoSansJpRegular[]" NOTO_SANS_HPP "${NOTO_SANS_HPP}") - string(REGEX REPLACE "unsigned int .+_len" "const unsigned int notoSansJpRegularLen" NOTO_SANS_HPP "${NOTO_SANS_HPP}") + string( + REGEX + REPLACE "unsigned char .+\\[\\]" "const unsigned char notoSansJpRegular[]" + NOTO_SANS_HPP "${NOTO_SANS_HPP}") + string( + REGEX + REPLACE "unsigned int .+_len" "const unsigned int notoSansJpRegularLen" + NOTO_SANS_HPP "${NOTO_SANS_HPP}") file(WRITE "${NOTO_SANS_DIR}/noto_sans.hpp" "${NOTO_SANS_HPP}") endif() include(./deps/dpf/cmake/DPF-plugin.cmake) project(${PROJECT_NAME}) -# MSVC only: -# - Use UTF-8 code page -# - Make asio use Windows 10 APIs -# - Enable IME +# MSVC only: Use UTF-8 code page, use C++20, Make asio use Windows 10 APIs, Enable IME if(MSVC) add_compile_options("/utf-8") + add_compile_options("/std:c++20") add_definitions(-D_WIN32_WINNT=0x0A00) add_definitions(-DIMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) endif() -# MacOS only: use -Wno-implicit-function-declaration for zlib -if(APPLE) - add_compile_options("-Wno-implicit-function-declaration") -endif() - if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DDEBUG) add_definitions(-DDPF_DEBUG) @@ -58,24 +60,19 @@ add_definitions(-DDGL_NO_SHARED_RESOURCES=1) dpf_add_plugin( ${PROJECT_NAME} - TARGETS - vst3 au - UI_TYPE - opengl - FILES_COMMON - src/common.cpp - FILES_DSP - src/plugin.cpp - FILES_UI - src/ui.cpp - deps/dpf_widgets/opengl/DearImGui.cpp) + TARGETS vst3 au + UI_TYPE opengl + FILES_COMMON src/common.cpp + FILES_DSP src/plugin.cpp + FILES_UI src/ui.cpp deps/dpf_widgets/opengl/DearImGui.cpp) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20) -# Including other libraries before DPF causes "find_library" to fail with infinite -# recursion, so we include it after DPF -set(ZLIB_BUILD_EXAMPLES OFF) -set(RENAME_ZCONF OFF) -add_subdirectory(deps/zlib) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) +# Including other libraries before DPF causes "find_library" to fail with +# infinite recursion, so we include it after DPF +set(ZSTD_BUILD_STATIC ON) +set(ZSTD_BUILD_SHARED OFF) +add_subdirectory(deps/zstd/build/cmake) target_include_directories( ${PROJECT_NAME} @@ -87,8 +84,7 @@ target_include_directories( "deps/dpf_widgets" "deps/dpf_widgets/opengl" "deps/uuid-v4" - "deps/zlib" "deps/yamc/include" - "deps/gzip-hpp/include") + "deps/zstd/lib") -target_link_libraries(${PROJECT_NAME} PRIVATE zlibstatic) +target_link_libraries(${PROJECT_NAME} PRIVATE libzstd_static) diff --git a/DawPlugin/deps/gzip-hpp b/DawPlugin/deps/gzip-hpp deleted file mode 160000 index 7546b35ab..000000000 --- a/DawPlugin/deps/gzip-hpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7546b35aba5917154a0e9ae43f804b57d22bb966 diff --git a/DawPlugin/deps/zlib b/DawPlugin/deps/zlib deleted file mode 160000 index d47682831..000000000 --- a/DawPlugin/deps/zlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d476828316d05d54c6fd6a068b121b30c147b5cd diff --git a/DawPlugin/deps/zstd b/DawPlugin/deps/zstd new file mode 160000 index 000000000..709be6c22 --- /dev/null +++ b/DawPlugin/deps/zstd @@ -0,0 +1 @@ +Subproject commit 709be6c22799e80a93e01b3cf8ac00f93cdebe8d diff --git a/DawPlugin/src/common.cpp b/DawPlugin/src/common.cpp index b2f257fb0..254333432 100644 --- a/DawPlugin/src/common.cpp +++ b/DawPlugin/src/common.cpp @@ -1,15 +1,44 @@ #include "common.hpp" -#include "choc/memory/choc_Base64.h" -#include "choc/text/choc_JSON.h" -#include "gzip/decompress.hpp" -#include "gzip/compress.hpp" +#include +#include +#include +#include -std::vector Utils::gunzip(const char *data, size_t size) { - std::vector decompressed; - gzip::Decompressor decompressor; - decompressor.decompress(decompressed, data, size); - return decompressed; +std::vector Utils::zstd(const uint8_t *data, size_t size, int level) { + size_t est_compress_size = ZSTD_compressBound(size); + + std::vector comp_buffer; + comp_buffer.resize(est_compress_size); + + auto compress_size = + ZSTD_compress(comp_buffer.data(), est_compress_size, data, size, + level == 0 ? ZSTD_maxCLevel() : level); + + comp_buffer.resize(compress_size); + comp_buffer.shrink_to_fit(); + + return comp_buffer; } +std::vector Utils::unzstd(const uint8_t *data, size_t size) { + auto est_decomp_size = ZSTD_getFrameContentSize(data, size); + if (est_decomp_size == ZSTD_CONTENTSIZE_ERROR || est_decomp_size == 0) { + return {}; + } + if (est_decomp_size == ZSTD_CONTENTSIZE_UNKNOWN) { + est_decomp_size = ZSTD_DStreamOutSize(); + } + + std::vector decomp_buffer; + decomp_buffer.resize(est_decomp_size); + + size_t const decomp_size = + ZSTD_decompress(decomp_buffer.data(), est_decomp_size, data, size); + + decomp_buffer.resize(decomp_size); + decomp_buffer.shrink_to_fit(); + return decomp_buffer; +} + std::string Utils::unBase64ToString(const std::string &encoded) { std::vector decoded; choc::base64::decodeToContainer(decoded, encoded); diff --git a/DawPlugin/src/common.hpp b/DawPlugin/src/common.hpp index 9f5a105cc..f5d400740 100644 --- a/DawPlugin/src/common.hpp +++ b/DawPlugin/src/common.hpp @@ -2,11 +2,13 @@ #include "DistrhoPluginInfo.h" #include "choc/containers/choc_Value.h" #include +#include #include #include namespace Utils { -std::vector gunzip(const char *data, size_t size); +std::vector zstd(const uint8_t *data, size_t size, int level); +std::vector unzstd(const uint8_t *data, size_t size); std::string unBase64ToString(const std::string &encoded); std::vector unBase64ToVector(const std::string &encoded); diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 31c5cbb86..4dbb19615 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -1,17 +1,15 @@ #include "plugin.hpp" -#include "DistrhoDetails.hpp" -#include "DistrhoPlugin.hpp" -#include "DistrhoPluginInfo.h" -#include "asio.hpp" -#include "choc/containers/choc_Value.h" -#include "choc/memory/choc_Base64.h" -#include "choc/text/choc_JSON.h" #include "common.hpp" -#include "dpf/distrho/extra/String.hpp" -#include "gzip/compress.hpp" -#include "uuid/v4/uuid.h" +#include +#include +#include +#include #include +#include +#include +#include #include +#include #include #include #include @@ -20,6 +18,7 @@ #include #include #include +#include #include namespace Network { @@ -191,7 +190,7 @@ String OpenUtauPlugin::getState(const char *rawKey) const { raw.insert(raw.end(), (uint8_t *)audio.data(), (uint8_t *)audio.data() + size * sizeof(float)); } - auto compressed = gzip::compress((const char *)raw.data(), raw.size()); + auto compressed = Utils::zstd(raw.data(), raw.size(), 0); auto encoded = choc::base64::encodeToString(compressed); return String(encoded.c_str()); @@ -222,7 +221,7 @@ void OpenUtauPlugin::setState(const char *rawKey, const char *value) { this->ustx = Utils::unBase64ToString(value); } else if (key == "audios") { auto decoded = Utils::unBase64ToVector(value); - auto decompressed = Utils::gunzip((char *)decoded.data(), decoded.size()); + auto decompressed = Utils::unzstd(decoded.data(), decoded.size()); uint32_t cursor = 0; std::map> audioBuffers; @@ -623,8 +622,7 @@ void OpenUtauPlugin::onNotification(const std::string kind, auto hash = std::stoul(std::string(key)); std::string encoded = value.get(); auto decoded = Utils::unBase64ToVector(encoded); - auto decompressed = - Utils::gunzip((char *)decoded.data(), decoded.size()); + auto decompressed = Utils::unzstd(decoded.data(), decoded.size()); std::vector audio((float *)decompressed.data(), (float *)decompressed.data() + decompressed.size() / sizeof(float)); diff --git a/DawPlugin/src/ui.cpp b/DawPlugin/src/ui.cpp index 9c6e80c80..f946a108f 100644 --- a/DawPlugin/src/ui.cpp +++ b/DawPlugin/src/ui.cpp @@ -1,10 +1,10 @@ -#include "DistrhoPluginInfo.h" -#include "DistrhoUI.hpp" #include "common.hpp" -#include "dpf_widgets/generic/ResizeHandle.hpp" -#include "dpf_widgets/opengl/DearImGui/imgui.h" -#include "noto_sans/noto_sans.hpp" #include "plugin.hpp" +#include +#include +#include +#include +#include #include START_NAMESPACE_DISTRHO diff --git a/OpenUtau.Core/DawIntegration/DawManager.cs b/OpenUtau.Core/DawIntegration/DawManager.cs index d52bf80d6..43332106b 100644 --- a/OpenUtau.Core/DawIntegration/DawManager.cs +++ b/OpenUtau.Core/DawIntegration/DawManager.cs @@ -137,8 +137,8 @@ private async Task UpdateAudio() { var audios = new Dictionary(); foreach (var audioHash in missingAudios.missingAudios) { var buffer = buffersDict[audioHash].byteBuffer; + var compressed = Zstd.Compress(buffer); - var compressed = Gzip.Compress(buffer); audios[audioHash] = Convert.ToBase64String(compressed); } diff --git a/OpenUtau.Core/OpenUtau.Core.csproj b/OpenUtau.Core/OpenUtau.Core.csproj index 1df8a039b..4c1fbee34 100644 --- a/OpenUtau.Core/OpenUtau.Core.csproj +++ b/OpenUtau.Core/OpenUtau.Core.csproj @@ -31,6 +31,7 @@ + diff --git a/OpenUtau.Core/Util/Gzip.cs b/OpenUtau.Core/Util/Gzip.cs deleted file mode 100644 index 48ffe2037..000000000 --- a/OpenUtau.Core/Util/Gzip.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OpenUtau.Core.Util { - public static class Gzip - { - public static byte[] Compress(byte[] data) { - using (var compressedStream = new System.IO.MemoryStream()) { - using (var zipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Compress)) { - zipStream.Write(data, 0, data.Length); - } - return compressedStream.ToArray(); - } - } - - public static byte[] Decompress(byte[] data) { - using (var compressedStream = new System.IO.MemoryStream(data)) { - using (var zipStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Decompress)) { - using (var resultStream = new System.IO.MemoryStream()) { - zipStream.CopyTo(resultStream); - return resultStream.ToArray(); - } - } - } - } - } -} diff --git a/OpenUtau.Core/Util/Zstd.cs b/OpenUtau.Core/Util/Zstd.cs new file mode 100644 index 000000000..4122b1f14 --- /dev/null +++ b/OpenUtau.Core/Util/Zstd.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenUtau.Core.Util { + public static class Zstd { + public static byte[] Compress(byte[] data) { + using var compressor = new ZstdSharp.Compressor(); + var compressed = compressor.Wrap(data); + return compressed.ToArray(); + } + + public static byte[] Decompress(byte[] data) { + using var decompressor = new ZstdSharp.Decompressor(); + var decompressed = decompressor.Unwrap(data); + return decompressed.ToArray(); + } + } +} From 1658d441818a0a5cd2fd72acf4dc657bf44e8272 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Mon, 9 Dec 2024 18:11:10 +0900 Subject: [PATCH 60/72] Fix: Fix default level --- DawPlugin/src/common.cpp | 3 +-- DawPlugin/src/common.hpp | 2 +- DawPlugin/src/plugin.cpp | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/DawPlugin/src/common.cpp b/DawPlugin/src/common.cpp index 254333432..39dc97bcc 100644 --- a/DawPlugin/src/common.cpp +++ b/DawPlugin/src/common.cpp @@ -11,8 +11,7 @@ std::vector Utils::zstd(const uint8_t *data, size_t size, int level) { comp_buffer.resize(est_compress_size); auto compress_size = - ZSTD_compress(comp_buffer.data(), est_compress_size, data, size, - level == 0 ? ZSTD_maxCLevel() : level); + ZSTD_compress(comp_buffer.data(), est_compress_size, data, size, level); comp_buffer.resize(compress_size); comp_buffer.shrink_to_fit(); diff --git a/DawPlugin/src/common.hpp b/DawPlugin/src/common.hpp index f5d400740..5f800273a 100644 --- a/DawPlugin/src/common.hpp +++ b/DawPlugin/src/common.hpp @@ -7,7 +7,7 @@ #include namespace Utils { -std::vector zstd(const uint8_t *data, size_t size, int level); +std::vector zstd(const uint8_t *data, size_t size, int level = 3); std::vector unzstd(const uint8_t *data, size_t size); std::string unBase64ToString(const std::string &encoded); std::vector unBase64ToVector(const std::string &encoded); diff --git a/DawPlugin/src/plugin.cpp b/DawPlugin/src/plugin.cpp index 4dbb19615..04b46882b 100644 --- a/DawPlugin/src/plugin.cpp +++ b/DawPlugin/src/plugin.cpp @@ -1,15 +1,16 @@ #include "plugin.hpp" +#include "DistrhoDetails.hpp" +#include "DistrhoPlugin.hpp" +#include "DistrhoPluginInfo.h" +#include "asio.hpp" +#include "choc/containers/choc_Value.h" +#include "choc/memory/choc_Base64.h" +#include "choc/text/choc_JSON.h" #include "common.hpp" -#include -#include -#include -#include +#include "dpf/distrho/extra/String.hpp" +#include "uuid/v4/uuid.h" #include -#include -#include -#include #include -#include #include #include #include @@ -18,7 +19,6 @@ #include #include #include -#include #include namespace Network { @@ -190,7 +190,7 @@ String OpenUtauPlugin::getState(const char *rawKey) const { raw.insert(raw.end(), (uint8_t *)audio.data(), (uint8_t *)audio.data() + size * sizeof(float)); } - auto compressed = Utils::zstd(raw.data(), raw.size(), 0); + auto compressed = Utils::zstd(raw.data(), raw.size()); auto encoded = choc::base64::encodeToString(compressed); return String(encoded.c_str()); From 2c04a1cb2c67dcddcf0bcf4a15c3ef5eaf6f36e2 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Fri, 8 Aug 2025 20:52:51 +0900 Subject: [PATCH 61/72] perf: use MemoryStream Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- OpenUtau.Core/DawIntegration/DawClient.cs | 66 +++++++++++++---------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/OpenUtau.Core/DawIntegration/DawClient.cs b/OpenUtau.Core/DawIntegration/DawClient.cs index bce62ef00..77d5bb028 100644 --- a/OpenUtau.Core/DawIntegration/DawClient.cs +++ b/OpenUtau.Core/DawIntegration/DawClient.cs @@ -38,35 +38,47 @@ private async Task StartReceiver(CancellationToken token) { throw new Exception("stream is null"); } await Task.Run(async () => { - byte[] currentMessageBuffer = new byte[0]; - while (true) { - byte[] buffer = new byte[1024]; - int bytesRead; - try { - bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token); - } catch (SocketException) { - break; - } - currentMessageBuffer = currentMessageBuffer.Concat(buffer.Slice(0, bytesRead)).ToArray(); - while (currentMessageBuffer.Contains((byte)'\n')) { - int index = Array.IndexOf(currentMessageBuffer, (byte)'\n'); - string message = Encoding.UTF8.GetString(currentMessageBuffer.Take(index).ToArray()); - currentMessageBuffer = currentMessageBuffer.Skip(index + 1).ToArray(); - string[] parts = message.Split(' ', 2); - var kind = parts[0]; - var content = parts[1]; - if (handlers.ContainsKey(kind)) { - handlers[kind](content); - } else if (onetimeHandlers.ContainsKey(kind)) { - onetimeHandlers[kind](content); - onetimeHandlers.Remove(kind); - } else { - Log.Warning($"Unhandled message: {kind}"); + using (var currentMessageBuffer = new MemoryStream()) { + while (true) { + byte[] buffer = new byte[1024]; + int bytesRead; + try { + bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token); + } catch (SocketException) { + break; + } + currentMessageBuffer.Write(buffer, 0, bytesRead); + while (true) { + // Search for newline in the buffer + var buf = currentMessageBuffer.GetBuffer(); + int length = (int)currentMessageBuffer.Length; + int index = Array.IndexOf(buf, (byte)'\n', 0, length); + if (index < 0) { + break; + } + // Extract message up to newline + string message = Encoding.UTF8.GetString(buf, 0, index); + // Remove processed message from buffer + int remaining = length - (index + 1); + // Shift remaining bytes to start + Array.Copy(buf, index + 1, buf, 0, remaining); + currentMessageBuffer.SetLength(remaining); + string[] parts = message.Split(' ', 2); + var kind = parts[0]; + var content = parts.Length > 1 ? parts[1] : ""; + if (handlers.ContainsKey(kind)) { + handlers[kind](content); + } else if (onetimeHandlers.ContainsKey(kind)) { + onetimeHandlers[kind](content); + onetimeHandlers.Remove(kind); + } else { + Log.Warning($"Unhandled message: {kind}"); + } } - } - if (cancellationTokenSource != null && cancellationTokenSource.IsCancellationRequested) { - break; + if (cancellationTokenSource != null && cancellationTokenSource.IsCancellationRequested) { + break; + } } } From 8e076b1d68644df36b13cca6cfb1475b191d6dfb Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Fri, 8 Aug 2025 20:44:32 +0900 Subject: [PATCH 62/72] fix: captions -> caption --- OpenUtau/Strings/Strings.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index d268ef78b..9161248e9 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -611,7 +611,7 @@ General Change track color Track Settings - Connect to DAW + Connect to DAW Download Plugin, show UI and connect (temporary text) Download Plugin No plugin host found. From 40b997dc7e9ff300524554628e96aa185b151ed5 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Fri, 8 Aug 2025 20:45:54 +0900 Subject: [PATCH 63/72] fix: use timeSpan --- OpenUtau.Core/Util/Debounce.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau.Core/Util/Debounce.cs b/OpenUtau.Core/Util/Debounce.cs index 622be9894..9c622655c 100644 --- a/OpenUtau.Core/Util/Debounce.cs +++ b/OpenUtau.Core/Util/Debounce.cs @@ -12,7 +12,7 @@ public void Do(TimeSpan timeSpan, Func callback) { cancellation?.Cancel(); cancellation = new CancellationTokenSource(); - Task.Delay(TimeSpan.FromSeconds(5), cancellation.Token) + Task.Delay(timeSpan, cancellation.Token) .ContinueWith(async task => { if (task.IsCompletedSuccessfully) { await callback(); From b4ca75e06e7b3226e9ead9d1135bacac431dcb7e Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Fri, 8 Aug 2025 20:48:28 +0900 Subject: [PATCH 64/72] revert: revert undo changes --- OpenUtau.Core/DocManager.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 01356a68e..533ce7a6d 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -31,15 +31,6 @@ public class DocManager : SingletonBase { public int playPosTick = 0; - readonly Deque undoQueue = new Deque(); - readonly Deque redoQueue = new Deque(); - UCommandGroup? undoGroup = null; - UCommandGroup? savedPoint = null; - UCommandGroup? autosavedPoint = null; - - private readonly object lockObj = new object(); - private readonly List subscribers = new List(); - public TaskScheduler MainScheduler => mainScheduler; public Action PostOnUIThread { get; set; } public Plugin[] Plugins { get; private set; } @@ -118,6 +109,12 @@ public void SearchAllPlugins() { } #region Command Queue + + readonly Deque undoQueue = new Deque(); + readonly Deque redoQueue = new Deque(); + UCommandGroup? undoGroup = null; + UCommandGroup? savedPoint = null; + UCommandGroup? autosavedPoint = null; public bool Recovered { get; set; } = false; // Flag to not overwrite backup file public bool ChangesSaved { @@ -184,7 +181,6 @@ public void AutoSave() { } } - public void ExecuteCmd(UCommand cmd) { if (mainThread != Thread.CurrentThread) { if (!(cmd is ProgressBarNotification)) { @@ -333,6 +329,9 @@ public void Redo() { # region Command Subscribers + private readonly object lockObj = new object(); + private readonly List subscribers = new List(); + public void AddSubscriber(ICmdSubscriber sub) { lock (lockObj) { if (!subscribers.Contains(sub)) { From e938f4df385f874f4a8305b2042aa6a6eee20ca8 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Fri, 8 Aug 2025 21:07:13 +0900 Subject: [PATCH 65/72] fix: fix USTx --- OpenUtau.Core/Format/USTx.cs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/OpenUtau.Core/Format/USTx.cs b/OpenUtau.Core/Format/USTx.cs index 450efd29e..4716a350e 100644 --- a/OpenUtau.Core/Format/USTx.cs +++ b/OpenUtau.Core/Format/USTx.cs @@ -132,10 +132,21 @@ public static void AutoSave(string filePath, UProject project) { public static UProject Load(string filePath) { var text = File.ReadAllText(filePath, Encoding.UTF8); - var project = LoadText(text); - project.FilePath = filePath; + try { + var project = LoadText(text); + project.FilePath = filePath; - return project; + return project; + } catch (FileFormatException ex) { + if (ex.Message == "Project file is newer than software.") { + // Keep the original behavior + throw new MessageCustomizableException($"Project file is newer than software: {filePath}", $":\n{filePath}", ex); + } else { + throw ex; + } + } catch (Exception ex) { + throw ex; + } } public static UProject LoadText(string text) { @@ -145,7 +156,7 @@ public static UProject LoadText(string text) { project.AfterLoad(); project.ValidateFull(); if (project.ustxVersion > kUstxVersion) { - throw new MessageCustomizableException($"Project file is newer than software: {filePath}", $":\n{filePath}", new FileFormatException("Project file is newer than software.")); + throw new FileFormatException($"Project file is newer than software."); } if (project.ustxVersion < kUstxVersion) { Log.Information($"Upgrading project from {project.ustxVersion} to {kUstxVersion}"); @@ -178,10 +189,10 @@ public static UProject LoadText(string text) { project.ValidateFull(); } if (project.ustxVersion < new Version(0, 6)) { - #pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0612 // Type or member is obsolete project.timeSignatures = new List { new UTimeSignature(0, project.beatPerBar, project.beatUnit) }; project.tempos = new List { new UTempo(0, project.bpm) }; - #pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0612 // Type or member is obsolete project.ValidateFull(); } if (project.ustxVersion < new Version(0, 7)) { From 27e5a309ed3f6da8c66fda754418da2cf2492501 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Fri, 8 Aug 2025 21:21:17 +0900 Subject: [PATCH 66/72] fix: disable daw-plugin --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65a4e5953..24f314e1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,10 +33,10 @@ jobs: - { name: win-x64, rid: win-x64, arch: x64, os: win, runs-on: windows-latest, daw-plugin: true } - { name: win-x86, rid: win-x86, arch: x86, os: win, runs-on: windows-latest, daw-plugin: false } - { name: win-arm64, rid: win-arm64, arch: arm64, os: win, runs-on: windows-latest, daw-plugin: true } - - { name: osx-x64, rid: osx-x64, arch: x64, os: osx, runs-on: macos-13, daw-plugin: true } - - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13, daw-plugin: true } - - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-24.04, daw-plugin: true } - - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-24.04, daw-plugin: true } + - { name: osx-x64, rid: osx-x64, arch: x64, os: osx, runs-on: macos-13, daw-plugin: false } + - { name: osx-arm64, rid: osx-arm64, arch: arm64, os: osx, runs-on: macos-13, daw-plugin: false } + - { name: linux-x64, rid: linux-x64, arch: x64, os: linux, runs-on: ubuntu-24.04, daw-plugin: false } + - { name: linux-arm64, rid: linux-arm64, arch: arm64, os: linux, runs-on: ubuntu-24.04, daw-plugin: false } steps: # Setup From a5bd80f1b0d716c5b3597e3523709e3434e7b5b7 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 10 Aug 2025 08:59:25 +0900 Subject: [PATCH 67/72] fix: run Before/AfterSave --- DawPlugin/CMakeLists.txt | 2 +- DawPlugin/IPC.md | 63 +++++++++++++++++++++ DawPlugin/src/plugin.hpp | 3 +- OpenUtau.Core/DawIntegration/DawClient.cs | 2 +- OpenUtau.Core/DawIntegration/DawManager.cs | 26 +++++---- OpenUtau.Core/DawIntegration/DawMessages.cs | 4 +- OpenUtau.Core/Format/USTx.cs | 3 + 7 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 DawPlugin/IPC.md diff --git a/DawPlugin/CMakeLists.txt b/DawPlugin/CMakeLists.txt index 518a70621..c7a71a41f 100644 --- a/DawPlugin/CMakeLists.txt +++ b/DawPlugin/CMakeLists.txt @@ -7,7 +7,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") elseif(CMAKE_BUILD_TYPE STREQUAL "Release") set(PROJECT_NAME openutau_daw_plugin) elseif(NOT CMAKE_BUILD_TYPE) - message(FATAL_ERROR "Build type not set") + message(FATAL_ERROR "Build type not set. Please set CMAKE_BUILD_TYPE to either Debug or Release.") else() message(FATAL_ERROR "Unknown build type: ${CMAKE_BUILD_TYPE}") endif() diff --git a/DawPlugin/IPC.md b/DawPlugin/IPC.md new file mode 100644 index 000000000..4c8ab0bc5 --- /dev/null +++ b/DawPlugin/IPC.md @@ -0,0 +1,63 @@ +# Inter-Process Communication (IPC) between OpenUtau and DAW Plugin + +This document outlines the inter-process communication (IPC) mechanism used for integration between the OpenUtau main application and its DAW plugin. + +## Overview + +The communication between OpenUtau and the DAW plugin is established using **TCP sockets** over the loopback interface (`127.0.0.1`, localhost). All messages exchanged are serialized and deserialized using **JSON**. + +## Key Components + +The IPC mechanism is primarily managed by classes within the `OpenUtau.Core.DawIntegration` namespace. + +## IPC Workflow Summary + +1. **DAW Plugin Initialization**: A DAW plugin, upon starting, acts as a TCP server and typically creates a `.json` file in the designated temporary directory (`OpenUtau/PluginServers`) containing its listening port and name. +2. **OpenUtau Server Discovery**: OpenUtau's `DawServerFinder` periodically scans this temporary directory to discover available DAW plugin servers. +3. **Connection Establishment**: When a DAW plugin is selected or detected, OpenUtau (via `DawManager` and `DawClient`) initiates a TCP connection to the DAW plugin's exposed port on `127.0.0.1`. +4. **Initial Handshake**: OpenUtau sends an `init` request, and loads the current USTX project data from the DAW plugin. +5. **Continuous Synchronization**: + - OpenUtau continuously monitors changes in its project (USTX, tracks, parts). + - Using debounced notifications (`updateUstx`, `updateTracks`), OpenUtau pushes relevant updates to the DAW plugin. + - For audio synchronization, OpenUtau sends `updatePartLayout` with hashes of audio parts. The DAW plugin responds with hashes of any missing audio. + - OpenUtau then renders and sends the missing audio data via `UpdateAudioNotification` (compressed and encoded). +6. **DAW to OpenUtau Communication**: The DAW plugin can send notifications (`ping`) or requests back to OpenUtau, which are handled by registered listeners in `DawClient`. + +## OpenUtau to DAW Plugin Messages + +There are 4 main types of messages sent from OpenUtau to the DAW plugin: + +- **OpenUtau to DAW Requests**: Sent by OpenUtau to the DAW plugin, expecting a response. +- **OpenUtau to DAW Notifications**: Sent by OpenUtau to the DAW plugin, not expecting a response. +- **DAW Plugin to OpenUtau Requests**: Sent by the DAW plugin to OpenUtau, expecting a response. Currently not used. +- **DAW Plugin to OpenUtau Notifications**: Sent by the DAW plugin to OpenUtau, not expecting a response. + +The following messages are sent from OpenUtau to the DAW plugin: + +- `init` (Request): + - Initializes the connection between OpenUtau and the DAW plugin. Also retrives the current USTX project data saved in plugin. + - **Request**: None + - **Response**: `{ "ustx": }` +- `updateUstx` (Notification): + - Notifies the DAW plugin about changes to the entire USTX project in OpenUtau. + - **Request**: `{ "ustx": }` + - **Response**: None +- `updateTracks` (Notification): + - Notifies the DAW plugin about changes to track properties (e.g., name, volume, pan) in OpenUtau. + - **Request**: `{ "tracks": [ { "name": , "volume": , "pan": } ] }` + - **Response**: None +- `updatePartLayout` (Request): + - Synchronizes part layout information (e.g., track number, start/end times, audio hashes) from OpenUtau to the DAW. + - **Request**: `{ "parts": [ { "trackNo": , "startMs": , "endMs": , "audioHash":