From eabe24dd1133cfb8cbffd734f454b84d731e5c85 Mon Sep 17 00:00:00 2001 From: hugwalk <21334079+hugwalk@users.noreply.github.com> Date: Thu, 11 Aug 2022 19:47:53 +0800 Subject: [PATCH 001/321] Add Chinese (Traditional) translation --- OpenUtau/App.axaml | 1 + OpenUtau/Strings/Strings.zh-TW.axaml | 311 +++++++++++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 OpenUtau/Strings/Strings.zh-TW.axaml diff --git a/OpenUtau/App.axaml b/OpenUtau/App.axaml index 65c6823ad..9dc25abb9 100644 --- a/OpenUtau/App.axaml +++ b/OpenUtau/App.axaml @@ -21,6 +21,7 @@ + diff --git a/OpenUtau/Strings/Strings.zh-TW.axaml b/OpenUtau/Strings/Strings.zh-TW.axaml new file mode 100644 index 000000000..068bdd51d --- /dev/null +++ b/OpenUtau/Strings/Strings.zh-TW.axaml @@ -0,0 +1,311 @@ + + + 緩入 + 緩入並緩出 + 緩出 + 線性 + 新增控制點 + 刪除控制點 + 貼齊前一個音符 + + 清除 + 複製 + 反轉記錄檔順序 + + 關於 OpenUTAU + + OpenUtau 致力於為 UTAU 社群建立開源編輯環境,提供現代化使用體驗與智慧型拆音功能。歡迎瀏覽我們的 GitHub。 + + 結束 OpenUtau + 專案包含未儲存的變更。是否要儲存? + 匯出 + 請先儲存專案 + 取消 + + + 確定 + 未套用引擎 + 未套用引擎! 請將引擎 EXE 或 DLL 檔案置入 Resamplers 資料夾中,並在偏好設定選擇欲使用的引擎! + 拍號 + + 錯誤 + + 縮寫 + 套用 + 表現 + 預設 + 引擎 Flag + 是引擎 Flag + 最大值 + 最小值 + 名稱 + 選項 + 請使用半形逗號分隔 + 類型 + 曲線 + 數值 + 選項 + + 套用 + 取消 + 編輯歌詞 + 即時預覽 + 個音符 + 重設 + 請先選取音符! + 分隔符號 + + 編輯 + 複製 + 剪下 + 刪除 + 貼上 + 重做 + 全選 + 復原 + 檔案 + 結束 + 匯出 UST 專案 + 匯出 UST 檔案至... + 匯出 WAV 音訊 + 匯出 WAV 音訊至... + 匯入音訊... + 匯入 MIDI... + 匯入音軌... + 新增 + 從範本新增 + 開啟舊檔 + 開啟為 + 開啟匯出位置 + 開啟專案位置 + 最近開啟 + 儲存檔案 + 另存新檔 + 另存為範本 + 說明 + 關於 OpenUtau + OpenUtau Wiki + 工具 + 清除快取 + 除錯視窗 + 版面配置 + 橫向 1:1 + 橫向 1:2 + 橫向 1:3 + 縱向 1:1 + 縱向 1:2 + 縱向 1:3 + 偏好設定... + 表現... + 安裝歌手... + 安装歌手 (進階模式)... + 歌手... + + 歌詞 + 預設歌詞 + 滑音 + 長度 + 開始 + 預設集 + 命名新預設集 + 移除 + 移除先前套用的預設集。 + 儲存 + 儲存目前設定為新預設集。 + 重設所有設定 + 重設所有設定為預設值。 + 警告: 此選項將移除自訂預設集。 + 顫音 + 最短長度 + 長音符自動顫音 + 深度 + 淡入 + 長度 + 淡出 + 週期 + 相位 + + 別名 + 色彩 + 子音 + 後裁切 + 檔案 + 前裁切 + 重疊 + 音標 + 前綴 + 先行發聲 + 設定 + 後綴 + + 歌詞 + 使用 "+" 替換 "-" + 編輯歌詞 + 平假名轉羅馬拼音 + 日文 VCV 轉 CV + 移除字母後綴 + 移除音調後綴 + 羅馬拼音轉平假名 + 音符預設設定 + 音符 + 新增音符尾端 "-" + 新增音符尾端 "R" + 清除顫音 + 載入已算繪的音高曲線 + 量化至 1/128 + 量化至 1/64 + 重設所有表現 + 重設音標時間 + 重設音高曲線 + 重設顫音 + 段落 + 舊版插件 (實驗性) + 重新命名段落 + 顯示算繪音高曲線 (R) + 顯示音標 (O) + 顯示手動音高曲線 (I) + 啟用自動貼齊 (P) + 顯示使用提示 (T) + 啟用音調提示 (Y) + 顯示顫音 (U) + 顯示波形 (W) + 音高繪製工具 (4) + 橡皮擦工具 (3) + 剪刀工具 (5) + 畫筆工具 (2) +點擊左鍵以繪製 +點擊右鍵以刪除 + 選取工具 (1) +按住 Ctrl 以新增選取範圍 + + 進階 + 測試版 + 引擎紀錄 + 儲存引擎輸出紀錄至記錄檔。此選項可能影響介面與算繪效能。 + 穩定版 + 外觀 + 語言 + 在鋼琴卷軸上顯示角色背景圖 + 主題 + 深色 + 淺色 + 偏好設定 + 注意: 請重新啟動 OpenUtau 以套用變更 + 關閉 + 開啟 + 路徑 + 額外歌手路徑 + 安裝至額外歌手路徑 + 重設 + 選擇 + 播放 + 自動捲動 + 自動捲動模式 + 捲動整個頁面 + 固定游標位置 + 音訊後端 + 自動 + PortAudio + 開始捲動位置 + 播放裝置 + 暫停時 + 不進行動作 + 移動游標至起始播放位置 + 測試 + 算繪 + 相位補償 + 預先算繪 + 預先算繪執行緒 + 引擎 + +警告: Moresampler 尚未完整支援,可能導致使用速度緩慢並提高 CPU 使用率。若欲繼續,請遵循以下步驟: +1. 將 moreconfig.txt 中的 "resampler-compatibility" 設定值更改為 "on"。 +2. 將 moreconfig.txt 中的 "auto-update-llsm-mrq" 設定值更改為 "off"。 +3. 首次播放時,請稍等 Moresampler 產生 LLSM 檔案。 + + + 注意: 欲使用外部引擎,請將引擎 EXE 或 DLL 檔案置入 Resamplers 資料夾中,並在偏好設定選擇欲使用的引擎。 + + + 已儲存專案。{0} + 等待算繪 + + 歌手 + 位置 + 左移 + 右移 + 選取下一項 + 選取上一項 + 顯示全部 + 放大 + 縮小 + 重新整理 + 設定預設拆音器 + 設定編碼 + 設定背景圖 + 取消 + 清除 + 色彩 + 新增色彩 + 移除色彩 + 重新命名 + 編輯子聲庫 + 重設 + 儲存 + 全選 + 設定 + 音調 + 音調範圍 + + 覆寫別名 + +使用左鍵描繪: 設定表現 +使用右鍵描繪: 重設表現 + + 此算繪器不支援表現 + Tab: 下一個音符 + Shift+Tab: 上一個音符 + +選取工具 + 框選: 選取音符 + Ctrl + 框選: 選取更多音符 + 上下鍵: 移調選取音符(一個半音) + Ctrl + 上下鍵: 移調選取音符(一個八度) +畫筆工具 + 使用左鍵描繪: 新增音符 + 點擊右鍵: 移除音符 + 使用右鍵描繪: 移除多個音符 +一般 + T: 顯示提示 + 拖曳音符: 移動音符 + 拖曳音符尾端: 調整音符長度 + Alt + 拖曳音符尾端: 調整相連音符長度 + 空白鍵: 播放 + + ◀ 捲動以橫向縮放 ▶ + 捲動以縱向縮放 ▶ + + 更多 + 下移 + 上移 + 移除 + 選取算繪器 + 選取歌手 + + Segoe UI,San Francisco,Helvetica Neue + Microsoft YaHei,Simsun,苹方-简,宋体-简 + + 檢查更新 + 開放歌聲合成平台 + GitHub + 版本 v{0} 已可更新! + 檢查更新... + 已為最新版本 + 無法檢查更新 + 更新 + + 警告 + 您的 OpenUtau 主路徑 「{0}」 包含非 ASCII 字元。部分 EXE 引擎可能無法正常運作。 + From 1588ab3a1d714faab2fbf3246a5ebb485b83d8e8 Mon Sep 17 00:00:00 2001 From: Lotte V <61566058+lottev1991@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:20:35 +0200 Subject: [PATCH 002/321] Burst consonants bug fix --- OpenUtau.Plugin.Builtin/ENDeltaVer1Phonemizer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/ENDeltaVer1Phonemizer.cs b/OpenUtau.Plugin.Builtin/ENDeltaVer1Phonemizer.cs index 9c007eb25..0102270a9 100644 --- a/OpenUtau.Plugin.Builtin/ENDeltaVer1Phonemizer.cs +++ b/OpenUtau.Plugin.Builtin/ENDeltaVer1Phonemizer.cs @@ -237,12 +237,14 @@ protected override List ProcessSyllable(Syllable syllable) } else { // like [V C1] [C1 C2] [C2 ..] or like [V C1] [C1 -] [C3 ..] TryAddPhoneme(phonemes, syllable.tone, cc1); - if (burstConsonants.Contains(cc[i])) { - TryAddPhoneme(phonemes, syllable.tone, cc[i], $"{cc[i]} -"); + if (burstConsonants.Contains(cc.Last())) + { + TryAddPhoneme(phonemes, syllable.tone, cc.Last(), $"{cc.Last()} -"); } - if (cc[i] == cc.Last() && !affricates.Contains(cc[i])) { - phonemes.Remove(cc[i]); - phonemes.Remove($"{cc[i]} -"); + if (!affricates.Contains(cc.Last())) + { + phonemes.Remove(cc.Last()); + phonemes.Remove($"{cc.Last()} -"); } } } From 71167c3121f016ed9f43db54440c4630008708d6 Mon Sep 17 00:00:00 2001 From: Lotte V <61566058+lottev1991@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:20:38 +0200 Subject: [PATCH 003/321] Burst consonants bug fix --- OpenUtau.Plugin.Builtin/ENDeltaVer2Phonemizer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenUtau.Plugin.Builtin/ENDeltaVer2Phonemizer.cs b/OpenUtau.Plugin.Builtin/ENDeltaVer2Phonemizer.cs index 1a989f9a9..40899aa61 100644 --- a/OpenUtau.Plugin.Builtin/ENDeltaVer2Phonemizer.cs +++ b/OpenUtau.Plugin.Builtin/ENDeltaVer2Phonemizer.cs @@ -235,12 +235,14 @@ protected override List ProcessSyllable(Syllable syllable) } else { // like [V C1] [C1 C2] [C2 ..] or like [V C1] [C1 -] [C3 ..] TryAddPhoneme(phonemes, syllable.tone, cc1); - if (burstConsonants.Contains(cc[i])) { - TryAddPhoneme(phonemes, syllable.tone, cc[i], $"{cc[i]} -"); + if (burstConsonants.Contains(cc.Last())) + { + TryAddPhoneme(phonemes, syllable.tone, cc.Last(), $"{cc.Last()} -"); } - if (cc[i] == cc.Last() && !affricates.Contains(cc[i])) { - phonemes.Remove(cc[i]); - phonemes.Remove($"{cc[i]} -"); + if (!affricates.Contains(cc.Last())) + { + phonemes.Remove(cc.Last()); + phonemes.Remove($"{cc.Last()} -"); } } } From 554566e4742b8ea9acca8f9c0e2638355c974aaf Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Wed, 17 Aug 2022 00:52:17 -0700 Subject: [PATCH 004/321] oto editing except saving --- OpenUtau.Core/Commands/Notifications.cs | 5 +++ OpenUtau.Core/DocManager.cs | 5 ++- OpenUtau.Core/SingerManager.cs | 2 +- OpenUtau.Core/Ustx/USinger.cs | 55 ++++++++++++++++++++--- OpenUtau/Strings/Strings.axaml | 5 +++ OpenUtau/ViewModels/SingersViewModel.cs | 58 +++++++++++++++++-------- OpenUtau/Views/MainWindow.axaml.cs | 18 +++++--- OpenUtau/Views/SingersDialog.axaml | 36 +++++++++++++-- OpenUtau/Views/SingersDialog.axaml.cs | 4 +- 9 files changed, 151 insertions(+), 37 deletions(-) diff --git a/OpenUtau.Core/Commands/Notifications.cs b/OpenUtau.Core/Commands/Notifications.cs index 290fa79ec..c1ff748cf 100644 --- a/OpenUtau.Core/Commands/Notifications.cs +++ b/OpenUtau.Core/Commands/Notifications.cs @@ -129,6 +129,11 @@ public SingersRefreshedNotification() { } public override string ToString() => "Singers refreshed."; } + public class OtoChangedNotification : UNotification { + public OtoChangedNotification() { } + public override string ToString() => "Oto changed."; + } + public class WillRemoveTrackNotification : UNotification { public int TrackNo; public WillRemoveTrackNotification(int trackNo) { diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 76678c946..76e2519d4 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -187,11 +187,14 @@ public void ExecuteCmd(UCommand cmd) { SingerManager.Inst.SearchAllSingers(); } else if (cmd is ValidateProjectNotification) { Project.ValidateFull(); - } else if (cmd is SingersRefreshedNotification) { + } else if (cmd is SingersRefreshedNotification || cmd is OtoChangedNotification) { foreach (var track in Project.tracks) { track.OnSingerRefreshed(); } Project.ValidateFull(); + if (cmd is OtoChangedNotification) { + ExecuteCmd(new PreRenderNotification()); + } } Publish(cmd); if (!cmd.Silent) { diff --git a/OpenUtau.Core/SingerManager.cs b/OpenUtau.Core/SingerManager.cs index c4905616c..7f3374c46 100644 --- a/OpenUtau.Core/SingerManager.cs +++ b/OpenUtau.Core/SingerManager.cs @@ -100,7 +100,7 @@ private void Refresh() { DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"Reloaded {singer.Id}")); } if (singers.Count > 0) { - DocManager.Inst.ExecuteCmd(new SingersRefreshedNotification()); + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } } } diff --git a/OpenUtau.Core/Ustx/USinger.cs b/OpenUtau.Core/Ustx/USinger.cs index 2fd8d3018..5c41b1059 100644 --- a/OpenUtau.Core/Ustx/USinger.cs +++ b/OpenUtau.Core/Ustx/USinger.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Text; using OpenUtau.Classic; namespace OpenUtau.Core.Ustx { - public class UOto { + public class UOto : INotifyPropertyChanged { public string Alias { get; set; } public string Phonetic { get; set; } public string Set { get; set; } @@ -15,13 +16,51 @@ public class UOto { public SortedSet ToneSet { get; set; } public string File { get; set; } public string DisplayFile { get; set; } - public double Offset { get; set; } - public double Consonant { get; set; } - public double Cutoff { get; set; } - public double Preutter { get; set; } - public double Overlap { get; set; } + public double Offset { + get => offset; + set { + offset = Math.Round(value, 3); + NotifyPropertyChanged(nameof(Offset)); + } + } + public double Consonant { + get => consonant; + set { + consonant = Math.Max(0, Math.Round(value, 3)); + NotifyPropertyChanged(nameof(Consonant)); + } + } + public double Cutoff { + get => cutoff; + set { + cutoff = Math.Round(value, 3); + NotifyPropertyChanged(nameof(Cutoff)); + } + } + public double Preutter { + get => preutter; + set { + preutter = Math.Max(0, Math.Round(value, 3)); + NotifyPropertyChanged(nameof(Preutter)); + } + } + public double Overlap { + get => overlap; + set { + overlap = Math.Round(value, 3); + NotifyPropertyChanged(nameof(Overlap)); + } + } public List SearchTerms { private set; get; } + public event PropertyChangedEventHandler PropertyChanged; + + private double offset; + private double consonant; + private double cutoff; + private double preutter; + private double overlap; + public UOto() { } public UOto(Oto oto, UOtoSet set, USubbank subbank) { @@ -43,6 +82,10 @@ public UOto(Oto oto, UOtoSet set, USubbank subbank) { SearchTerms = new List(); } + private void NotifyPropertyChanged(string propertyName = "") { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + public override string ToString() => Alias; } diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index b070b1558..45ca3cdbc 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -139,6 +139,11 @@ Warning: this option removes custom presets. Preutter Set Suffix + Set Consonant + Set Cutoff + Set Offset + Set Overlap + Set Preutter Lyrics Replace "-" with "+" diff --git a/OpenUtau/ViewModels/SingersViewModel.cs b/OpenUtau/ViewModels/SingersViewModel.cs index 573ceabfc..0c9c33d04 100644 --- a/OpenUtau/ViewModels/SingersViewModel.cs +++ b/OpenUtau/ViewModels/SingersViewModel.cs @@ -20,8 +20,9 @@ public class SingersViewModel : ViewModelBase { [Reactive] public Bitmap? Avatar { get; set; } [Reactive] public string? Info { get; set; } [Reactive] public ObservableCollectionExtended Subbanks { get; set; } - [Reactive] public List? Otos { get; set; } + [Reactive] public ObservableCollectionExtended Otos { get; set; } [Reactive] public UOto? SelectedOto { get; set; } + [Reactive] public int SelectedIndex { get; set; } [Reactive] public List SetEncodingMenuItems { get; set; } [Reactive] public List SetDefaultPhonemizerMenuItems { get; set; } @@ -30,6 +31,7 @@ public class SingersViewModel : ViewModelBase { public SingersViewModel() { Subbanks = new ObservableCollectionExtended(); + Otos = new ObservableCollectionExtended(); #if DEBUG Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); #endif @@ -41,9 +43,11 @@ public SingersViewModel() { .Subscribe(singer => { singer.Reload(); Avatar = LoadAvatar(singer); - Otos = singer.Otos.Values.ToList(); + Otos.Clear(); + Otos.AddRange(singer.Otos.Values); Info = $"Author: {singer.Author}\nVoice: {singer.Voice}\nWeb: {singer.Web}\nVersion: {singer.Version}\n{singer.OtherInfo}\n\n{string.Join("\n", singer.Errors)}"; LoadSubbanks(); + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); }); @@ -190,42 +194,58 @@ public void RefreshSinger() { Singer?.Reload(); LoadSubbanks(); DocManager.Inst.ExecuteCmd(new SingersRefreshedNotification()); + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } public void SetOffset(double value) { - if (SelectedOto != null) { - var delta = value - SelectedOto.Offset; - SelectedOto.Offset += delta; - SelectedOto.Consonant -= delta; - SelectedOto.Preutter -= delta; - SelectedOto.Overlap -= delta; - } - this.RaisePropertyChanged(nameof(SelectedOto)); + if (SelectedOto == null) { + return; + } + var delta = value - SelectedOto.Offset; + SelectedOto.Offset += delta; + SelectedOto.Consonant -= delta; + SelectedOto.Preutter -= delta; + SelectedOto.Overlap -= delta; + if (SelectedOto.Cutoff < 0) { + SelectedOto.Cutoff += delta; + } + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } public void SetOverlap(double value) { - if (SelectedOto != null) { - SelectedOto.Overlap = value - SelectedOto.Offset; + if (SelectedOto == null) { + return; } + SelectedOto.Overlap = value - SelectedOto.Offset; + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } public void SetPreutter(double value) { - if (SelectedOto != null) { - var delta = value - SelectedOto.Offset - SelectedOto.Preutter; - SelectedOto.Preutter += delta; + if (SelectedOto == null) { + return; } + var delta = value - SelectedOto.Offset - SelectedOto.Preutter; + SelectedOto.Preutter += delta; + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } public void SetFixed(double value) { - if (SelectedOto != null) { - SelectedOto.Consonant = value - SelectedOto.Offset; + if (SelectedOto == null) { + return; } + SelectedOto.Consonant = value - SelectedOto.Offset; + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } public void SetCutoff(double value) { - if (SelectedOto != null) { - SelectedOto.Cutoff = -(value - SelectedOto.Offset); + if (SelectedOto == null) { + return; + } + if (value < SelectedOto.Offset) { + return; } + SelectedOto.Cutoff = -(value - SelectedOto.Offset); + DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } } } diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index df692a3ed..b85e50ea4 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -372,10 +372,18 @@ void OnMenuExpressionss(object sender, RoutedEventArgs args) { } void OnMenuSingers(object sender, RoutedEventArgs args) { - var dialog = new SingersDialog() { - DataContext = new SingersViewModel(), - }; - dialog.ShowDialog(this); + var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; + if (lifetime == null) { + return; + } + var dialog = lifetime.Windows.FirstOrDefault(w => w is SingersDialog); + if (dialog == null) { + dialog = new SingersDialog() { + DataContext = new SingersViewModel(), + }; + dialog.Show(); + } + dialog.Activate(); if (dialog.Position.Y < 0) { dialog.Position = dialog.Position.WithY(0); } @@ -538,7 +546,7 @@ async void OnDrop(object? sender, DragEventArgs args) { ThemeManager.GetString("errors.caption"), MessageBox.MessageBoxButtons.Ok); } - }else if(ext==".mid") { + } else if (ext == ".mid") { try { viewModel.ImportMidi(file); } catch (Exception e) { diff --git a/OpenUtau/Views/SingersDialog.axaml b/OpenUtau/Views/SingersDialog.axaml index bfcd9b39a..179a2fead 100644 --- a/OpenUtau/Views/SingersDialog.axaml +++ b/OpenUtau/Views/SingersDialog.axaml @@ -5,10 +5,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:OpenUtau.App.ViewModels" xmlns:ScottPlot="clr-namespace:ScottPlot.Avalonia;assembly=ScottPlot.Avalonia" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="640" x:Class="OpenUtau.App.Views.SingersDialog" Icon="/Assets/open-utau.ico" - Title="{DynamicResource singers.caption}" Width="800" MinWidth="800" MinHeight="600" + Title="{DynamicResource singers.caption}" Width="800" MinWidth="800" MinHeight="640" WindowStartupLocation="CenterScreen" ExtendClientAreaToDecorationsHint="False" KeyDown="OnKeyDown"> @@ -23,6 +23,8 @@ + + @@ -83,7 +85,8 @@ Background="{DynamicResource SystemControlBackgroundAltHighBrush}"/> @@ -139,6 +142,33 @@ + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + diff --git a/OpenUtau/Views/SingersDialog.axaml.cs b/OpenUtau/Views/SingersDialog.axaml.cs index 25cee4eef..1999939b7 100644 --- a/OpenUtau/Views/SingersDialog.axaml.cs +++ b/OpenUtau/Views/SingersDialog.axaml.cs @@ -133,8 +133,8 @@ void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { DrawOto(oto, true); } - void DrawOto(Core.Ustx.UOto oto, bool fit = false) { - if (otoPlot == null) { + void DrawOto(Core.Ustx.UOto? oto, bool fit = false) { + if (otoPlot == null || oto == null) { return; } var limites = otoPlot.Plot.GetAxisLimits(); From ee03ae2e6856f047396f13aabaa5a72161aeec09 Mon Sep 17 00:00:00 2001 From: oxygen-dioxide <54425948+oxygen-dioxide@users.noreply.github.com> Date: Wed, 17 Aug 2022 23:00:19 +0800 Subject: [PATCH 005/321] slur support --- .../DiffSinger/DiffSingerRenderer.cs | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs index 4e08e60b4..6473fb673 100644 --- a/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs +++ b/OpenUtau.Core/DiffSinger/DiffSingerRenderer.cs @@ -12,6 +12,7 @@ using OpenUtau.Core.SignalChain; using OpenUtau.Core.Ustx; using Serilog; +using static OpenUtau.Api.Phonemizer; namespace OpenUtau.Core.DiffSinger { public class DiffSingerRenderer : IRenderer { @@ -153,26 +154,57 @@ private ulong PreEffectsHash(RenderPhrase phrase) { } } + static IEnumerable GetSlurNote(RenderNote[] ounotes) { + foreach (var ounote in ounotes) { + if (ounote.lyric[0] == '+') { + yield return ounote; + } + } + } //将OpenUTAU音素转化为diffsinger音符 //#TODO:连音符 static DiffSingerNote[] PhraseToDiffSingerNotes(RenderPhrase phrase) { + var slurNoteIterator = GetSlurNote(phrase.notes).GetEnumerator(); + slurNoteIterator.MoveNext(); + bool slurNoteContinue = true;//后面是否还有连音符 var notes = new List(); - notes.Add(new DiffSingerNote { - lyric = "R", - length = headTicks, - noteNum = 60, - }); + string lyric = ""; + int noteNum = 60; + int position = 0; foreach (var phone in phrase.phones) { - notes.Add(new DiffSingerNote { - lyric = phone.phoneme, - length = phone.duration, - noteNum = phone.tone, - }); + if (lyric != "") { + notes.Add(new DiffSingerNote { + lyric = lyric, + length = phone.position-position, + noteNum = noteNum, + }); + } + lyric = phone.phoneme; + position=phone.position; + noteNum = phone.tone; + RenderNote currentSlurNote = new RenderNote(new UNote()); + if (slurNoteIterator.Current!=null) { //如果这一句中有连音符 + currentSlurNote = slurNoteIterator.Current; + } else { + slurNoteContinue = false; + } + while (slurNoteContinue && currentSlurNote.position Date: Thu, 18 Aug 2022 19:47:16 +0900 Subject: [PATCH 006/321] Update Strings.ko-KR.axaml --- OpenUtau/Strings/Strings.ko-KR.axaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenUtau/Strings/Strings.ko-KR.axaml b/OpenUtau/Strings/Strings.ko-KR.axaml index 3d955554f..d26fbba17 100644 --- a/OpenUtau/Strings/Strings.ko-KR.axaml +++ b/OpenUtau/Strings/Strings.ko-KR.axaml @@ -170,7 +170,7 @@ 팁 보기 (T) 노트 톤 켜고 끄기 (Y) 비브라토 보기 (U) - + 파형 보기 (W) 피치 그리기 툴 (4) 삭제 툴 (3) 잘라내기 툴 (5) @@ -187,7 +187,7 @@ 안정된 버전 외형 언어 - + 피아노 롤 위에 초상화 표시 테마 다크 라이트 @@ -216,7 +216,7 @@ 테스트 렌더링 위상 보정 - + 사전 렌더러 스레드 사전 렌더링 리샘플러 From cf2e86ff0539c01596943e4434822302cb616e6c Mon Sep 17 00:00:00 2001 From: CP6 Date: Thu, 18 Aug 2022 19:50:34 +0900 Subject: [PATCH 007/321] Fix typo --- OpenUtau/Strings/Strings.ko-KR.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau/Strings/Strings.ko-KR.axaml b/OpenUtau/Strings/Strings.ko-KR.axaml index d26fbba17..01700b143 100644 --- a/OpenUtau/Strings/Strings.ko-KR.axaml +++ b/OpenUtau/Strings/Strings.ko-KR.axaml @@ -187,7 +187,7 @@ 안정된 버전 외형 언어 - 피아노 롤 위에 초상화 표시 + 피아노 롤에 초상화 표시 테마 다크 라이트 From 4e79536cc48a1db3d5623d9274348187b3f2f3e8 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Fri, 19 Aug 2022 00:20:50 -0700 Subject: [PATCH 008/321] oto editing with saving --- OpenUtau.Core/Classic/ClassicSinger.cs | 51 ++++--- OpenUtau.Core/Classic/OtoWatcher.cs | 5 + OpenUtau.Core/Classic/VoiceBank.cs | 5 +- OpenUtau.Core/Classic/VoicebankLoader.cs | 129 +++++++++++++----- OpenUtau.Core/Commands/Notifications.cs | 5 +- OpenUtau.Core/DocManager.cs | 1 + OpenUtau.Core/Enunu/EnunuSinger.cs | 12 +- OpenUtau.Core/PlaybackManager.cs | 16 +-- OpenUtau.Core/Render/RenderEngine.cs | 8 +- OpenUtau.Core/SingerManager.cs | 12 +- OpenUtau.Core/Ustx/USinger.cs | 59 ++++++-- OpenUtau.Core/Vogen/VogenSinger.cs | 12 +- .../SyllableBasedPhonemizer.cs | 8 -- OpenUtau.Test/Classic/VoicebankLoaderTest.cs | 51 +++++++ OpenUtau/Strings/Strings.axaml | 2 + OpenUtau/ViewModels/SingersViewModel.cs | 78 ++++++++--- OpenUtau/Views/SingersDialog.axaml | 9 +- OpenUtau/Views/SingersDialog.axaml.cs | 48 +++++-- 18 files changed, 366 insertions(+), 145 deletions(-) create mode 100644 OpenUtau.Test/Classic/VoicebankLoaderTest.cs diff --git a/OpenUtau.Core/Classic/ClassicSinger.cs b/OpenUtau.Core/Classic/ClassicSinger.cs index 9b745f64a..b0f22e60a 100644 --- a/OpenUtau.Core/Classic/ClassicSinger.cs +++ b/OpenUtau.Core/Classic/ClassicSinger.cs @@ -29,15 +29,15 @@ public class ClassicSinger : USinger { public override string DefaultPhonemizer => voicebank.DefaultPhonemizer; public override Encoding TextFileEncoding => voicebank.TextFileEncoding; public override IList Subbanks => subbanks; - public override Dictionary Otos => otos; + public override IList Otos => otos; Voicebank voicebank; List errors = new List(); byte[] avatarData; - List OtoSets = new List(); - Dictionary otos = new Dictionary(); - Dictionary> phonetics; + List otoSets = new List(); List subbanks = new List(); + List otos = new List(); + Dictionary otoMap = new Dictionary(); OtoWatcher otoWatcher; public ClassicSinger(Voicebank voicebank) { @@ -63,6 +63,7 @@ public override void Reload() { if (otoWatcher == null) { otoWatcher = new OtoWatcher(this, Location); } + OtoDirty = false; } catch (Exception e) { Log.Error(e, $"Failed to load {voicebank.File}"); } @@ -94,13 +95,20 @@ void Load() { .ToList(); var dummy = new USubbank(new Subbank()); - OtoSets.Clear(); + otoSets.Clear(); otos.Clear(); + otoMap.Clear(); errors.Clear(); foreach (var otoSet in voicebank.OtoSets) { var uSet = new UOtoSet(otoSet, voicebank.BasePath); - OtoSets.Add(uSet); + otoSets.Add(uSet); foreach (var oto in otoSet.Otos) { + if (!oto.IsValid) { + if (!string.IsNullOrEmpty(oto.Error)) { + errors.Add(oto.Error); + } + continue; + } UOto? uOto = null; for (var i = 0; i < patterns.Count; i++) { var m = patterns[i].Match(oto.Alias); @@ -113,20 +121,17 @@ void Load() { if (uOto == null) { uOto = new UOto(oto, uSet, dummy); } - if (!otos.ContainsKey(oto.Alias)) { - otos.Add(oto.Alias, uOto); + otos.Add(uOto); + if (!otoMap.ContainsKey(oto.Alias)) { + otoMap.Add(oto.Alias, uOto); } else { //Errors.Add($"oto conflict {Otos[oto.Alias].Set}/{oto.Alias} and {otoSet.Name}/{oto.Alias}"); } } - errors.AddRange(otoSet.Errors); } - phonetics = otos.Values - .GroupBy(oto => oto.Phonetic, oto => oto) - .ToDictionary(g => g.Key, g => g.OrderByDescending(oto => oto.Prefix.Length + oto.Suffix.Length).ToList()); Task.Run(() => { - otos.Values + otoMap.Values .ToList() .ForEach(oto => { oto.SearchTerms.Add(oto.Alias.ToLowerInvariant().Replace(" ", "")); @@ -135,13 +140,25 @@ void Load() { }); } + public override void Save() { + try { + otoWatcher.Paused = true; + foreach (var oto in Otos) { + oto.WriteBack(); + } + VoicebankLoader.WriteOtoSets(voicebank); + } finally { + otoWatcher.Paused = false; + } + } + public override bool TryGetMappedOto(string phoneme, int tone, out UOto oto) { oto = default; var subbank = subbanks.Find(subbank => string.IsNullOrEmpty(subbank.Color) && subbank.toneSet.Contains(tone)); - if (subbank != null && otos.TryGetValue($"{subbank.Prefix}{phoneme}{subbank.Suffix}", out oto)) { + if (subbank != null && otoMap.TryGetValue($"{subbank.Prefix}{phoneme}{subbank.Suffix}", out oto)) { return true; } - if (otos.TryGetValue(phoneme, out oto)) { + if (otoMap.TryGetValue(phoneme, out oto)) { return true; } return false; @@ -150,7 +167,7 @@ public override bool TryGetMappedOto(string phoneme, int tone, out UOto oto) { public override bool TryGetMappedOto(string phoneme, int tone, string color, out UOto oto) { oto = default; var subbank = subbanks.Find(subbank => subbank.Color == color && subbank.toneSet.Contains(tone)); - if (subbank != null && otos.TryGetValue($"{subbank.Prefix}{phoneme}{subbank.Suffix}", out oto)) { + if (subbank != null && otoMap.TryGetValue($"{subbank.Prefix}{phoneme}{subbank.Suffix}", out oto)) { return true; } return TryGetMappedOto(phoneme, tone, out oto); @@ -161,7 +178,7 @@ public override IEnumerable GetSuggestions(string text) { text = text.ToLowerInvariant().Replace(" ", ""); } bool all = string.IsNullOrEmpty(text); - return otos.Values + return otoMap.Values .Where(oto => all || oto.SearchTerms.Exists(term => term.Contains(text))); } diff --git a/OpenUtau.Core/Classic/OtoWatcher.cs b/OpenUtau.Core/Classic/OtoWatcher.cs index bda5d785f..a725b6c29 100644 --- a/OpenUtau.Core/Classic/OtoWatcher.cs +++ b/OpenUtau.Core/Classic/OtoWatcher.cs @@ -5,6 +5,8 @@ namespace OpenUtau.Classic { class OtoWatcher : IDisposable { + public bool Paused { get; set; } + private ClassicSinger singer; private FileSystemWatcher watcher; @@ -22,6 +24,9 @@ public OtoWatcher(ClassicSinger singer, string path) { } private void OnFileChanged(object sender, FileSystemEventArgs e) { + if (Paused) { + return; + } Log.Information($"File \"{e.FullPath}\" {e.ChangeType}"); SingerManager.Inst.ScheduleReload(singer); } diff --git a/OpenUtau.Core/Classic/VoiceBank.cs b/OpenUtau.Core/Classic/VoiceBank.cs index 14e6c9f67..f4c29063b 100644 --- a/OpenUtau.Core/Classic/VoiceBank.cs +++ b/OpenUtau.Core/Classic/VoiceBank.cs @@ -49,7 +49,6 @@ public class OtoSet { public string File; public string Name; public List Otos = new List(); - public List Errors = new List(); public override string ToString() { return Name; @@ -82,6 +81,10 @@ public class Oto { // Length overlap with previous note, usually within consonant range. public double Overlap; + public string RawText; + public bool IsValid; + public string Error; + public override string ToString() { return Alias; } diff --git a/OpenUtau.Core/Classic/VoicebankLoader.cs b/OpenUtau.Core/Classic/VoicebankLoader.cs index 62a6effe5..90e48d8a4 100644 --- a/OpenUtau.Core/Classic/VoicebankLoader.cs +++ b/OpenUtau.Core/Classic/VoicebankLoader.cs @@ -260,7 +260,9 @@ public static Dictionary, SortedSet> ParsePrefixMap(S public static OtoSet ParseOtoSet(string filePath, Encoding encoding) { try { using (var stream = File.OpenRead(filePath)) { - return ParseOtoSet(stream, filePath, encoding); + var otoSet = ParseOtoSet(stream, filePath, encoding); + AddAliasForMissingFiles(otoSet); + return otoSet; } } catch (Exception e) { Log.Error(e, $"Failed to load {filePath}"); @@ -279,21 +281,28 @@ public static OtoSet ParseOtoSet(Stream stream, string filePath, Encoding encodi var line = reader.ReadLine(); fileLoc.line = line; try { - Oto oto = ParseOto(line); + Oto oto = ParseOto(line, fileLoc); if (oto != null) { otoSet.Otos.Add(oto); } + if (!string.IsNullOrEmpty(oto.Error)) { + Log.Error($"Failed to parse\n{fileLoc}: {oto.Error}"); + } } catch (Exception e) { Log.Error(e, $"Failed to parse\n{fileLoc}"); - otoSet.Errors.Add($"Oto error:\n{fileLoc}"); } fileLoc.line = null; fileLoc.lineNumber++; } } + return otoSet; + } + + static void AddAliasForMissingFiles(OtoSet otoSet) { // Use filename as alias if not in oto. var knownFiles = otoSet.Otos.Select(oto => oto.Wav).ToHashSet(); - foreach (var wav in Directory.EnumerateFiles(Path.GetDirectoryName(filePath), "*.wav", SearchOption.TopDirectoryOnly)) { + var dir = Path.GetDirectoryName(otoSet.File); + foreach (var wav in Directory.EnumerateFiles(dir, "*.wav", SearchOption.TopDirectoryOnly)) { var file = Path.GetFileName(wav); if (!knownFiles.Contains(file)) { var oto = new Oto { @@ -304,49 +313,48 @@ public static OtoSet ParseOtoSet(Stream stream, string filePath, Encoding encodi otoSet.Otos.Add(oto); } } - return otoSet; } - public static Oto ParseOto(string line) { + static Oto ParseOto(string line, FileLoc fileLoc) { const string format = "=,,,,,"; + var oto = new Oto { RawText = line }; if (string.IsNullOrWhiteSpace(line)) { - return null; + return oto; } var parts = line.Split('='); if (parts.Length < 2) { - throw new FileFormatException($"Line does not match format {format}."); + oto.Error = $"Line does not match format {format}."; + return oto; } - var wav = parts[0].Trim(); + oto.Wav = parts[0].Trim(); parts = parts[1].Split(','); - var result = new Oto { - Wav = wav, - Alias = parts[0].Trim() - }; - if (string.IsNullOrEmpty(result.Alias)) { - var ext = Path.GetExtension(wav); - if (!string.IsNullOrEmpty(ext)) { - result.Alias = wav.Replace(ext, ""); - } else { - result.Alias = wav; - } + oto.Alias = parts.ElementAtOrDefault(0); + if (string.IsNullOrEmpty(oto.Alias)) { + oto.Alias = RemoveExtension(oto.Wav); } - result.Phonetic = result.Alias; - if (!ParseDouble(parts.Length < 2 ? null : parts[1], out result.Offset)) { - throw new FileFormatException($"Failed to parse offset. Format is {format}."); + oto.Phonetic = oto.Alias; + if (!ParseDouble(parts.ElementAtOrDefault(1), out oto.Offset)) { + oto.Error = $"{fileLoc}\nFailed to parse offset. Format is {format}."; + return oto; } - if (!ParseDouble(parts.Length < 3 ? null : parts[2], out result.Consonant)) { - throw new FileFormatException($"Failed to parse consonant. Format is {format}."); + if (!ParseDouble(parts.ElementAtOrDefault(2), out oto.Consonant)) { + oto.Error = $"{fileLoc}\nFailed to parse consonant. Format is {format}."; + return oto; } - if (!ParseDouble(parts.Length < 4 ? null : parts[3], out result.Cutoff)) { - throw new FileFormatException($"Failed to parse cutoff. Format is {format}."); + if (!ParseDouble(parts.ElementAtOrDefault(3), out oto.Cutoff)) { + oto.Error = $"{fileLoc}\nFailed to parse cutoff. Format is {format}."; + return oto; } - if (!ParseDouble(parts.Length < 5 ? null : parts[4], out result.Preutter)) { - throw new FileFormatException($"Failed to parse preutter. Format is {format}."); + if (!ParseDouble(parts.ElementAtOrDefault(4), out oto.Preutter)) { + oto.Error = $"{fileLoc}\nFailed to parse preutter. Format is {format}."; + return oto; } - if (!ParseDouble(parts.Length < 6 ? null : parts[5], out result.Overlap)) { - throw new FileFormatException($"Failed to parse overlap. Format is {format}."); + if (!ParseDouble(parts.ElementAtOrDefault(5), out oto.Overlap)) { + oto.Error = $"{fileLoc}\nFailed to parse overlap. Format is {format}."; + return oto; } - return result; + oto.IsValid = true; + return oto; } static bool ParseDouble(string s, out double value) { @@ -356,5 +364,62 @@ static bool ParseDouble(string s, out double value) { } return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value); } + + public static void WriteOtoSets(Voicebank voicebank) { + foreach (var otoSet in voicebank.OtoSets) { + using (var stream = File.Open(otoSet.File, FileMode.Create, FileAccess.Write)) { + WriteOtoSet(otoSet, stream, voicebank.TextFileEncoding); + } + Log.Information($"Write oto set {otoSet.Name}"); + } + } + + public static void WriteOtoSet(OtoSet otoSet, Stream stream, Encoding encoding) { + using (var writer = new StreamWriter(stream, encoding)) { + foreach (var oto in otoSet.Otos) { + if (!oto.IsValid) { + writer.Write(oto.RawText); + writer.Write('\n'); + continue; + } + writer.Write(oto.Wav); + writer.Write('='); + if (oto.Alias != RemoveExtension(oto.Wav)) { + writer.Write(oto.Alias); + } + writer.Write(','); + if (oto.Offset != 0) { + writer.Write(oto.Offset); + } + writer.Write(','); + if (oto.Consonant != 0) { + writer.Write(oto.Consonant); + } + writer.Write(','); + if (oto.Cutoff != 0) { + writer.Write(oto.Cutoff); + } + writer.Write(','); + if (oto.Preutter != 0) { + writer.Write(oto.Preutter); + } + writer.Write(','); + if (oto.Overlap != 0) { + writer.Write(oto.Overlap); + } + writer.Write('\n'); + } + writer.Flush(); + } + } + + static string RemoveExtension(string filePath) { + var ext = Path.GetExtension(filePath); + if (!string.IsNullOrEmpty(ext)) { + return filePath.Substring(0, filePath.Length - ext.Length); + } else { + return filePath; + } + } } } diff --git a/OpenUtau.Core/Commands/Notifications.cs b/OpenUtau.Core/Commands/Notifications.cs index c1ff748cf..650cfc79a 100644 --- a/OpenUtau.Core/Commands/Notifications.cs +++ b/OpenUtau.Core/Commands/Notifications.cs @@ -130,7 +130,10 @@ public SingersRefreshedNotification() { } } public class OtoChangedNotification : UNotification { - public OtoChangedNotification() { } + public readonly bool external; + public OtoChangedNotification(bool external = false) { + this.external = external; + } public override string ToString() => "Oto changed."; } diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 76e2519d4..2510e418e 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -30,6 +30,7 @@ public class DocManager : SingletonBase { public int playPosTick = 0; + public TaskScheduler MainScheduler => mainScheduler; public Plugin[] Plugins { get; private set; } public PhonemizerFactory[] PhonemizerFactories { get; private set; } public UProject Project { get; private set; } diff --git a/OpenUtau.Core/Enunu/EnunuSinger.cs b/OpenUtau.Core/Enunu/EnunuSinger.cs index 87cd19dc9..b8b71b542 100644 --- a/OpenUtau.Core/Enunu/EnunuSinger.cs +++ b/OpenUtau.Core/Enunu/EnunuSinger.cs @@ -28,13 +28,11 @@ class EnunuSinger : USinger { public override string DefaultPhonemizer => voicebank.DefaultPhonemizer; public override Encoding TextFileEncoding => voicebank.TextFileEncoding; public override IList Subbanks => subbanks; - public override Dictionary Otos => otos; Voicebank voicebank; EnunuConfig enuconfig; List errors = new List(); List subbanks = new List(); - Dictionary otos = new Dictionary(); HashSet phonemes = new HashSet(); Dictionary table = new Dictionary(); @@ -106,10 +104,7 @@ public EnunuSinger(Voicebank voicebank) { public override bool TryGetMappedOto(string phoneme, int tone, out UOto oto) { var parts = phoneme.Split(); if (parts.All(p => phonemes.Contains(p))) { - oto = new UOto() { - Alias = phoneme, - Phonetic = phoneme, - }; + oto = UOto.OfDummy(phoneme); return true; } oto = null; @@ -127,10 +122,7 @@ public override IEnumerable GetSuggestions(string text) { bool all = string.IsNullOrEmpty(text); return table.Keys .Where(key => all || key.Contains(text)) - .Select(key => new UOto() { - Alias = key, - Phonetic = key, - }); + .Select(key => UOto.OfDummy(key)); } public override byte[] LoadPortrait() { diff --git a/OpenUtau.Core/PlaybackManager.cs b/OpenUtau.Core/PlaybackManager.cs index b4aaade1e..10d8d8f97 100644 --- a/OpenUtau.Core/PlaybackManager.cs +++ b/OpenUtau.Core/PlaybackManager.cs @@ -10,6 +10,7 @@ using OpenUtau.Core.Render; using OpenUtau.Core.SignalChain; using OpenUtau.Core.Ustx; +using OpenUtau.Core.Util; using Serilog; namespace OpenUtau.Core { @@ -42,7 +43,7 @@ public int Read(float[] buffer, int offset, int count) { } } - public class PlaybackManager : ICmdSubscriber { + public class PlaybackManager : SingletonBase, ICmdSubscriber { private PlaybackManager() { DocManager.Inst.AddSubscriber(this); try { @@ -53,9 +54,6 @@ private PlaybackManager() { } } - private static PlaybackManager _s; - public static PlaybackManager Inst { get { if (_s == null) { _s = new PlaybackManager(); } return _s; } } - List faders; MasterAdapter masterMix; double startMs; @@ -130,10 +128,9 @@ private void Render(UProject project, int tick) { if (!Resamplers.CheckResampler()) { return; } - var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); Task.Run(() => { RenderEngine engine = new RenderEngine(project, tick); - var result = engine.RenderProject(tick, scheduler, ref renderCancellation); + var result = engine.RenderProject(tick, DocManager.Inst.MainScheduler, ref renderCancellation); faders = result.Item2; StartingToPlay = false; StartPlayback(project.TickToMillisecond(tick), result.Item1); @@ -143,7 +140,7 @@ private void Render(UProject project, int tick) { DocManager.Inst.ExecuteCmd(new UserMessageNotification(task.Exception.Flatten().ToString())); throw task.Exception; } - }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, scheduler); + }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, DocManager.Inst.MainScheduler); } public void UpdatePlayPos() { @@ -159,11 +156,10 @@ public static float DecibelToVolume(double db) { } public void RenderToFiles(UProject project, string exportPath) { - var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); Task.Run(() => { var task = Task.Run(() => { RenderEngine engine = new RenderEngine(project); - var trackMixes = engine.RenderTracks(scheduler, ref renderCancellation); + var trackMixes = engine.RenderTracks(DocManager.Inst.MainScheduler, ref renderCancellation); for (int i = 0; i < trackMixes.Count; ++i) { if (trackMixes[i] == null || i >= project.tracks.Count || project.tracks[i].Mute) { continue; @@ -185,7 +181,7 @@ public void RenderToFiles(UProject project, string exportPath) { } void SchedulePreRender() { - var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); + var scheduler = TaskScheduler.Default; Log.Information("SchedulePreRender"); var engine = new RenderEngine(DocManager.Inst.Project); engine.PreRenderProject(scheduler, ref renderCancellation); diff --git a/OpenUtau.Core/Render/RenderEngine.cs b/OpenUtau.Core/Render/RenderEngine.cs index 69720bee3..df9fe87d1 100644 --- a/OpenUtau.Core/Render/RenderEngine.cs +++ b/OpenUtau.Core/Render/RenderEngine.cs @@ -9,11 +9,9 @@ namespace OpenUtau.Core.Render { public class Progress { - readonly TaskScheduler uiScheduler; readonly int total; int completed = 0; - public Progress(TaskScheduler uiScheduler, int total) { - this.uiScheduler = uiScheduler; + public Progress(int total) { this.total = total; } @@ -29,7 +27,7 @@ public void Clear() { private void Notify(double progress, string info) { var notif = new ProgressBarNotification(progress, info); var task = new Task(() => DocManager.Inst.ExecuteCmd(notif)); - task.Start(uiScheduler); + task.Start(DocManager.Inst.MainScheduler); } } @@ -198,7 +196,7 @@ private void RenderRequests( .ToArray(); tuples = orderedTuples; } - var progress = new Progress(uiScheduler, tuples.Sum(t => t.Item1.phones.Length)); + var progress = new Progress(tuples.Sum(t => t.Item1.phones.Length)); foreach (var tuple in tuples) { var phrase = tuple.Item1; var source = tuple.Item2; diff --git a/OpenUtau.Core/SingerManager.cs b/OpenUtau.Core/SingerManager.cs index 7f3374c46..db517d67b 100644 --- a/OpenUtau.Core/SingerManager.cs +++ b/OpenUtau.Core/SingerManager.cs @@ -80,7 +80,9 @@ private void Refresh() { } foreach (var singer in singers) { Log.Information($"Reloading {singer.Id}"); - DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"Reloading {singer.Id}")); + new Task(() => { + DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"Reloading {singer.Id}")); + }).Start(DocManager.Inst.MainScheduler); int retries = 5; while (retries > 0) { retries--; @@ -97,10 +99,10 @@ private void Refresh() { } } Log.Information($"Reloaded {singer.Id}"); - DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"Reloaded {singer.Id}")); - } - if (singers.Count > 0) { - DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); + new Task(() => { + DocManager.Inst.ExecuteCmd(new ProgressBarNotification(0, $"Reloaded {singer.Id}")); + DocManager.Inst.ExecuteCmd(new OtoChangedNotification(external: true)); + }).Start(DocManager.Inst.MainScheduler); } } } diff --git a/OpenUtau.Core/Ustx/USinger.cs b/OpenUtau.Core/Ustx/USinger.cs index 5c41b1059..af2d3d9c0 100644 --- a/OpenUtau.Core/Ustx/USinger.cs +++ b/OpenUtau.Core/Ustx/USinger.cs @@ -7,19 +7,19 @@ namespace OpenUtau.Core.Ustx { public class UOto : INotifyPropertyChanged { - public string Alias { get; set; } - public string Phonetic { get; set; } - public string Set { get; set; } - public string Color { get; set; } - public string Prefix { get; set; } - public string Suffix { get; set; } - public SortedSet ToneSet { get; set; } - public string File { get; set; } - public string DisplayFile { get; set; } + public string Alias { get; private set; } + public string Phonetic { get; private set; } + public string Set { get; private set; } + public string Color { get; private set; } + public string Prefix { get; private set; } + public string Suffix { get; private set; } + public SortedSet ToneSet { get; private set; } + public string File { get; private set; } + public string DisplayFile { get; private set; } public double Offset { get => offset; set { - offset = Math.Round(value, 3); + offset = Math.Max(0, Math.Round(value, 3)); NotifyPropertyChanged(nameof(Offset)); } } @@ -51,10 +51,11 @@ public double Overlap { NotifyPropertyChanged(nameof(Overlap)); } } - public List SearchTerms { private set; get; } + public List SearchTerms { get; private set; } public event PropertyChangedEventHandler PropertyChanged; + private Oto oto; private double offset; private double consonant; private double cutoff; @@ -64,6 +65,7 @@ public double Overlap { public UOto() { } public UOto(Oto oto, UOtoSet set, USubbank subbank) { + this.oto = oto; Alias = oto.Alias; Phonetic = oto.Phonetic; Set = set.Name; @@ -82,6 +84,19 @@ public UOto(Oto oto, UOtoSet set, USubbank subbank) { SearchTerms = new List(); } + public static UOto OfDummy(string alias) => new UOto() { + Alias = alias, + Phonetic = alias, + }; + + public void WriteBack() { + oto.Offset = offset; + oto.Consonant = consonant; + oto.Cutoff = cutoff; + oto.Preutter = preutter; + oto.Overlap = overlap; + } + private void NotifyPropertyChanged(string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } @@ -163,7 +178,9 @@ private static void AddToneRange(string range, SortedSet set) { [Flags] public enum USingerType { Classic = 0x1, Enunu = 0x2, Vogen = 0x4 } - public class USinger { + public class USinger : INotifyPropertyChanged { + protected static readonly List emptyOtos = new List(); + public virtual string Id { get; } public virtual string Name => name; public virtual USingerType SingerType { get; } @@ -182,13 +199,23 @@ public class USinger { public virtual string DefaultPhonemizer { get; } public virtual Encoding TextFileEncoding => Encoding.UTF8; public virtual IList Subbanks { get; } - public virtual Dictionary Otos { get; } + public virtual IList Otos => emptyOtos; public bool Found => found; public bool Loaded => found && loaded; + public bool OtoDirty { + get => otoDirty; + set { + otoDirty = value; + NotifyPropertyChanged(nameof(OtoDirty)); + } + } + + public event PropertyChangedEventHandler PropertyChanged; protected bool found; protected bool loaded; + protected bool otoDirty; private string name; @@ -196,6 +223,7 @@ public class USinger { public virtual void EnsureLoaded() { } public virtual void Reload() { } + public virtual void Save() { } public virtual bool TryGetMappedOto(string phoneme, int tone, out UOto oto) { oto = default; return false; @@ -205,7 +233,6 @@ public virtual bool TryGetMappedOto(string phoneme, int tone, string color, out return false; } - private static readonly List emptyOtos = new List(); public virtual IEnumerable GetSuggestions(string text) { return emptyOtos; } public virtual byte[] LoadPortrait() => null; public override string ToString() => Name; @@ -217,5 +244,9 @@ public static USinger CreateMissing(string name) { name = name, }; } + + private void NotifyPropertyChanged(string propertyName = "") { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } } diff --git a/OpenUtau.Core/Vogen/VogenSinger.cs b/OpenUtau.Core/Vogen/VogenSinger.cs index f7b0105e0..72486fade 100644 --- a/OpenUtau.Core/Vogen/VogenSinger.cs +++ b/OpenUtau.Core/Vogen/VogenSinger.cs @@ -24,14 +24,12 @@ class VogenSinger : USinger { public override string DefaultPhonemizer => "OpenUtau.Core.Vogen.VogenMandarinPhonemizer"; public override Encoding TextFileEncoding => Encoding.UTF8; public override IList Subbanks => subbanks; - public override Dictionary Otos => otos; string basePath; string filePath; VogenMeta meta; List errors = new List(); List subbanks = new List(); - Dictionary otos = new Dictionary(); public byte[] model; public byte[] avatarData; @@ -47,18 +45,12 @@ public VogenSinger(string filePath, VogenMeta meta, byte[] model, byte[] avatar) } public override bool TryGetMappedOto(string phoneme, int tone, out UOto oto) { - oto = new UOto() { - Alias = phoneme, - Phonetic = phoneme, - }; + oto = UOto.OfDummy(phoneme); return true; } public override bool TryGetMappedOto(string phoneme, int tone, string color, out UOto oto) { - oto = new UOto() { - Alias = phoneme, - Phonetic = phoneme, - }; + oto = UOto.OfDummy(phoneme); return true; } diff --git a/OpenUtau.Plugin.Builtin/SyllableBasedPhonemizer.cs b/OpenUtau.Plugin.Builtin/SyllableBasedPhonemizer.cs index 40dfb866b..a653a731b 100644 --- a/OpenUtau.Plugin.Builtin/SyllableBasedPhonemizer.cs +++ b/OpenUtau.Plugin.Builtin/SyllableBasedPhonemizer.cs @@ -482,14 +482,6 @@ protected virtual double GetTransitionBasicLengthMs(string alias = "") { return GetTransitionBasicLengthMsByConstant(); } - protected double GetTransitionBasicLengthMsByOto(string alias) { - if (alias != null && alias.Length > 0 && singer.Otos.TryGetValue(alias, out var oto)) { - return oto.Preutter * GetTempoNoteLengthFactor(); - } else { - return GetTransitionBasicLengthMsByConstant(); - } - } - protected double GetTransitionBasicLengthMsByConstant() { return TransitionBasicLengthMs * GetTempoNoteLengthFactor(); } diff --git a/OpenUtau.Test/Classic/VoicebankLoaderTest.cs b/OpenUtau.Test/Classic/VoicebankLoaderTest.cs new file mode 100644 index 000000000..f3f2917cd --- /dev/null +++ b/OpenUtau.Test/Classic/VoicebankLoaderTest.cs @@ -0,0 +1,51 @@ +using System.IO; +using System.Text; +using Xunit; +using Xunit.Abstractions; + +namespace OpenUtau.Classic { + public class VoicebankLoaderTest { + readonly ITestOutputHelper output; + + public VoicebankLoaderTest(ITestOutputHelper output) { + this.output = output; + } + + [Fact] + public void OtoSetRoundTrip() { + string text = @"a.wav=,,,,, +a.wav=- a, +a.wav=a R,500,,, +!@#$!@#$ +aoieu.wav=- a,,,,,,,,, + +aoieu.wav=a o,, +aoieu.wav=o i,,, +aoieu.wav=i e,,100,150,,, +aoieu.wav=e u,20, +aoieu.wav=u R,5,,33,44,, +".Replace("\r\n", "\n"); + string expected = @"a.wav=,,,,, +a.wav=- a,,,,, +a.wav=a R,500,,,, +!@#$!@#$ +aoieu.wav=- a,,,,, + +aoieu.wav=a o,,,,, +aoieu.wav=o i,,,,, +aoieu.wav=i e,,100,150,, +aoieu.wav=e u,20,,,, +aoieu.wav=u R,5,,33,44, +".Replace("\r\n", "\n"); + + using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(text))) { + var otoSet = VoicebankLoader.ParseOtoSet(stream, "oto.ini", Encoding.ASCII); + using (MemoryStream stream2 = new MemoryStream()) { + VoicebankLoader.WriteOtoSet(otoSet, stream2, Encoding.ASCII); + string actual = Encoding.ASCII.GetString(stream2.ToArray()); + Assert.Equal(expected, actual); + } + } + } + } +} diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 45ca3cdbc..d479bd5a0 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -238,6 +238,8 @@ Warning: moresampler is not fully supported. It may be slow and cause high CPU u Waiting Rendering Singers + Reset Otos + Save Otos Location Move Left Move Right diff --git a/OpenUtau/ViewModels/SingersViewModel.cs b/OpenUtau/ViewModels/SingersViewModel.cs index 0c9c33d04..5d271fec9 100644 --- a/OpenUtau/ViewModels/SingersViewModel.cs +++ b/OpenUtau/ViewModels/SingersViewModel.cs @@ -41,10 +41,10 @@ public SingersViewModel() { this.WhenAnyValue(vm => vm.Singer) .WhereNotNull() .Subscribe(singer => { - singer.Reload(); + singer.EnsureLoaded(); Avatar = LoadAvatar(singer); Otos.Clear(); - Otos.AddRange(singer.Otos.Values); + Otos.AddRange(singer.Otos); Info = $"Author: {singer.Author}\nVoice: {singer.Voice}\nWeb: {singer.Web}\nVersion: {singer.Version}\n{singer.OtherInfo}\n\n{string.Join("\n", singer.Errors)}"; LoadSubbanks(); DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); @@ -191,13 +191,26 @@ public void LoadSubbanks() { } public void RefreshSinger() { - Singer?.Reload(); + if (Singer == null) { + return; + } + int index = SelectedIndex; + + Singer.Reload(); + Avatar = LoadAvatar(Singer); + Otos.Clear(); + Otos.AddRange(Singer.Otos); LoadSubbanks(); + DocManager.Inst.ExecuteCmd(new SingersRefreshedNotification()); DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); + if (Otos.Count > 0) { + index = Math.Clamp(index, 0, Otos.Count - 1); + SelectedIndex = index; + } } - public void SetOffset(double value) { + public void SetOffset(double value, double totalDur) { if (SelectedOto == null) { return; } @@ -209,43 +222,72 @@ public void SetOffset(double value) { if (SelectedOto.Cutoff < 0) { SelectedOto.Cutoff += delta; } - DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); + FixCutoff(SelectedOto, totalDur); + NotifyOtoChanged(); } - public void SetOverlap(double value) { + public void SetOverlap(double value, double totalDur) { if (SelectedOto == null) { return; } SelectedOto.Overlap = value - SelectedOto.Offset; - DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); + FixCutoff(SelectedOto, totalDur); + NotifyOtoChanged(); } - public void SetPreutter(double value) { + public void SetPreutter(double value, double totalDur) { if (SelectedOto == null) { return; } - var delta = value - SelectedOto.Offset - SelectedOto.Preutter; - SelectedOto.Preutter += delta; - DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); + SelectedOto.Preutter = value - SelectedOto.Offset; + FixCutoff(SelectedOto, totalDur); + NotifyOtoChanged(); } - public void SetFixed(double value) { + public void SetFixed(double value, double totalDur) { if (SelectedOto == null) { return; } SelectedOto.Consonant = value - SelectedOto.Offset; - DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); + FixCutoff(SelectedOto, totalDur); + NotifyOtoChanged(); } - public void SetCutoff(double value) { - if (SelectedOto == null) { - return; - } - if (value < SelectedOto.Offset) { + public void SetCutoff(double value, double totalDur) { + if (SelectedOto == null || value < SelectedOto.Offset) { return; } SelectedOto.Cutoff = -(value - SelectedOto.Offset); + FixCutoff(SelectedOto, totalDur); + NotifyOtoChanged(); + } + + private static void FixCutoff(UOto oto, double totalDur) { + double cutoff = oto.Cutoff >= 0 + ? totalDur - oto.Cutoff + : oto.Offset - oto.Cutoff; + double minCutoff = oto.Offset + Math.Max(Math.Max(oto.Overlap, oto.Preutter), oto.Consonant); + if (cutoff < minCutoff) { + oto.Cutoff = -(minCutoff - oto.Offset); + } + } + + private void NotifyOtoChanged() { + if (Singer != null) { + Singer.OtoDirty = true; + } DocManager.Inst.ExecuteCmd(new OtoChangedNotification()); } + + public void SaveOtos() { + if (Singer != null) { + try { + Singer.Save(); + } catch (Exception e) { + DocManager.Inst.ExecuteCmd(new UserMessageNotification(e.ToString())); + } + } + RefreshSinger(); + } } } diff --git a/OpenUtau/Views/SingersDialog.axaml b/OpenUtau/Views/SingersDialog.axaml index 179a2fead..54c4b9ab0 100644 --- a/OpenUtau/Views/SingersDialog.axaml +++ b/OpenUtau/Views/SingersDialog.axaml @@ -76,7 +76,14 @@ - + + Items="{Binding Singers}" SelectedItem="{Binding Singer}" SelectionChanged="OnSelectedSingerChanged"/> @@ -94,7 +94,7 @@ BorderBrush="{DynamicResource SystemControlHighlightBaseMediumHighBrush}" Items="{Binding Otos}" SelectedItem="{Binding SelectedOto}" SelectedIndex="{Binding SelectedIndex}" - IsReadOnly="True" SelectionChanged="OnSelectionChanged"> + IsReadOnly="True" SelectionChanged="OnSelectedOtoChanged"> diff --git a/OpenUtau/Views/SingersDialog.axaml.cs b/OpenUtau/Views/SingersDialog.axaml.cs index 37d974b7b..c48350e07 100644 --- a/OpenUtau/Views/SingersDialog.axaml.cs +++ b/OpenUtau/Views/SingersDialog.axaml.cs @@ -40,7 +40,7 @@ public partial class SingersDialog : Window, ICmdSubscriber { IPlottable? waveform; IPlottable? spectrogram; List timingMarks = new List(); - AxisLimits outerLimites; + AxisLimits outerLimits; public SingersDialog() { try { @@ -129,13 +129,20 @@ async void OnEditSubbanksButton(object sender, RoutedEventArgs args) { await dialog.ShowDialog(this); } - void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { + void OnSelectedSingerChanged(object sender, SelectionChangedEventArgs e) { + otoPlot?.Plot.Clear(); + otoPlot?.Refresh(); + } + + void OnSelectedOtoChanged(object sender, SelectionChangedEventArgs e) { var viewModel = (DataContext as SingersViewModel)!; if (viewModel.Singer == null || e.AddedItems.Count < 1) { return; } var oto = (Core.Ustx.UOto?)e.AddedItems[0]; if (oto == null || !File.Exists(oto.File)) { + otoPlot?.Plot.Clear(); + otoPlot?.Refresh(); return; } DrawOto(oto, true); @@ -145,7 +152,8 @@ void DrawOto(Core.Ustx.UOto? oto, bool fit = false) { if (otoPlot == null || oto == null) { return; } - var limites = otoPlot.Plot.GetAxisLimits(); + var limits = otoPlot.Plot.GetAxisLimits(); + otoPlot.Plot.SetAxisLimitsY(0, 120); bool loadWav = wavPath != oto.File; if (loadWav) { try { @@ -154,12 +162,10 @@ void DrawOto(Core.Ustx.UOto? oto, bool fit = false) { wavPath = oto.File; } double hopSize = GetHopSize(wav.WaveFmt.SamplingRate); - outerLimites = new AxisLimits(0, wav.Signals[0].Length / hopSize, 0, 120); + outerLimits = new AxisLimits(0, wav.Signals[0].Length / hopSize, 0, 120); otoPlot.Plot.SetOuterViewLimits( - outerLimites.XMin, outerLimites.XMax, - outerLimites.YMin, outerLimites.YMax); - otoPlot.Plot.SetAxisLimitsY( - outerLimites.YMin, outerLimites.YMax); + outerLimits.XMin, outerLimits.XMax, + outerLimits.YMin, outerLimits.YMax); } catch (Exception e) { Log.Error(e, "failed to load wav"); wav = null; @@ -221,7 +227,7 @@ void DrawOto(Core.Ustx.UOto? oto, bool fit = false) { if (loadWav) { ZoomAll(); } else { - otoPlot.Plot.SetAxisLimits(limites); + otoPlot.Plot.SetAxisLimitsX(limits.XMin, limits.XMax); } otoPlot.Refresh(); } @@ -247,7 +253,7 @@ void DrawTiming(Core.Ustx.UOto oto) { double preutterX = (oto.Offset + oto.Preutter) * msToCoord; double overlapX = (oto.Offset + oto.Overlap) * msToCoord; double cutoffX = cutoff * msToCoord; - double durX = outerLimites.XMax; + double durX = outerLimits.XMax; if (offsetX > 0) { timingMarks.Add(otoPlot.Plot.AddPolygon( @@ -279,7 +285,8 @@ void ZoomAll() { if (otoPlot == null || wav == null) { return; } - otoPlot.Plot.SetAxisLimitsX(outerLimites.XMin, outerLimites.XMax); + otoPlot.Plot.SetAxisLimitsX(outerLimits.XMin, outerLimits.XMax); + otoPlot.Plot.SetAxisLimitsY(0, 120); } int GetHopSize(int sampleRate) { @@ -359,14 +366,14 @@ void OnKeyDown(object sender, KeyEventArgs args) { case Key.Q: { if (otoGrid != null) { otoGrid.SelectedIndex = Math.Max(0, otoGrid.SelectedIndex - 1); - otoGrid.ScrollIntoView(otoGrid.SelectedItem, otoGrid.Columns[0]); + otoGrid.ScrollIntoView(otoGrid.SelectedItem, null); } break; } case Key.E: { if (otoGrid != null) { otoGrid.SelectedIndex++; - otoGrid.ScrollIntoView(otoGrid.SelectedItem, otoGrid.Columns[0]); + otoGrid.ScrollIntoView(otoGrid.SelectedItem, null); } break; } @@ -392,6 +399,14 @@ public void OnNext(UCommand cmd, bool isUndo) { viewModel.RefreshSinger(); } DrawOto(viewModel.SelectedOto); + } else if (cmd is GotoOtoNotification editOto) { + var viewModel = DataContext as SingersViewModel; + if (viewModel == null) { + return; + } + viewModel.GotoOto(editOto.singer, editOto.oto); + otoGrid?.ScrollIntoView(otoGrid.SelectedItem, null); + Activate(); } } From f04a2534f0b5788785d795d3e54d49676ebb2c76 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Fri, 19 Aug 2022 18:16:07 -0700 Subject: [PATCH 010/321] manual snapping unit and triplet snapping --- OpenUtau.Core/Util/MusicMath.cs | 21 ++++++++ OpenUtau/Controls/TickBackground.cs | 14 ++++-- OpenUtau/Strings/Strings.axaml | 2 + OpenUtau/ViewModels/NotesViewModel.cs | 65 +++++++++++++++++++++---- OpenUtau/Views/PianoRollWindow.axaml | 23 ++++++--- OpenUtau/Views/PianoRollWindow.axaml.cs | 6 +++ 6 files changed, 111 insertions(+), 20 deletions(-) diff --git a/OpenUtau.Core/Util/MusicMath.cs b/OpenUtau.Core/Util/MusicMath.cs index e6d048436..e8924536b 100644 --- a/OpenUtau.Core/Util/MusicMath.cs +++ b/OpenUtau.Core/Util/MusicMath.cs @@ -165,5 +165,26 @@ public static double ToneToFreq(double tone) { public static double FreqToTone(double freq) { return Math.Log(freq / 440.0, a) + 69; } + + public static List GetSnapUnitDivs(int resolution, int beatUnit) { + var result = new List(); + int div = beatUnit; + int ticks = resolution * 4 / beatUnit; + result.Add(div); + while (ticks % 2 == 0) { + ticks /= 2; + div *= 2; + result.Add(div); + } + div = beatUnit * 3; + ticks = resolution * 4 / beatUnit / 3; + result.Add(div); + while (ticks % 2 == 0) { + ticks /= 2; + div *= 2; + result.Add(div); + } + return result; + } } } diff --git a/OpenUtau/Controls/TickBackground.cs b/OpenUtau/Controls/TickBackground.cs index 56c4cd29c..a60799576 100644 --- a/OpenUtau/Controls/TickBackground.cs +++ b/OpenUtau/Controls/TickBackground.cs @@ -138,7 +138,8 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change.Property == ResolutionProperty || change.Property == TickOriginProperty || change.Property == TickWidthProperty || - change.Property == TickOffsetProperty) { + change.Property == TickOffsetProperty || + change.Property == SnapUnitProperty) { InvalidateVisual(); } } @@ -149,10 +150,15 @@ public override void Render(DrawingContext context) { } int beatUnitTicks = Resolution * 4 / BeatUnit; int barTicks = beatUnitTicks * BeatPerBar; - int snapUnitIndex = (int)((TickOffset + TickOrigin) / SnapUnit); + int snapUnit = SnapUnit; + while (snapUnit * TickWidth < ViewConstants.MinTicklineWidth / 2) { + // Avoid drawing too dense. + snapUnit *= 2; + } + int snapUnitIndex = (int)((TickOffset + TickOrigin) / snapUnit); double pixelOffset = (TickOffset + TickOrigin) * TickWidth; - for (; snapUnitIndex * SnapUnit * TickWidth - pixelOffset < Bounds.Width; ++snapUnitIndex) { - int tick = snapUnitIndex * SnapUnit; + for (; snapUnitIndex * snapUnit * TickWidth - pixelOffset < Bounds.Width; ++snapUnitIndex) { + int tick = snapUnitIndex * snapUnit; double x = Math.Round(tick * TickWidth - pixelOffset) + 0.5; double y = ShowBarNumber ? 24.5 : -0.5; var pen = penDanshed; diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index d479bd5a0..e8d50d90c 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -172,6 +172,8 @@ Warning: this option removes custom presets. View Phonemes (O) View Pitch Bend (I) Toggle Snap (P) + Auto + Auto (triplet) View Tips (T) Toggle Note Tone (Y) View Vibrato (U) diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 2a680ad1e..7643e800f 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -75,6 +75,9 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber { public double HScrollBarMax => Math.Max(0, TickCount - ViewportTicks); public double VScrollBarMax => Math.Max(0, TrackCount - ViewportTracks); public UProject Project => DocManager.Inst.Project; + [Reactive] public List SnapUnits { get; set; } + + public ReactiveCommand SetSnapUnitCommand { get; set; } // See the comments on TracksViewModel.playPosXToTickOffset private double playPosXToTickOffset => ViewportTicks / Bounds.Width; @@ -92,8 +95,15 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber { private int _lastNoteLength = 480; private string? portraitSource; private readonly object portraitLock = new object(); + private int snapUnitDiv = -2; public NotesViewModel() { + SnapUnits = new List(); + SetSnapUnitCommand = ReactiveCommand.Create(div => { + snapUnitDiv = div; + UpdateSnapUnit(); + }); + snapUnitWidth = this.WhenAnyValue(x => x.SnapUnit, x => x.TickWidth) .Select(v => v.Item1 * v.Item2) .ToProperty(this, v => v.SnapUnitWidth); @@ -117,16 +127,7 @@ public NotesViewModel() { this.WhenAnyValue(x => x.TickWidth) .Subscribe(tickWidth => { - int div = Project.beatUnit; - int ticks = Project.resolution * 4 / Project.beatUnit; - double width = ticks * tickWidth; - while (width / 2 >= ViewConstants.PianoRollMinTicklineWidth && ticks % 2 == 0) { - width /= 2; - ticks /= 2; - div *= 2; - } - SnapUnit = ticks; - SnapUnitText = $"1/{div}"; + UpdateSnapUnit(); }); this.WhenAnyValue(x => x.TickOffset) .Subscribe(tickOffset => { @@ -145,6 +146,29 @@ public NotesViewModel() { ExpShadowOpacity = 0.3; } }); + this.WhenAnyValue(x => x.Project) + .Subscribe(project => { + if (project == null) { + return; + } + SnapUnits.Clear(); + SnapUnits.Add(new MenuItemViewModel { + Header = ThemeManager.GetString("pianoroll.toggle.snap.auto"), + Command = SetSnapUnitCommand, + CommandParameter = -2, + }); + SnapUnits.Add(new MenuItemViewModel { + Header = ThemeManager.GetString("pianoroll.toggle.snap.autotriplet"), + Command = SetSnapUnitCommand, + CommandParameter = -3, + }); + SnapUnits.AddRange(MusicMath.GetSnapUnitDivs(project.resolution, project.beatUnit) + .Select(div => new MenuItemViewModel { + Header = $"1/{div}", + Command = SetSnapUnitCommand, + CommandParameter = div, + })); + }); CursorTool = false; PencilTool = true; @@ -183,6 +207,27 @@ public NotesViewModel() { DocManager.Inst.AddSubscriber(this); } + private void UpdateSnapUnit() { + if (snapUnitDiv > 0) { + SnapUnit = Project.resolution * 4 / snapUnitDiv; + SnapUnitText = $"1/{snapUnitDiv}"; + return; + } + int div = Project.beatUnit; + if (snapUnitDiv % 3 == 0) { + div *= 3; + } + int ticks = Project.resolution * 4 / div; + double width = ticks * TickWidth; + while (width / 2 >= ViewConstants.PianoRollMinTicklineWidth && ticks % 2 == 0) { + width /= 2; + ticks /= 2; + div *= 2; + } + SnapUnit = ticks; + SnapUnitText = $"(1/{div})"; + } + public void OnXZoomed(Point position, double delta) { bool recenter = true; if (TickOffset == 0 && position.X < 0.1) { diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index 5067427eb..08c0a7fb9 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -314,15 +314,26 @@ - - + - - + + diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index 7014fc82c..9042b26f8 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -639,6 +639,12 @@ public void PhonemeCanvasPointerReleased(object sender, PointerReleasedEventArgs Cursor = null; } + public void OnSnapUnitMenuButton(object sender, RoutedEventArgs args) { + var menu = this.FindControl("SnapUnitMenu"); + menu.PlacementTarget = sender as Button; + menu.Open(); + } + #region value tip void IValueTip.ShowValueTip() { From a123b3742c7dbf36956b99cb9c987c806ab884bb Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Fri, 19 Aug 2022 23:31:12 -0700 Subject: [PATCH 011/321] the normal pen tool you wont complaint --- OpenUtau/Strings/Strings.axaml | 7 +- OpenUtau/ViewModels/NotesViewModel.cs | 9 ++- OpenUtau/ViewModels/NotesViewModelHitTest.cs | 10 --- OpenUtau/ViewModels/PianoRollViewModel.cs | 23 ++++++ OpenUtau/Views/PianoRollWindow.axaml | 62 ++++++++++++---- OpenUtau/Views/PianoRollWindow.axaml.cs | 78 ++++++++++++++------ 6 files changed, 136 insertions(+), 53 deletions(-) diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index e8d50d90c..2232e0ff1 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -2,6 +2,7 @@ xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + Delete Ease in Ease in/out Ease out @@ -183,7 +184,11 @@ Warning: this option removes custom presets. Knife Tool (5) Pen Tool (2) Left click to draw -Right click to delete +Hold Ctrl to select + Pen Plus Tool (Ctrl + 2) +Left click to draw +Right click to delete +Hold Ctrl to select Selection Tool (1) Hold Ctrl to select more diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 7643e800f..73cf38ba4 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -45,7 +45,8 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber { [Reactive] public double PlayPosHighlightX { get; set; } [Reactive] public bool PlayPosWaitingRendering { get; set; } [Reactive] public bool CursorTool { get; set; } - [Reactive] public bool PencilTool { get; set; } + [Reactive] public bool PenTool { get; set; } + [Reactive] public bool PenPlusTool { get; set; } [Reactive] public bool EraserTool { get; set; } [Reactive] public bool DrawPitchTool { get; set; } [Reactive] public bool KnifeTool { get; set; } @@ -171,13 +172,15 @@ public NotesViewModel() { }); CursorTool = false; - PencilTool = true; + PenTool = true; + PenPlusTool = false; EraserTool = false; DrawPitchTool = false; KnifeTool = false; SelectToolCommand = ReactiveCommand.Create(index => { CursorTool = index == "1"; - PencilTool = index == "2"; + PenTool = index == "2"; + PenPlusTool = index == "2+"; EraserTool = index == "3"; DrawPitchTool = index == "4"; KnifeTool = index == "5"; diff --git a/OpenUtau/ViewModels/NotesViewModelHitTest.cs b/OpenUtau/ViewModels/NotesViewModelHitTest.cs index ab5bbb65f..99518ef0e 100644 --- a/OpenUtau/ViewModels/NotesViewModelHitTest.cs +++ b/OpenUtau/ViewModels/NotesViewModelHitTest.cs @@ -24,16 +24,6 @@ public struct PitchPointHitInfo { public bool OnPoint; public float X; public float Y; - public bool IsFirst { get; set; } - public bool CanDel { get; set; } - public bool CanAdd { get; set; } - public ReactiveCommand EaseInOutCommand { get; set; } - public ReactiveCommand LinearCommand { get; set; } - public ReactiveCommand EaseInCommand { get; set; } - public ReactiveCommand EaseOutCommand { get; set; } - public ReactiveCommand SnapCommand { get; set; } - public ReactiveCommand DelCommand { get; set; } - public ReactiveCommand AddCommand { get; set; } } public struct VibratoHitInfo { diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index b983f62c1..1432761eb 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -20,6 +20,25 @@ public PhonemeMouseoverEvent(UPhoneme? mouseoverPhoneme) { } } + public class NotesContextMenuArgs { + public bool ForNote { get; set; } + public NoteHitInfo NoteHitInfo { get; set; } + public ReactiveCommand? NoteDeleteCommand { get; set; } + + public bool ForPitchPoint { get; set; } + public bool PitchPointIsFirst { get; set; } + public bool PitchPointCanDel { get; set; } + public bool PitchPointCanAdd { get; set; } + public PitchPointHitInfo PitchPointHitInfo { get; set; } + public ReactiveCommand? PitchPointEaseInOutCommand { get; set; } + public ReactiveCommand? PitchPointLinearCommand { get; set; } + public ReactiveCommand? PitchPointEaseInCommand { get; set; } + public ReactiveCommand? PitchPointEaseOutCommand { get; set; } + public ReactiveCommand? PitchPointSnapCommand { get; set; } + public ReactiveCommand? PitchPointDelCommand { get; set; } + public ReactiveCommand? PitchPointAddCommand { get; set; } + } + public class PianoRollViewModel : ViewModelBase, ICmdSubscriber { public bool ExtendToFrame => OS.IsMacOS(); @@ -30,6 +49,7 @@ public class PianoRollViewModel : ViewModelBase, ICmdSubscriber { [Reactive] public List NoteBatchEdits { get; set; } [Reactive] public List LyricBatchEdits { get; set; } [Reactive] public double Progress { get; set; } + public ReactiveCommand NoteDeleteCommand { get; set; } public ReactiveCommand PitEaseInOutCommand { get; set; } public ReactiveCommand PitLinearCommand { get; set; } public ReactiveCommand PitEaseInCommand { get; set; } @@ -44,6 +64,9 @@ public class PianoRollViewModel : ViewModelBase, ICmdSubscriber { public PianoRollViewModel() { NotesViewModel = new NotesViewModel(); + NoteDeleteCommand = ReactiveCommand.Create(info => { + NotesViewModel.DeleteSelectedNotes(); + }); PitEaseInOutCommand = ReactiveCommand.Create(info => { DocManager.Inst.StartUndoGroup(); DocManager.Inst.ExecuteCmd(new ChangePitchPointShapeCommand(NotesViewModel.Part, info.Note.pitch.data[info.Index], PitchPointShape.io)); diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index 08c0a7fb9..b97d9b323 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -141,21 +141,41 @@ Fill="{DynamicResource NeutralAccentBrushSemi2}" IsHitTestVisible="False"/> - + + + IsVisible="{Binding ForPitchPoint}" + Command="{Binding PitchPointEaseInOutCommand}" + CommandParameter="{Binding PitchPointHitInfo}"/> + IsVisible="{Binding ForPitchPoint}" + Command="{Binding PitchPointLinearCommand}" + CommandParameter="{Binding PitchPointHitInfo}"/> + IsVisible="{Binding ForPitchPoint}" + Command="{Binding PitchPointEaseInCommand}" + CommandParameter="{Binding PitchPointHitInfo}"/> - - - + IsVisible="{Binding ForPitchPoint}" + Command="{Binding PitchPointEaseOutCommand}" + CommandParameter="{Binding PitchPointHitInfo}"/> + + + @@ -181,7 +201,7 @@ @@ -195,9 +215,21 @@ - - - + + + + + + + + + + + + ("ValueTipText"); lyricBox = this.FindControl("LyricBox"); - pitchContextMenu = this.FindControl("PitchContextMenu"); + notesContextMenu = this.FindControl("NotesContextMenu"); expSelector1 = this.FindControl("expSelector1"); expSelector2 = this.FindControl("expSelector2"); expSelector3 = this.FindControl("expSelector3"); @@ -303,7 +304,8 @@ private void NotesCanvasLeftPointerPressed(Canvas canvas, PointerPoint point, Po return; } if (ViewModel.NotesViewModel.CursorTool || - ViewModel.NotesViewModel.PencilTool && args.KeyModifiers == cmdKey) { + ViewModel.NotesViewModel.PenTool && args.KeyModifiers == cmdKey || + ViewModel.NotesViewModel.PenPlusTool && args.KeyModifiers == cmdKey) { if (args.KeyModifiers == KeyModifiers.None) { // New selection. ViewModel.NotesViewModel.DeselectNotes(); @@ -318,13 +320,15 @@ private void NotesCanvasLeftPointerPressed(Canvas canvas, PointerPoint point, Po return; } ViewModel.NotesViewModel.DeselectNotes(); - } else if (ViewModel.NotesViewModel.PencilTool) { + } else if (ViewModel.NotesViewModel.PenTool || + ViewModel.NotesViewModel.PenPlusTool) { ViewModel.NotesViewModel.DeselectNotes(); editState = new NoteDrawEditState(canvas, ViewModel, this, ViewModel.NotesViewModel.PlayTone); } } private void NotesCanvasRightPointerPressed(Canvas canvas, PointerPoint point, PointerPressedEventArgs args) { + var selectedNotes = new List(ViewModel.NotesViewModel.SelectedNotes); ViewModel.NotesViewModel.DeselectNotes(); if (ViewModel.NotesViewModel.DrawPitchTool) { editState = new ResetPitchState(canvas, ViewModel, this); @@ -332,25 +336,44 @@ private void NotesCanvasRightPointerPressed(Canvas canvas, PointerPoint point, P } if (ViewModel.NotesViewModel.ShowPitch) { var pitHitInfo = ViewModel.NotesViewModel.HitTest.HitTestPitchPoint(point.Position); - if (pitHitInfo.Note != null && pitchContextMenu != null) { - pitHitInfo.IsFirst = pitHitInfo.OnPoint && pitHitInfo.Index == 0; - pitHitInfo.CanDel = pitHitInfo.OnPoint && pitHitInfo.Index != 0 - && pitHitInfo.Index != pitHitInfo.Note.pitch.data.Count - 1; - pitHitInfo.CanAdd = !pitHitInfo.OnPoint; - pitHitInfo.EaseInOutCommand = ViewModel.PitEaseInOutCommand; - pitHitInfo.LinearCommand = ViewModel.PitLinearCommand; - pitHitInfo.EaseInCommand = ViewModel.PitEaseInCommand; - pitHitInfo.EaseOutCommand = ViewModel.PitEaseOutCommand; - pitHitInfo.SnapCommand = ViewModel.PitSnapCommand; - pitHitInfo.AddCommand = ViewModel.PitAddCommand; - pitHitInfo.DelCommand = ViewModel.PitDelCommand; - pitchContextMenu.DataContext = pitHitInfo; - openingPitchContextMenu = true; + if (pitHitInfo.Note != null && notesContextMenu != null) { + notesContextMenu.DataContext = new NotesContextMenuArgs { + ForPitchPoint = true, + PitchPointIsFirst = pitHitInfo.OnPoint && pitHitInfo.Index == 0, + PitchPointCanDel = pitHitInfo.OnPoint && pitHitInfo.Index != 0 + && pitHitInfo.Index != pitHitInfo.Note.pitch.data.Count - 1, + PitchPointCanAdd = !pitHitInfo.OnPoint, + PitchPointHitInfo = pitHitInfo, + PitchPointEaseInOutCommand = ViewModel.PitEaseInOutCommand, + PitchPointLinearCommand = ViewModel.PitLinearCommand, + PitchPointEaseInCommand = ViewModel.PitEaseInCommand, + PitchPointEaseOutCommand = ViewModel.PitEaseOutCommand, + PitchPointSnapCommand = ViewModel.PitSnapCommand, + PitchPointAddCommand = ViewModel.PitAddCommand, + PitchPointDelCommand = ViewModel.PitDelCommand, + }; + shouldOpenNotesContextMenu = true; return; } } - if (ViewModel.NotesViewModel.PencilTool || ViewModel.NotesViewModel.EraserTool) { - ViewModel.NotesViewModel.DeselectNotes(); + if (ViewModel.NotesViewModel.CursorTool || ViewModel.NotesViewModel.PenTool) { + var hitInfo = ViewModel.NotesViewModel.HitTest.HitTestNote(point.Position); + if (hitInfo.hitBody) { + if (selectedNotes.Contains(hitInfo.note)) { + ViewModel.NotesViewModel.SelectedNotes.AddRange(selectedNotes); + } + ViewModel.NotesViewModel.SelectNote(hitInfo.note); + } + if (ViewModel.NotesViewModel.SelectedNotes.Count > 0) { + if (notesContextMenu != null) { + notesContextMenu.DataContext = new NotesContextMenuArgs { + ForNote = true, + NoteDeleteCommand = ViewModel.NoteDeleteCommand, + }; + shouldOpenNotesContextMenu = true; + } + } + } else if (ViewModel.NotesViewModel.EraserTool || ViewModel.NotesViewModel.PenPlusTool) { editState = new NoteEraseEditState(canvas, ViewModel, this, MouseButton.Right); Cursor = ViewConstants.cursorNo; } @@ -468,14 +491,20 @@ public void NotesCanvasPointerWheelChanged(object sender, PointerWheelEventArgs } } - public void PitchContextMenuOpening(object sender, CancelEventArgs args) { - if (openingPitchContextMenu) { - openingPitchContextMenu = false; + public void NotesContextMenuOpening(object sender, CancelEventArgs args) { + if (shouldOpenNotesContextMenu) { + shouldOpenNotesContextMenu = false; } else { args.Cancel = true; } } + public void NotesContextMenuClosing(object sender, CancelEventArgs args) { + if (notesContextMenu != null) { + notesContextMenu.DataContext = null; + } + } + public void ExpCanvasPointerPressed(object sender, PointerPressedEventArgs args) { lyricBox?.EndEdit(); if (ViewModel.NotesViewModel.Part == null) { @@ -723,6 +752,7 @@ void OnKeyDown(object sender, KeyEventArgs args) { } } else if (args.KeyModifiers == cmdKey) { switch (args.Key) { + case Key.D2: notesVm.SelectToolCommand?.Execute("2+").Subscribe(); args.Handled = true; break; case Key.A: notesVm.SelectAllNotes(); args.Handled = true; break; case Key.S: _ = MainWindow?.Save(); args.Handled = true; break; case Key.Z: ViewModel.Undo(); args.Handled = true; break; From 2f3bff35432b682755ff20c8710c8a27859e303b Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Fri, 19 Aug 2022 23:58:45 -0700 Subject: [PATCH 012/321] use home key and end key to navigate in both main window and piano roll window --- OpenUtau/Views/MainWindow.axaml | 2 +- OpenUtau/Views/MainWindow.axaml.cs | 11 +++++++++++ OpenUtau/Views/PianoRollWindow.axaml.cs | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index 27ecc5631..f73a0110c 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -17,7 +17,7 @@ - + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 18dfc0f08..c77d18fe9 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -142,6 +142,10 @@ void OnMainMenuOpened(object sender, RoutedEventArgs args) { viewModel.RefreshCacheSize(); } + void OnMainMenuClosed(object sender, RoutedEventArgs args) { + this.Focus(); + } + void OnMenuOpenProjectLocation(object sender, RoutedEventArgs args) { var project = DocManager.Inst.Project; if (string.IsNullOrEmpty(project.FilePath) || !project.Saved) { @@ -498,6 +502,13 @@ void OnKeyDown(object sender, KeyEventArgs args) { switch (args.Key) { case Key.Delete: viewModel.TracksViewModel.DeleteSelectedParts(); break; case Key.Space: PlayOrPause(); break; + case Key.Home: viewModel.PlaybackViewModel.MovePlayPos(0); break; + case Key.End: + if (viewModel.TracksViewModel.Parts.Count > 0) { + int endTick = viewModel.TracksViewModel.Parts.Max(part => part.EndTick); + viewModel.PlaybackViewModel.MovePlayPos(endTick); + } + break; default: break; } } else if (args.KeyModifiers == KeyModifiers.Alt) { diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index 36b58e06d..b40804beb 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -748,6 +748,16 @@ void OnKeyDown(object sender, KeyEventArgs args) { } args.Handled = true; break; + case Key.Home: + if (ViewModel.NotesViewModel.Part != null) { + ViewModel.PlaybackViewModel?.MovePlayPos(ViewModel.NotesViewModel.Part.position); + } + break; + case Key.End: + if (ViewModel.NotesViewModel.Part != null) { + ViewModel.PlaybackViewModel?.MovePlayPos(ViewModel.NotesViewModel.Part.EndTick); + } + break; default: break; } } else if (args.KeyModifiers == cmdKey) { From 82a9385acfb3f3e7155ac43b05023655615dbb22 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Sat, 20 Aug 2022 00:08:19 -0700 Subject: [PATCH 013/321] fix menu unfocusing --- OpenUtau/Views/MainWindow.axaml | 3 ++- OpenUtau/Views/MainWindow.axaml.cs | 6 +++++- OpenUtau/Views/PianoRollWindow.axaml | 3 ++- OpenUtau/Views/PianoRollWindow.axaml.cs | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index f73a0110c..1c88bfab4 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -17,7 +17,8 @@ - + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index c77d18fe9..1f8c55af2 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -143,7 +143,11 @@ void OnMainMenuOpened(object sender, RoutedEventArgs args) { } void OnMainMenuClosed(object sender, RoutedEventArgs args) { - this.Focus(); + Focus(); // Force unfocus menu for key down events. + } + + void OnMainMenuPointerLeave(object sender, PointerEventArgs args) { + Focus(); // Force unfocus menu for key down events. } void OnMenuOpenProjectLocation(object sender, RoutedEventArgs args) { diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index b97d9b323..c74230c0a 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -372,7 +372,8 @@ - + diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index b40804beb..8341b24c6 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -74,6 +74,14 @@ void WindowClosing(object? sender, CancelEventArgs e) { e.Cancel = true; } + void OnMenuClosed(object sender, RoutedEventArgs args) { + Focus(); // Force unfocus menu for key down events. + } + + void OnMenuPointerLeave(object sender, PointerEventArgs args) { + Focus(); // Force unfocus menu for key down events. + } + void OnMenuRenamePart(object? sender, RoutedEventArgs e) { var part = ViewModel.NotesViewModel.Part; if (part == null) { From 0c9ae859fa25e1d54041a1f945f8c558f4b3fbf0 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Sat, 20 Aug 2022 01:13:33 -0700 Subject: [PATCH 014/321] part context menu and fix some issues --- OpenUtau/Strings/Strings.axaml | 5 +- OpenUtau/ViewModels/MainWindowViewModel.cs | 11 ++++ OpenUtau/ViewModels/NotesViewModel.cs | 13 +++++ OpenUtau/ViewModels/PianoRollViewModel.cs | 8 --- OpenUtau/ViewModels/TracksViewModel.cs | 5 +- OpenUtau/Views/MainWindow.axaml | 13 +++++ OpenUtau/Views/MainWindow.axaml.cs | 65 +++++++++++++++++++--- OpenUtau/Views/PianoRollWindow.axaml | 1 - OpenUtau/Views/PianoRollWindow.axaml.cs | 16 ------ 9 files changed, 100 insertions(+), 37 deletions(-) diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 2232e0ff1..b2b106f2d 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -2,7 +2,9 @@ xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - Delete + Delete note + Delete part + Rename part Ease in Ease in/out Ease out @@ -168,7 +170,6 @@ Warning: this option removes custom presets. Reset vibratos Part Legacy Plugin (Experimental) - Rename View Final Pitch to Render (R) View Phonemes (O) View Pitch Bend (I) diff --git a/OpenUtau/ViewModels/MainWindowViewModel.cs b/OpenUtau/ViewModels/MainWindowViewModel.cs index b8b64992c..241102a8c 100644 --- a/OpenUtau/ViewModels/MainWindowViewModel.cs +++ b/OpenUtau/ViewModels/MainWindowViewModel.cs @@ -12,6 +12,13 @@ using Serilog; namespace OpenUtau.App.ViewModels { + public class PartsContextMenuArgs { + public UPart? Part { get; set; } + public bool IsVoicePart => Part is UVoicePart; + public ReactiveCommand? PartDeleteCommand { get; set; } + public ReactiveCommand? PartRenameCommand { get; set; } + } + public class MainWindowViewModel : ViewModelBase, ICmdSubscriber { public bool ExtendToFrame => OS.IsMacOS(); public string Title => !ProjectSaved @@ -29,6 +36,7 @@ public class MainWindowViewModel : ViewModelBase, ICmdSubscriber { public string AppVersion => $"OpenUtau v{System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version}"; [Reactive] public double Progress { get; set; } [Reactive] public string ProgressText { get; set; } + public ReactiveCommand PartDeleteCommand { get; set; } private ObservableCollectionExtended openRecent = new ObservableCollectionExtended(); @@ -46,6 +54,9 @@ public MainWindowViewModel() { DocManager.Inst.Project.Saved = false; DocManager.Inst.Project.FilePath = null; }); + PartDeleteCommand = ReactiveCommand.Create(part => { + TracksViewModel.DeleteSelectedParts(); + }); DocManager.Inst.AddSubscriber(this); } diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 73cf38ba4..8261765f7 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -443,6 +443,14 @@ public void CommitTempSelectNotes() { SelectedNotes.ToArray(), TempSelectedNotes.ToArray())); } + public void CleanupSelectedNotes() { + if (Part == null) { + return; + } + var except = SelectedNotes.Except(Part.notes).ToHashSet(); + SelectedNotes.RemoveAll(note => except.Contains(note)); + } + public void TransposeSelection(int deltaNoteNum) { if (SelectedNotes.Count <= 0) { return; @@ -604,12 +612,17 @@ public void OnNext(UCommand cmd, bool isUndo) { if (!isUndo) { UnloadPart(); } + } else if (cmd is AddPartCommand addPart) { + if (isUndo && addPart.part == Part) { + UnloadPart(); + } } else if (cmd is ResizePartCommand) { OnPartModified(); } else if (cmd is MovePartCommand) { OnPartModified(); } } else if (cmd is NoteCommand noteCommand) { + CleanupSelectedNotes(); if (noteCommand.Part == Part) { MessageBus.Current.SendMessage(new NotesRefreshEvent()); } diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 1432761eb..c338f9e1b 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -184,14 +184,6 @@ public PianoRollViewModel() { public void Undo() => DocManager.Inst.Undo(); public void Redo() => DocManager.Inst.Redo(); - public void RenamePart(UVoicePart part, string name) { - if (!string.IsNullOrWhiteSpace(name) && name != part.name) { - DocManager.Inst.StartUndoGroup(); - DocManager.Inst.ExecuteCmd(new RenamePartCommand(DocManager.Inst.Project, part, name)); - DocManager.Inst.EndUndoGroup(); - } - } - public void MouseoverPhoneme(UPhoneme? phoneme) { MessageBus.Current.SendMessage(new PhonemeMouseoverEvent(phoneme)); } diff --git a/OpenUtau/ViewModels/TracksViewModel.cs b/OpenUtau/ViewModels/TracksViewModel.cs index c06f77f2d..617ca2117 100644 --- a/OpenUtau/ViewModels/TracksViewModel.cs +++ b/OpenUtau/ViewModels/TracksViewModel.cs @@ -267,7 +267,8 @@ public void DeleteSelectedParts() { return; } DocManager.Inst.StartUndoGroup(); - foreach (var part in SelectedParts) { + var selectedParts = SelectedParts.ToArray(); + foreach (var part in selectedParts) { DocManager.Inst.ExecuteCmd(new RemovePartCommand(Project, part)); } DocManager.Inst.EndUndoGroup(); @@ -345,10 +346,12 @@ public void OnNext(UCommand cmd, bool isUndo) { Parts.Add(partCommand.part); } else { Parts.Remove(partCommand.part); + SelectedParts.Remove(partCommand.part); } } else if (partCommand is RemovePartCommand) { if (!isUndo) { Parts.Remove(partCommand.part); + SelectedParts.Remove(partCommand.part); } else { Parts.Add(partCommand.part); } diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index 1c88bfab4..0321e11e4 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -189,6 +189,19 @@ Canvas.Top="0" Fill="{DynamicResource NeutralAccentBrushSemi2}" IsHitTestVisible="False"/> + + + + + + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 1f8c55af2..53368d2d5 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Reactive; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -19,6 +20,7 @@ using OpenUtau.Core; using OpenUtau.Core.Format; using OpenUtau.Core.Ustx; +using ReactiveUI; using Serilog; using Point = Avalonia.Point; @@ -37,9 +39,15 @@ public partial class MainWindow : Window, ICmdSubscriber { private DispatcherTimer autosaveTimer; private bool forceClose; + private ContextMenu? partsContextMenu; + private bool shouldOpenPartsContextMenu; + + private readonly ReactiveCommand PartRenameCommand; + public MainWindow() { InitializeComponent(); DataContext = viewModel = new MainWindowViewModel(); + partsContextMenu = this.Find("PartsContextMenu"); #if DEBUG this.AttachDevTools(); #endif @@ -56,6 +64,8 @@ public MainWindow() { (sender, args) => DocManager.Inst.AutoSave()); autosaveTimer.Start(); + PartRenameCommand = ReactiveCommand.Create(part => RenamePart(part)); + AddHandler(DragDrop.DropEvent, OnDrop); DocManager.Inst.AddSubscriber(this); @@ -670,12 +680,6 @@ public void PartsCanvasPointerPressed(object sender, PointerPressedEventArgs arg } if (point.Properties.IsLeftButtonPressed) { if (args.KeyModifiers == cmdKey) { - // New selection. - viewModel.TracksViewModel.DeselectParts(); - partEditState = new PartSelectionEditState(canvas, viewModel, GetSelectionBox(canvas)); - Cursor = ViewConstants.cursorCross; - } else if (args.KeyModifiers == (cmdKey | KeyModifiers.Shift)) { - // Additional selection. partEditState = new PartSelectionEditState(canvas, viewModel, GetSelectionBox(canvas)); Cursor = ViewConstants.cursorCross; } else if (control == canvas) { @@ -704,9 +708,22 @@ public void PartsCanvasPointerPressed(object sender, PointerPressedEventArgs arg } } } else if (point.Properties.IsRightButtonPressed) { - viewModel.TracksViewModel.DeselectParts(); - partEditState = new PartEraseEditState(canvas, viewModel); - Cursor = ViewConstants.cursorNo; + if (control is PartControl partControl) { + if (!viewModel.TracksViewModel.SelectedParts.Contains(partControl.part)) { + viewModel.TracksViewModel.DeselectParts(); + viewModel.TracksViewModel.SelectPart(partControl.part); + } + if (partsContextMenu != null && viewModel.TracksViewModel.SelectedParts.Count > 0) { + partsContextMenu.DataContext = new PartsContextMenuArgs { + Part = partControl.part, + PartDeleteCommand = viewModel.PartDeleteCommand, + PartRenameCommand = PartRenameCommand, + }; + shouldOpenPartsContextMenu = true; + } + } else { + viewModel.TracksViewModel.DeselectParts(); + } } else if (point.Properties.IsMiddleButtonPressed) { partEditState = new PartPanningState(canvas, viewModel); Cursor = ViewConstants.cursorHand; @@ -819,6 +836,36 @@ public void PartsCanvasPointerWheelChanged(object sender, PointerWheelEventArgs } } + public void PartsContextMenuOpening(object sender, CancelEventArgs args) { + if (shouldOpenPartsContextMenu) { + shouldOpenPartsContextMenu = false; + } else { + args.Cancel = true; + } + } + + public void PartsContextMenuClosing(object sender, CancelEventArgs args) { + if (partsContextMenu != null) { + partsContextMenu.DataContext = null; + } + } + + void RenamePart(UPart part) { + var dialog = new TypeInDialog(); + dialog.Title = ThemeManager.GetString("context.part.rename"); + dialog.SetText(part.name); + dialog.onFinish = name => { + if (!string.IsNullOrWhiteSpace(name) && name != part.name) { + if (!string.IsNullOrWhiteSpace(name) && name != part.name) { + DocManager.Inst.StartUndoGroup(); + DocManager.Inst.ExecuteCmd(new RenamePartCommand(DocManager.Inst.Project, part, name)); + DocManager.Inst.EndUndoGroup(); + } + } + }; + dialog.ShowDialog(this); + } + public async void WindowClosing(object? sender, CancelEventArgs e) { if (forceClose || DocManager.Inst.ChangesSaved) { return; diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index c74230c0a..8eb9635c2 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -375,7 +375,6 @@ - diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index 8341b24c6..ca95f5c07 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -82,22 +82,6 @@ void OnMenuPointerLeave(object sender, PointerEventArgs args) { Focus(); // Force unfocus menu for key down events. } - void OnMenuRenamePart(object? sender, RoutedEventArgs e) { - var part = ViewModel.NotesViewModel.Part; - if (part == null) { - return; - } - var dialog = new TypeInDialog(); - dialog.Title = "Rename"; - dialog.SetText(part.name); - dialog.onFinish = name => { - if (!string.IsNullOrWhiteSpace(name) && name != part.name) { - ViewModel.RenamePart(part, name); - } - }; - dialog.ShowDialog(this); - } - void OnMenuEditLyrics(object? sender, RoutedEventArgs e) { if (ViewModel.NotesViewModel.SelectedNotes.Count == 0) { _ = MessageBox.Show( From 08fd4060dc639df1df4cbc7765a4533616441596 Mon Sep 17 00:00:00 2001 From: OvDM - HAI-D <68165064+overdramatic@users.noreply.github.com> Date: Sat, 20 Aug 2022 08:42:32 -0300 Subject: [PATCH 015/321] Update Strings.pt-BR.axaml --- OpenUtau/Strings/Strings.pt-BR.axaml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/OpenUtau/Strings/Strings.pt-BR.axaml b/OpenUtau/Strings/Strings.pt-BR.axaml index 732f8c297..1a74089f2 100644 --- a/OpenUtau/Strings/Strings.pt-BR.axaml +++ b/OpenUtau/Strings/Strings.pt-BR.axaml @@ -2,6 +2,9 @@ xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + Remover nota + Deletar parte + Renomear parte Curva J Curva S Curva R @@ -139,6 +142,11 @@ Aviso: esta opção remove predefinições personalizadas. Pasta Sufixo + Definir Consonant + Definir Cutoff + Definir Offset + Definir Overlap + Definir Preutter Lírica Trocar "-" por "+" @@ -162,11 +170,12 @@ Aviso: esta opção remove predefinições personalizadas. Redefinir Vibratos Parte Plugin Legado (Experimental) - Renomear Ver Tom Final para Renderizar (R) Ver Fonemas (O) Ver Curva de Tom (I) Ativar Ajuste (P) + Auto + Auto (triplet) Ver Dicas (T) Ativar Tom de Nota (Y) Ver Vibrato (U) @@ -176,7 +185,11 @@ Aviso: esta opção remove predefinições personalizadas. Ferramenta Faca (5) Ferramenta Caneta (2) Botão esquerdo para desenhar -Botão direito para apagar +Botão direito na nota para apagar + Ferramenta Caneta+ (Ctrl + 2) +Botão esquerdo para desenhar +Botão direito e arraste para apagar +Segure Ctrl para selecionar Ferramenta Seleção (1) Segure Ctrl para selecionar mais notas @@ -233,6 +246,8 @@ Segure Ctrl para selecionar mais notas Esperando Renderização Vozes + Redefinir Otos + Salvar Otos Pasta Mover para Esquerda Mover para Direita From 48d7909037730f6f2a6cef628899c0af0125217d Mon Sep 17 00:00:00 2001 From: OvDM - HAI-D <68165064+overdramatic@users.noreply.github.com> Date: Sat, 20 Aug 2022 09:01:13 -0300 Subject: [PATCH 016/321] Update Strings.pt-BR.axaml --- OpenUtau/Strings/Strings.pt-BR.axaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenUtau/Strings/Strings.pt-BR.axaml b/OpenUtau/Strings/Strings.pt-BR.axaml index 1a74089f2..4c67a2ac3 100644 --- a/OpenUtau/Strings/Strings.pt-BR.axaml +++ b/OpenUtau/Strings/Strings.pt-BR.axaml @@ -190,8 +190,7 @@ Botão direito na nota para apagar Botão esquerdo para desenhar Botão direito e arraste para apagar Segure Ctrl para selecionar - Ferramenta Seleção (1) -Segure Ctrl para selecionar mais notas + Ferramenta Seleção (1) Avançado From 6326face5d2a830d9a2aa698b5531739e31a3761 Mon Sep 17 00:00:00 2001 From: IDOLTRASH Date: Sat, 20 Aug 2022 16:21:32 +0100 Subject: [PATCH 017/321] Update Strings.it-IT.axaml --- OpenUtau/Strings/Strings.it-IT.axaml | 60 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/OpenUtau/Strings/Strings.it-IT.axaml b/OpenUtau/Strings/Strings.it-IT.axaml index 7c8a32621..21b47e6cb 100644 --- a/OpenUtau/Strings/Strings.it-IT.axaml +++ b/OpenUtau/Strings/Strings.it-IT.axaml @@ -23,8 +23,8 @@ Esporta Salva prima il file di progetto Cancella - - + No + Ok Si Nessun resampler Nessun resampler! Aggiungi il file DLL o EXE del resampler nella cartella Resampler e scegli nelle Preferenze! @@ -38,8 +38,8 @@ Predefinito Flag Resampler È Flag Resampler - - + Max + Min Nome Opzione Valori Separa valori con ',' @@ -52,7 +52,7 @@ Cancella Modifica Testo Anteprima in tempo reale - + Max Resetta Seleziona note prima di questa operazione Separatori @@ -65,12 +65,12 @@ Ripristina Seleziona tutto Annulla - + File Esci Esporta File Ust - Esporta i File Ust in... + Esporta File Ust in... Esporta File Wav - Esporta i File Wav in... + Esporta File Wav in... Importa Audio... Importa MIDI... Importa Tracce... @@ -105,10 +105,10 @@ Testo Testo di default - + Portamento Lunghezza Inizio - + Preset Nome del preset Rimuovi Rimuove l'ultimo preset utilizzato. @@ -117,22 +117,22 @@ Resetta Impostazioni Ripristina tutte le impostazioni ai valori di default. Attenzione: questa opzione rimuove tutti i preset salvati. - + Vibrato Lunghezza Minima Auto Vibrato per Lunghezza Profondità - + Fade In Lunghezza - + Fade Out Periodo Spostamento - + Alias Colore Consonante Taglio - - + File + Offset Coincidenza Fonemi Prefisso @@ -158,12 +158,12 @@ Attenzione: questa opzione rimuove tutti i preset salvati. Quantizza a 1/64 Resetta tutte le espressioni Resetta tutti i tempi dei fonemi - Resetta tutti i pitch bends + Resetta tutti i pitch bend Resetta tutti i vibrati Parte Legacy Plugin (Sperimentale) Rinomina - Mostra i Pitch Bend Finale Renderizzato (R) + Mostra il Pitch Bend Finale Renderizzato (R) Mostra Fonemi (O) Mostra Pitch Bend (I) Attiva/Disattiva Snap (P) @@ -181,7 +181,7 @@ Click destro per eliminare Tieni premuto click sinistro + Ctrl per selezionare più note Avanzato - + Beta Log del Resampler Memorizza l'output del resampler in un file log. Questa operazione può rallentare la UI e il resampler. Stabile @@ -192,12 +192,12 @@ Tieni premuto click sinistro + Ctrl per selezionare più note Scuro Chiaro Preferenze - Nota: riavvia OpenUtau dopo aver cambiato questo elemento. - - + Nota: riavvia OpenUtau dopo aver modificato questo elemento. + Off + On Percorsi Percorso alternativo per i cantanti - Install nel percorso alternativo per i cantanti + Installa nel percorso alternativo per i cantanti Resetta Seleziona Audio @@ -207,18 +207,18 @@ Tieni premuto click sinistro + Ctrl per selezionare più note Cursore Immobile Backend Audio Automatico - + PortAudio Posizione del cursore Dispositivo audio Pausa alla posizione corrente Off Muovi cursore alla posizione di riproduzione originale - + Test Renderizzazione Compensazione di Fase - + Pre-renderizzazione Threads Pre-renderizzazione - + Resampler Attenzione: moresampler non è pienamente supportato. Potrebbe essere lento e causare alto utilizzo della CPU. Se vuoi: 1. Imposta "resampler-compatibility" su "on" nel file moreconfig.txt. @@ -294,12 +294,12 @@ Tieni premuto click destro: Resetta espressioni Seleziona Renderer Seleziona Cantante - - + Segoe UI,San Francisco,Helvetica Neue + Microsoft YaHei,Simsun,苹方-简,宋体-简 Controlla aggiornamenti Piattaforma di sintetizzazione vocale open source - + GitHub Aggiornamento v{0} disponibile! Controllo aggiornamenti... Aggiornato From fe2401498e36f737d3f2d11dfd4cb8c382b29b9b Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Sat, 20 Aug 2022 20:32:55 -0700 Subject: [PATCH 018/321] upgrade packages and replaces NChardet with UTF.Unknown for netstandard --- OpenUtau.Core/Format/Midi.cs | 73 ++++++-------------- OpenUtau.Core/OpenUtau.Core.csproj | 12 ++-- OpenUtau.Test/Classic/VoicebankConfigTest.cs | 3 +- OpenUtau.Test/OpenUtau.Test.csproj | 1 - OpenUtau/OpenUtau.csproj | 6 +- 5 files changed, 34 insertions(+), 61 deletions(-) diff --git a/OpenUtau.Core/Format/Midi.cs b/OpenUtau.Core/Format/Midi.cs index 0f5af641d..2360ba812 100644 --- a/OpenUtau.Core/Format/Midi.cs +++ b/OpenUtau.Core/Format/Midi.cs @@ -1,27 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Text; -using System.Threading.Tasks; - using NAudio.Midi; -using NChardet; - +using UtfUnknown; using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; +using System.IO; -namespace OpenUtau.Core.Format -{ - public class MyCharsetDetectionObserver: NChardet.ICharsetDetectionObserver - { - public string Charset = null; - - public void Notify(string charset) { - Charset = charset; - } - } - public static class Midi - { +namespace OpenUtau.Core.Format { + public static class Midi { static public UProject LoadProject(string file) { UProject uproject = new UProject(); Ustx.AddDefaultExpressions(uproject); @@ -39,35 +25,27 @@ static public UProject LoadProject(string file) { } return uproject; } - static public List Load(string file, UProject project) - { + static public List Load(string file, UProject project) { List resultParts = new List(); MidiFile midi = new MidiFile(file); string lyric = NotePresets.Default.DefaultLyric; - for (int i = 0; i < midi.Tracks; i++) - { - //detect lyric encoding - Detector det = new Detector(6); - MyCharsetDetectionObserver cdo = new MyCharsetDetectionObserver(); - det.Init(cdo); - bool done = false; - foreach (var e in midi.Events.GetTrackEvents(i)) { - if (e is TextEvent) { //Lyric event - var _e = e as TextEvent; - if (_e.MetaEventType == MetaEventType.Lyric) { - done = det.DoIt(_e.Data, _e.Data.Length, false); - if (done) { - break; - } + // Detects lyric encoding + Encoding lyricEncoding = Encoding.UTF8; + using (var stream = new MemoryStream()) { + for (int i = 0; i < midi.Tracks; i++) { + foreach (var e in midi.Events.GetTrackEvents(i)) { + if (e is TextEvent te && te.MetaEventType == MetaEventType.Lyric) { + stream.Write(te.Data); } } } - det.DataEnd(); - string Charset = cdo.Charset; - Encoding lyricEncoding = Encoding.UTF8; - if (Charset!=null){ - lyricEncoding = Encoding.GetEncoding(Charset); + stream.Seek(0, SeekOrigin.Begin); + var detectionResult = CharsetDetector.DetectFromStream(stream); + if (detectionResult.Detected.Confidence > 0.5) { + lyricEncoding = detectionResult.Detected.Encoding; } + } + for (int i = 0; i < midi.Tracks; i++) { Dictionary parts = new Dictionary(); foreach (var e in midi.Events.GetTrackEvents(i)) { if (e is NoteOnEvent) { @@ -80,23 +58,18 @@ static public List Load(string file, UProject project) _e.NoteNumber, (int)_e.AbsoluteTime * project.resolution / midi.DeltaTicksPerQuarterNote, _e.NoteLength * project.resolution / midi.DeltaTicksPerQuarterNote); - if(lyric=="-") { + if (lyric == "-") { lyric = "+"; } note.lyric = lyric; if (NotePresets.Default.AutoVibratoToggle && note.duration >= NotePresets.Default.AutoVibratoNoteDuration) note.vibrato.length = NotePresets.Default.DefaultVibrato.VibratoLength; parts[e.Channel].notes.Add(note); lyric = NotePresets.Default.DefaultLyric; - } - else if (e is TextEvent) { //Lyric event - var _e = e as TextEvent; - if(_e.MetaEventType == MetaEventType.Lyric) { - lyric = lyricEncoding.GetString(_e.Data); - } + } else if (e is TextEvent te && te.MetaEventType == MetaEventType.Lyric) { + lyric = lyricEncoding.GetString(te.Data); } } - foreach (var pair in parts) - { + foreach (var pair in parts) { pair.Value.Duration = pair.Value.GetMinDurTick(project); resultParts.Add(pair.Value); } diff --git a/OpenUtau.Core/OpenUtau.Core.csproj b/OpenUtau.Core/OpenUtau.Core.csproj index 2cf08fa7b..e88ce8ca8 100644 --- a/OpenUtau.Core/OpenUtau.Core.csproj +++ b/OpenUtau.Core/OpenUtau.Core.csproj @@ -8,24 +8,24 @@ - + - - - + + + - - + + diff --git a/OpenUtau.Test/Classic/VoicebankConfigTest.cs b/OpenUtau.Test/Classic/VoicebankConfigTest.cs index fbaa81eb8..9be170ae7 100644 --- a/OpenUtau.Test/Classic/VoicebankConfigTest.cs +++ b/OpenUtau.Test/Classic/VoicebankConfigTest.cs @@ -12,6 +12,7 @@ public VoicebankConfigTest(ITestOutputHelper output) { static VoicebankConfig CreateConfig() { return new VoicebankConfig() { + PortraitOpacity = 0.75f, SymbolSet = new SymbolSet() { Preset = SymbolSetPreset.hiragana, }, @@ -50,7 +51,7 @@ public void SerializationTest() { var yaml = Yaml.DefaultSerializer.Serialize(CreateConfig()); output.WriteLine(yaml); - Assert.Equal(@"portrait_opacity: 0.67000001668930054 + Assert.Equal(@"portrait_opacity: 0.75 symbol_set: preset: hiragana head: '-' diff --git a/OpenUtau.Test/OpenUtau.Test.csproj b/OpenUtau.Test/OpenUtau.Test.csproj index b0c3ca4a6..7ff294e5c 100644 --- a/OpenUtau.Test/OpenUtau.Test.csproj +++ b/OpenUtau.Test/OpenUtau.Test.csproj @@ -13,7 +13,6 @@ - diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index d880aa24f..271442e5e 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -40,12 +40,12 @@ - + - - + + From a9c9fdf42539f060e898feaf92551660ef577409 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Sun, 21 Aug 2022 14:37:20 -0700 Subject: [PATCH 019/321] fix midi import issue when lyric encoding is undetected --- OpenUtau.Core/Format/Midi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau.Core/Format/Midi.cs b/OpenUtau.Core/Format/Midi.cs index 2360ba812..5adcd6dc7 100644 --- a/OpenUtau.Core/Format/Midi.cs +++ b/OpenUtau.Core/Format/Midi.cs @@ -41,7 +41,7 @@ static public List Load(string file, UProject project) { } stream.Seek(0, SeekOrigin.Begin); var detectionResult = CharsetDetector.DetectFromStream(stream); - if (detectionResult.Detected.Confidence > 0.5) { + if (detectionResult.Detected != null && detectionResult.Detected.Confidence > 0.5) { lyricEncoding = detectionResult.Detected.Encoding; } } From b2c5b151998dc4215aab8ff058ae366b5a25efa6 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Mon, 22 Aug 2022 20:56:50 -0700 Subject: [PATCH 020/321] fix enunu long part issue, add enunu voice color support --- OpenUtau.Core/Enunu/EnunuPhonemizer.cs | 8 +-- OpenUtau.Core/Enunu/EnunuRenderer.cs | 2 + OpenUtau.Core/Enunu/EnunuSinger.cs | 70 ++++++++++++++++++++------ OpenUtau.Core/Enunu/EnunuUtils.cs | 4 ++ OpenUtau.Core/Render/RenderPhrase.cs | 9 +++- OpenUtau.Core/Ustx/UPhoneme.cs | 11 ++++ OpenUtau/Views/PianoRollWindow.axaml | 2 +- 7 files changed, 85 insertions(+), 21 deletions(-) diff --git a/OpenUtau.Core/Enunu/EnunuPhonemizer.cs b/OpenUtau.Core/Enunu/EnunuPhonemizer.cs index 3dc7c35f7..374666c9b 100644 --- a/OpenUtau.Core/Enunu/EnunuPhonemizer.cs +++ b/OpenUtau.Core/Enunu/EnunuPhonemizer.cs @@ -129,11 +129,11 @@ static Phoneme[] ParseLabel(string path) { var line = reader.ReadLine(); var parts = line.Split(); if (parts.Length == 3 && - int.TryParse(parts[0], out var pos) && - int.TryParse(parts[1], out var end)) { + long.TryParse(parts[0], out long pos) && + long.TryParse(parts[1], out long end)) { phonemes.Add(new Phoneme { phoneme = parts[2], - position = pos, + position = (int)(pos / 1000L), }); } } @@ -145,7 +145,7 @@ public override Result Process(Note[] notes, Note? prev, Note? next, Note? prevN if (partResult.TryGetValue(notes, out var phonemes)) { return new Result { phonemes = phonemes.Select(p => { - double posMs = p.position * 0.0001; + double posMs = p.position * 0.1; p.position = MsToTick(posMs) - notes[0].position; return p; }).ToArray(), diff --git a/OpenUtau.Core/Enunu/EnunuRenderer.cs b/OpenUtau.Core/Enunu/EnunuRenderer.cs index 25a236c53..1d2711572 100644 --- a/OpenUtau.Core/Enunu/EnunuRenderer.cs +++ b/OpenUtau.Core/Enunu/EnunuRenderer.cs @@ -20,6 +20,7 @@ public class EnunuRenderer : IRenderer { static readonly HashSet supportedExp = new HashSet(){ Format.Ustx.DYN, + Format.Ustx.CLR, Format.Ustx.PITD, Format.Ustx.GENC, Format.Ustx.BREC, @@ -190,6 +191,7 @@ static EnunuNote[] PhraseToEnunuNotes(RenderPhrase phrase) { lyric = phone.phoneme, length = phone.duration, noteNum = phone.tone, + timbre = phone.suffix, }); } notes.Add(new EnunuNote { diff --git a/OpenUtau.Core/Enunu/EnunuSinger.cs b/OpenUtau.Core/Enunu/EnunuSinger.cs index b8b71b542..7e28c41d1 100644 --- a/OpenUtau.Core/Enunu/EnunuSinger.cs +++ b/OpenUtau.Core/Enunu/EnunuSinger.cs @@ -35,14 +35,42 @@ class EnunuSinger : USinger { List subbanks = new List(); HashSet phonemes = new HashSet(); + HashSet timbres = new HashSet(); Dictionary table = new Dictionary(); public byte[] avatarData; public EnunuSinger(Voicebank voicebank) { this.voicebank = voicebank; + found = true; + } + + public override void EnsureLoaded() { + if (Loaded) { + return; + } + Reload(); + } + + public override void Reload() { + if (!Found) { + return; + } + try { + voicebank.Reload(); + Load(); + loaded = true; + } catch (Exception e) { + Log.Error(e, $"Failed to load {voicebank.File}"); + } + } + + void Load() { enuconfig = EnunuConfig.Load(this); + phonemes.Clear(); + timbres.Clear(); + table.Clear(); try { var tablePath = Path.Join(Location, enuconfig.tablePath); foreach (var line in File.ReadAllLines(tablePath)) { @@ -57,22 +85,20 @@ public EnunuSinger(Voicebank voicebank) { } try { var hedPath = Path.Join(Location, enuconfig.questionPath); - var pattern = new Regex(@"\{(.*)\}"); + var pattern = new Regex("^\\s*QS\\s+\\\"(.*)\\\"\\s+\\{(.*)}"); foreach (var line in File.ReadAllLines(hedPath)) { - if (!line.StartsWith("QS ")) { - continue; - } var m = pattern.Match(line); if (!m.Success) { continue; } - var g = m.Groups[1].Value; - foreach (var p in g.Split(',')) { - var exp = p.Trim(); - if (exp.StartsWith("*^") && exp.EndsWith("-*") || - exp.StartsWith("*-") && exp.EndsWith("+*") || - exp.StartsWith("*+") && exp.EndsWith("=*")) { - phonemes.Add(exp.Substring(2, exp.Length - 4)); + foreach (var p in m.Groups[2].Value.Split(',')) { + var value = p.Trim(); + if (value.StartsWith("*^") && value.EndsWith("-*") || + value.StartsWith("*-") && value.EndsWith("+*") || + value.StartsWith("*+") && value.EndsWith("=*")) { + phonemes.Add(value.Substring(2, value.Length - 4)); + } else if (value.StartsWith("*^") && value.EndsWith("_*")) { + timbres.Add(value.Substring(2, value.Length - 4)); } } } @@ -80,6 +106,25 @@ public EnunuSinger(Voicebank voicebank) { Log.Error(e, $"Failed to load hed for {Name}"); } + subbanks.Clear(); + if (voicebank.Subbanks == null || voicebank.Subbanks.Count == 0 || + voicebank.Subbanks.Count == 1 && string.IsNullOrEmpty(voicebank.Subbanks[0].Color)) { + subbanks.Add(new USubbank(new Subbank() { + Prefix = string.Empty, + Suffix = string.Empty, + ToneRanges = new[] { "C1-B7" }, + })); + subbanks.AddRange(timbres.Select(flag => new USubbank(new Subbank() { + Color = flag, + Prefix = string.Empty, + Suffix = flag, + ToneRanges = new[] { "C1-B7" }, + }))); + } else { + subbanks.AddRange(voicebank.Subbanks + .Select(subbank => new USubbank(subbank))); + } + if (Avatar != null && File.Exists(Avatar)) { try { using (var stream = new FileStream(Avatar, FileMode.Open)) { @@ -96,9 +141,6 @@ public EnunuSinger(Voicebank voicebank) { avatarData = null; Log.Error("Avatar can't be found"); } - - found = true; - loaded = true; } public override bool TryGetMappedOto(string phoneme, int tone, out UOto oto) { diff --git a/OpenUtau.Core/Enunu/EnunuUtils.cs b/OpenUtau.Core/Enunu/EnunuUtils.cs index 81ab84d29..873507623 100644 --- a/OpenUtau.Core/Enunu/EnunuUtils.cs +++ b/OpenUtau.Core/Enunu/EnunuUtils.cs @@ -10,6 +10,7 @@ struct EnunuNote { public int length; public int noteNum; public int noteIndex; + public string timbre; } internal static class EnunuUtils { @@ -29,6 +30,9 @@ internal static void WriteUst(IList notes, double tempo, USinger sing writer.WriteLine($"Lyric={notes[i].lyric}"); writer.WriteLine($"Length={notes[i].length}"); writer.WriteLine($"NoteNum={notes[i].noteNum}"); + if (!string.IsNullOrEmpty(notes[i].timbre)) { + writer.WriteLine($"Flags={notes[i].timbre}"); + } } writer.WriteLine("[#TRACKEND]"); } diff --git a/OpenUtau.Core/Render/RenderPhrase.cs b/OpenUtau.Core/Render/RenderPhrase.cs index b91123cdb..27cffcc73 100644 --- a/OpenUtau.Core/Render/RenderPhrase.cs +++ b/OpenUtau.Core/Render/RenderPhrase.cs @@ -32,6 +32,7 @@ public class RenderPhone { // classic args public readonly string resampler; public readonly Tuple[] flags; + public readonly string suffix; public readonly float volume; public readonly float velocity; public readonly float modulation; @@ -59,6 +60,9 @@ internal RenderPhone(UProject project, UTrack track, UVoicePart part, UNote note } } flags = phoneme.GetResamplerFlags(project, track); + string voiceColor = phoneme.GetVoiceColor(project, track); + suffix = track.Singer.Subbanks.FirstOrDefault( + subbank => subbank.Color == voiceColor)?.Suffix; volume = phoneme.GetExpression(project, track, Format.Ustx.VOL).Item1 * 0.01f; velocity = phoneme.GetExpression(project, track, Format.Ustx.VEL).Item1 * 0.01f; modulation = phoneme.GetExpression(project, track, Format.Ustx.MOD).Item1 * 0.01f; @@ -73,16 +77,17 @@ private ulong Hash() { using (var stream = new MemoryStream()) { using (var writer = new BinaryWriter(stream)) { writer.Write(duration); - writer.Write(phoneme ?? ""); + writer.Write(phoneme ?? string.Empty); writer.Write(tone); - writer.Write(resampler ?? ""); + writer.Write(resampler ?? string.Empty); foreach (var flag in flags) { writer.Write(flag.Item1); if (flag.Item2.HasValue) { writer.Write(flag.Item2.Value); } } + writer.Write(suffix ?? string.Empty); writer.Write(volume); writer.Write(velocity); writer.Write(modulation); diff --git a/OpenUtau.Core/Ustx/UPhoneme.cs b/OpenUtau.Core/Ustx/UPhoneme.cs index fd1c224a3..9a3c182cb 100644 --- a/OpenUtau.Core/Ustx/UPhoneme.cs +++ b/OpenUtau.Core/Ustx/UPhoneme.cs @@ -213,6 +213,17 @@ public void SetExpression(UProject project, UTrack track, string abbr, float val } return flags.ToArray(); } + + public string GetVoiceColor(UProject project, UTrack track) { + if (track.VoiceColorExp == null) { + return null; + } + int index = (int)GetExpression(project, track, Format.Ustx.CLR).Item1; + if (index < 0 || index >= track.VoiceColorExp.options.Length) { + return null; + } + return track.VoiceColorExp.options[index]; + } } public class UEnvelope { diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index 8eb9635c2..9d6109f48 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -23,7 +23,7 @@ - + From f3dd164c2055806044da85dc47e0cec31486e469 Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Mon, 22 Aug 2022 21:53:00 -0700 Subject: [PATCH 021/321] adds a little g2p tool "phonetic assistant" --- OpenUtau.Core/Api/G2pPack.cs | 3 - OpenUtau/Strings/Strings.axaml | 3 + .../ViewModels/PhoneticAssistantViewModel.cs | 61 +++++++++++++++++++ OpenUtau/Views/MainWindow.axaml | 1 + OpenUtau/Views/MainWindow.axaml.cs | 12 ++++ OpenUtau/Views/PhoneticAssistant.axaml | 20 ++++++ OpenUtau/Views/PhoneticAssistant.axaml.cs | 26 ++++++++ 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 OpenUtau/ViewModels/PhoneticAssistantViewModel.cs create mode 100644 OpenUtau/Views/PhoneticAssistant.axaml create mode 100644 OpenUtau/Views/PhoneticAssistant.axaml.cs diff --git a/OpenUtau.Core/Api/G2pPack.cs b/OpenUtau.Core/Api/G2pPack.cs index b448d52c7..bb2c92873 100644 --- a/OpenUtau.Core/Api/G2pPack.cs +++ b/OpenUtau.Core/Api/G2pPack.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using SharpCompress.Archives; using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenUtau.Core.Util; diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index b2b106f2d..a13a7f625 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -148,6 +148,9 @@ Warning: this option removes custom presets. Set Overlap Set Preutter + Phonetic Assistant + Copy + Lyrics Replace "-" with "+" Edit Lyrics diff --git a/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs b/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs new file mode 100644 index 000000000..f0027ae30 --- /dev/null +++ b/OpenUtau/ViewModels/PhoneticAssistantViewModel.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; + +namespace OpenUtau.App.ViewModels { + public class PhoneticAssistantViewModel : ViewModelBase { + public class G2pOption { + public string name; + public Type klass; + public G2pOption(Type klass) { + name = klass.Name; + this.klass = klass; + } + public override string ToString() => name; + } + public List G2ps => g2ps; + + [Reactive] public G2pOption? G2p { get; set; } + [Reactive] public string Grapheme { get; set; } + [Reactive] public string Phonemes { get; set; } + + private readonly List g2ps = new List() { + new G2pOption(typeof(Plugin.Builtin.ArpabetG2p)), + new G2pOption(typeof(Plugin.Builtin.FrenchG2p)), + new G2pOption(typeof(Plugin.Builtin.PortugueseG2p)), + new G2pOption(typeof(Plugin.Builtin.RussianG2p)), + }; + + private Api.G2pPack? g2p; + + public PhoneticAssistantViewModel() { + Grapheme = string.Empty; + Phonemes = string.Empty; + this.WhenAnyValue(x => x.G2p) + .Subscribe(option => { + g2p = null; + if (option != null) { + g2p = Activator.CreateInstance(option.klass) as Api.G2pPack; + Refresh(); + } + }); + this.WhenAnyValue(x => x.Grapheme) + .Subscribe(_ => Refresh()); + } + + private void Refresh() { + if (g2p == null) { + Phonemes = string.Empty; + return; + } + string[] phonemes = g2p.Query(Grapheme); + if (phonemes == null) { + Phonemes = string.Empty; + return; + } + Phonemes = string.Join(' ', phonemes); + } + } +} diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index 0321e11e4..1a65c20be 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -64,6 +64,7 @@ + diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 53368d2d5..898453ea4 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -470,6 +470,18 @@ void OnMenuDebugWindow(object sender, RoutedEventArgs args) { window.Show(); } + void OnMenuPhoneticAssistant(object sender, RoutedEventArgs args) { + var desktop = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; + if (desktop == null) { + return; + } + var window = desktop.Windows.FirstOrDefault(w => w is PhoneticAssistant); + if (window == null) { + window = new PhoneticAssistant(); + } + window.Show(); + } + void OnMenuWiki(object sender, RoutedEventArgs args) { try { OS.OpenWeb("https://github.com/stakira/OpenUtau/wiki/Getting-Started"); diff --git a/OpenUtau/Views/PhoneticAssistant.axaml b/OpenUtau/Views/PhoneticAssistant.axaml new file mode 100644 index 000000000..64be9ee88 --- /dev/null +++ b/OpenUtau/Views/PhoneticAssistant.axaml @@ -0,0 +1,20 @@ + + + + + + + u.Dispose()); unbinds.Clear(); diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index 3637410eb..170714510 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -76,6 +76,9 @@ True Resources.resx + + TrackSettingsDialog.axaml + diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 0db1dad79..d1f3b7bf2 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -36,6 +36,10 @@ No resampler No resampler! Put your favourite resampler exe or dll in the Resamplers folder and choose it in Preferences! Time Signature + Track Settings + Location + Renderer + Set As Default Error @@ -251,6 +255,7 @@ Hold Ctrl to select Note: to use external resamplers, please add the resampler DLL or EXE file in the Resamplers folder in the OpenUtau install path and choose it in Preferences. + Wavtool Project saved. {0} Waiting Rendering diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 1e489e781..780ffdc2a 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -569,14 +569,14 @@ bool IsExpSupported(string expKey) { return true; } var track = Project.tracks[Part.trackNo]; - if (track.Renderer == null) { + if (track.RendererSettings.Renderer == null) { return true; } if (Project.expressions.TryGetValue(expKey, out var descriptor)) { - return track.Renderer.SupportsExpression(descriptor); + return track.RendererSettings.Renderer.SupportsExpression(descriptor); } if (expKey == track.VoiceColorExp.abbr) { - return track.Renderer.SupportsExpression(track.VoiceColorExp); + return track.RendererSettings.Renderer.SupportsExpression(track.VoiceColorExp); } return true; } diff --git a/OpenUtau/ViewModels/PlaybackViewModel.cs b/OpenUtau/ViewModels/PlaybackViewModel.cs index 73edc0208..de6c4eadf 100644 --- a/OpenUtau/ViewModels/PlaybackViewModel.cs +++ b/OpenUtau/ViewModels/PlaybackViewModel.cs @@ -27,13 +27,12 @@ public void SeekEnd() { Pause(); DocManager.Inst.ExecuteCmd(new SeekPlayPosTickNotification(Project.EndTick)); } - public bool PlayOrPause() { - var ret = PlaybackManager.Inst.PlayOrPause(); + public void PlayOrPause() { + PlaybackManager.Inst.PlayOrPause(); var lockStartTime = Convert.ToBoolean(Preferences.Default.LockStartTime); if (!PlaybackManager.Inst.Playing && !PlaybackManager.Inst.StartingToPlay && lockStartTime) { DocManager.Inst.ExecuteCmd(new SeekPlayPosTickNotification(PlaybackManager.Inst.StartTick)); } - return ret; } public void Pause() { PlaybackManager.Inst.PausePlayback(); diff --git a/OpenUtau/ViewModels/PreferencesViewModel.cs b/OpenUtau/ViewModels/PreferencesViewModel.cs index 350ab4f78..4340c5801 100644 --- a/OpenUtau/ViewModels/PreferencesViewModel.cs +++ b/OpenUtau/ViewModels/PreferencesViewModel.cs @@ -30,27 +30,18 @@ public AudioOutputDevice? AudioOutputDevice { [Reactive] public int LockStartTime { get; set; } public string AdditionalSingersPath => PathManager.Inst.AdditionalSingersPath; [Reactive] public int InstallToAdditionalSingersPath { get; set; } - public List? Resamplers { get; } - public IResampler? ExportResampler { - get => exportResampler; - set => this.RaiseAndSetIfChanged(ref exportResampler, value); - } - [Reactive] public int PhaseCompensation { get; set; } [Reactive] public int PreRender { get; set; } [Reactive] public int Theme { get; set; } [Reactive] public int ShowPortrait { get; set; } - public List? Languages { get; } + public List? Languages { get; } public CultureInfo? Language { get => language; set => this.RaiseAndSetIfChanged(ref language, value); } - public bool MoresamplerSelected => moresamplerSelected.Value; private List? audioOutputDevices; private AudioOutputDevice? audioOutputDevice; - private IResampler? exportResampler; private CultureInfo? language; - private readonly ObservableAsPropertyHelper moresamplerSelected; public PreferencesViewModel() { var audioOutput = PlaybackManager.Inst.AudioOutput; @@ -67,16 +58,7 @@ public PreferencesViewModel() { PlayPosMarkerMargin = Preferences.Default.PlayPosMarkerMargin; LockStartTime = Preferences.Default.LockStartTime; InstallToAdditionalSingersPath = Preferences.Default.InstallToAdditionalSingersPath ? 1 : 0; - Classic.Resamplers.Search(); - Resamplers = Classic.Resamplers.GetResamplers(); - if (Resamplers.Count > 0) { - int index = Resamplers.FindIndex(resampler => resampler.Name == Preferences.Default.Resampler); - if (index >= 0) { - exportResampler = Resamplers[index]; - } else { - exportResampler = null; - } - } + ToolsManager.Inst.Initialize(); var pattern = new Regex(@"Strings\.([\w-]+)\.axaml"); Languages = Application.Current.Resources.MergedDictionaries .Select(res => (ResourceInclude)res) @@ -91,7 +73,6 @@ public PreferencesViewModel() { Language = string.IsNullOrEmpty(Preferences.Default.Language) ? null : CultureInfo.GetCultureInfo(Preferences.Default.Language); - PhaseCompensation = Preferences.Default.PhaseCompensation; PreRender = Preferences.Default.PreRender ? 1 : 0; Theme = Preferences.Default.Theme; ShowPortrait = Preferences.Default.ShowPortrait ? 1 : 0; @@ -133,25 +114,6 @@ public PreferencesViewModel() { Preferences.Default.InstallToAdditionalSingersPath = index > 0; Preferences.Save(); }); - this.WhenAnyValue(vm => vm.ExportResampler) - .WhereNotNull() - .Subscribe(resampler => { - if (resampler != null) { - Preferences.Default.Resampler = resampler!.Name; - Preferences.Save(); - resampler!.CheckPermissions(); - DocManager.Inst.ExecuteCmd(new PreRenderNotification()); - } - }); - this.WhenAnyValue(vm => vm.ExportResampler) - .Select(engine => - (engine?.Name?.Contains("moresampler", StringComparison.InvariantCultureIgnoreCase) ?? false)) - .ToProperty(this, x => x.MoresamplerSelected, out moresamplerSelected); - this.WhenAnyValue(vm => vm.PhaseCompensation) - .Subscribe(phaseComp => { - Preferences.Default.PhaseCompensation = phaseComp; - Preferences.Save(); - }); this.WhenAnyValue(vm => vm.PreRender) .Subscribe(preRender => { Preferences.Default.PreRender = preRender > 0; diff --git a/OpenUtau/ViewModels/TrackHeaderViewModel.cs b/OpenUtau/ViewModels/TrackHeaderViewModel.cs index ff5b3bd90..cc429488a 100644 --- a/OpenUtau/ViewModels/TrackHeaderViewModel.cs +++ b/OpenUtau/ViewModels/TrackHeaderViewModel.cs @@ -19,7 +19,7 @@ public class TrackHeaderViewModel : ViewModelBase, IActivatableViewModel { public USinger Singer => track.Singer; public Phonemizer Phonemizer => track.Phonemizer; public string PhonemizerTag => track.Phonemizer.Tag; - public Core.Render.IRenderer Renderer => track.Renderer; + public Core.Render.IRenderer Renderer => track.RendererSettings.Renderer; public IReadOnlyList? SingerMenuItems { get; set; } public ReactiveCommand SelectSingerCommand { get; } public IReadOnlyList? PhonemizerMenuItems { get; set; } @@ -58,10 +58,13 @@ public TrackHeaderViewModel(UTrack track) { TryChangePhonemizer(singer.DefaultPhonemizer); } if (singer == null || !singer.Found) { - DocManager.Inst.ExecuteCmd(new TrackChangeRendererCommand(DocManager.Inst.Project, track, null)); - } else if (singer.SingerType != track.Renderer?.SingerType) { - string? renderer = Core.Render.Renderers.GetDefaultRenderer(singer.SingerType); - DocManager.Inst.ExecuteCmd(new TrackChangeRendererCommand(DocManager.Inst.Project, track, renderer)); + var settings = new URenderSettings(); + DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, track, settings)); + } else if (singer.SingerType != track.RendererSettings.Renderer?.SingerType) { + var settings = new URenderSettings { + renderer = Core.Render.Renderers.GetDefaultRenderer(singer.SingerType), + }; + DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, track, settings)); } DocManager.Inst.EndUndoGroup(); if (!string.IsNullOrEmpty(singer?.Id) && singer.Found) { @@ -100,8 +103,11 @@ public TrackHeaderViewModel(UTrack track) { this.RaisePropertyChanged(nameof(PhonemizerTag)); }); SelectRendererCommand = ReactiveCommand.Create(name => { + var settings = new URenderSettings { + renderer = name, + }; DocManager.Inst.StartUndoGroup(); - DocManager.Inst.ExecuteCmd(new TrackChangeRendererCommand(DocManager.Inst.Project, track, name)); + DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, track, settings)); DocManager.Inst.EndUndoGroup(); this.RaisePropertyChanged(nameof(Renderer)); }); diff --git a/OpenUtau/ViewModels/TrackSettingsViewModel.cs b/OpenUtau/ViewModels/TrackSettingsViewModel.cs new file mode 100644 index 000000000..21ce9b5f1 --- /dev/null +++ b/OpenUtau/ViewModels/TrackSettingsViewModel.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using DynamicData.Binding; +using OpenUtau.Classic; +using OpenUtau.Core; +using OpenUtau.Core.Render; +using OpenUtau.Core.Ustx; +using OpenUtau.Core.Util; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; + +namespace OpenUtau.App.ViewModels { + class TrackSettingsViewModel : ViewModelBase { + public UTrack Track { get; private set; } + public ObservableCollectionExtended Resamplers => resamplers; + [Reactive] public IResampler? Resampler { get; set; } + [Reactive] public bool NeedsResampler { get; set; } + public ObservableCollectionExtended Wavtools => wavtools; + [Reactive] public IWavtool? Wavtool { get; set; } + [Reactive] public bool NeedsWavtool { get; set; } + + ObservableCollectionExtended resamplers = + new ObservableCollectionExtended(); + ObservableCollectionExtended wavtools = + new ObservableCollectionExtended(); + + public TrackSettingsViewModel(UTrack track) { + ToolsManager.Inst.Initialize(); + Track = track; + if (!string.IsNullOrEmpty(Track.RendererSettings.renderer)) { + var renderer = Track.RendererSettings.renderer; + resamplers.AddRange(ToolsManager.Inst.Resamplers); + string resamplerName = Track.RendererSettings.resampler; + if (string.IsNullOrEmpty(resamplerName)) { + if (!Preferences.Default.DefaultResamplers.TryGetValue(renderer, out resamplerName)) { + resamplerName = string.Empty; + } + } + Resampler = ToolsManager.Inst.GetResampler(resamplerName); + wavtools.AddRange(Renderers.GetSupportedWavtools(Resampler)); + string wavtoolName = Track.RendererSettings.wavtool; + if (string.IsNullOrEmpty(wavtoolName)) { + if (!Preferences.Default.DefaultWavtools.TryGetValue(renderer, out wavtoolName)) { + wavtoolName = string.Empty; + } + } + Wavtool = ToolsManager.Inst.GetWavtool(wavtoolName); + NeedsResampler = Renderers.CLASSIC == renderer; + NeedsWavtool = Renderers.CLASSIC == renderer; + } + this.WhenAnyValue(x => x.Resampler) + .Subscribe(resampler => { + var wavtool = Wavtool; + wavtools.Clear(); + wavtools.AddRange(Renderers.GetSupportedWavtools(resampler)); + if (wavtool != null && wavtools.Contains(wavtool)) { + Wavtool = wavtool; + } else { + Wavtool = wavtools.FirstOrDefault(); + } + }); + } + + public void OpenResamplerLocation() { + OS.OpenFolder(PathManager.Inst.ResamplersPath); + } + + public void SetDefaultResampler() { + if (Resampler != null) { + Preferences.Default.DefaultResamplers[Track.RendererSettings.renderer] = Resampler.ToString(); + Preferences.Save(); + } + } + + public void OpenWavtoolLocation() { + OS.OpenFolder(PathManager.Inst.WavtoolsPath); + } + + public void SetDefaultWavtool() { + if (Wavtool != null) { + Preferences.Default.DefaultWavtools[Track.RendererSettings.renderer] = Wavtool.ToString(); + Preferences.Save(); + } + } + + public void Finish() { + if (Renderers.CLASSIC != Track.RendererSettings.renderer) { + return; + } + DocManager.Inst.StartUndoGroup(); + var settings = Track.RendererSettings.Clone(); + settings.resampler = Resampler?.ToString(); + settings.wavtool = Wavtool?.ToString(); + DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, Track, settings)); + DocManager.Inst.EndUndoGroup(); + } + } +} diff --git a/OpenUtau/Views/ExpressionsDialog.axaml.cs b/OpenUtau/Views/ExpressionsDialog.axaml.cs index 8cd008444..bb682a706 100644 --- a/OpenUtau/Views/ExpressionsDialog.axaml.cs +++ b/OpenUtau/Views/ExpressionsDialog.axaml.cs @@ -23,18 +23,8 @@ private void ApplyButtonClicked(object sender, RoutedEventArgs _) { try { (DataContext as ExpressionsViewModel)?.Apply(); Close(); - } catch (ArgumentException e) { - MessageBox.Show( - this, - e.Message, - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); } catch (Exception e) { - MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + MessageBox.ShowError(this, e); } } diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 725054187..132980933 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -182,11 +182,7 @@ async void Open() { viewModel.OpenProject(files); } catch (Exception e) { Log.Error(e, $"Failed to open files {string.Join("\n", files)}"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } @@ -217,11 +213,7 @@ void OnMenuOpenProjectLocation(object sender, RoutedEventArgs args) { OS.OpenFolder(System.IO.Path.GetDirectoryName(project.FilePath)); } catch (Exception e) { Log.Error(e, "Failed to open project location."); - MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + MessageBox.ShowError(this, e); } } @@ -283,11 +275,7 @@ async void OnMenuImportTracks(object sender, RoutedEventArgs args) { viewModel.ImportTracks(await dialog.ShowAsync(this)); } catch (Exception e) { Log.Error(e, $"Failed to import files"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } @@ -309,11 +297,7 @@ async void OnMenuImportAudio(object sender, RoutedEventArgs args) { viewModel.ImportAudio(files[0]); } catch (Exception e) { Log.Error(e, "Failed to import audio"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } @@ -335,11 +319,7 @@ async void OnMenuImportMidi(object sender, RoutedEventArgs args) { viewModel.ImportMidi(files[0]); } catch (Exception e) { Log.Error(e, "Failed to import midi"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } @@ -654,22 +634,14 @@ async void OnDrop(object? sender, DragEventArgs args) { viewModel.OpenProject(new string[] { file }); } catch (Exception e) { Log.Error(e, $"Failed to open file {file}"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } else if (ext == ".mid") { try { viewModel.ImportMidi(file); } catch (Exception e) { Log.Error(e, "Failed to import midi"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } else if (ext == ".zip" || ext == ".rar" || ext == ".uar") { var setup = new SingerSetupDialog() { @@ -688,11 +660,7 @@ async void OnDrop(object? sender, DragEventArgs args) { viewModel.ImportAudio(file); } catch (Exception e) { Log.Error(e, "Failed to import audio"); - _ = await MessageBox.Show( - this, - e.ToString(), - ThemeManager.GetString("errors.caption"), - MessageBox.MessageBoxButtons.Ok); + _ = await MessageBox.ShowError(this, e); } } } @@ -702,12 +670,22 @@ void OnPlayOrPause(object sender, RoutedEventArgs args) { } void PlayOrPause() { - if (!viewModel.PlaybackViewModel.PlayOrPause()) { + try { + viewModel.PlaybackViewModel.PlayOrPause(); + } catch (Core.Render.NoResamplerException _) { MessageBox.Show( this, ThemeManager.GetString("dialogs.noresampler.message"), ThemeManager.GetString("dialogs.noresampler.caption"), MessageBox.MessageBoxButtons.Ok); + } catch (Core.Render.NoWavtoolException _) { + MessageBox.Show( + this, + ThemeManager.GetString("dialogs.noresampler.message"), + ThemeManager.GetString("dialogs.noresampler.caption"), + MessageBox.MessageBoxButtons.Ok); + } catch (Exception e) { + MessageBox.ShowError(this, e); } } diff --git a/OpenUtau/Views/MessageBox.axaml.cs b/OpenUtau/Views/MessageBox.axaml.cs index fdebb9179..bc76ca300 100644 --- a/OpenUtau/Views/MessageBox.axaml.cs +++ b/OpenUtau/Views/MessageBox.axaml.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -19,6 +21,19 @@ private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + public static Task ShowError(Window parent, AggregateException e) { + e = e.Flatten(); + string text = $"{e.InnerExceptions.First().Message}\n\n{e}"; + string title = ThemeManager.GetString("errors.caption"); + return Show(parent, text, title, MessageBoxButtons.Ok); + } + + public static Task ShowError(Window parent, Exception e) { + string text = $"{e.Message}\n\n{e}"; + string title = ThemeManager.GetString("errors.caption"); + return Show(parent, text, title, MessageBoxButtons.Ok); + } + public static Task Show(Window parent, string text, string title, MessageBoxButtons buttons) { var msgbox = new MessageBox() { Title = title diff --git a/OpenUtau/Views/PianoRollWindow.axaml.cs b/OpenUtau/Views/PianoRollWindow.axaml.cs index fdd85ac9c..ea78e2c41 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml.cs +++ b/OpenUtau/Views/PianoRollWindow.axaml.cs @@ -794,13 +794,12 @@ void OnKeyDown(object sender, KeyEventArgs args) { case Key.Up: notesVm.TransposeSelection(1); break; case Key.Down: notesVm.TransposeSelection(-1); break; case Key.Space: - if (ViewModel.PlaybackViewModel != null && - !ViewModel.PlaybackViewModel.PlayOrPause()) { - MessageBox.Show( - this, - ThemeManager.GetString("dialogs.noresampler.message"), - ThemeManager.GetString("dialogs.noresampler.caption"), - MessageBox.MessageBoxButtons.Ok); + if (ViewModel.PlaybackViewModel != null) { + try { + ViewModel.PlaybackViewModel.PlayOrPause(); + } catch (Exception e) { + MessageBox.ShowError(this, e); + } } break; case Key.Home: diff --git a/OpenUtau/Views/PreferencesDialog.axaml b/OpenUtau/Views/PreferencesDialog.axaml index c1e1ee7ff..8cc7477b5 100644 --- a/OpenUtau/Views/PreferencesDialog.axaml +++ b/OpenUtau/Views/PreferencesDialog.axaml @@ -74,17 +74,6 @@ - - - - -