diff --git a/Mirivoice/Assets/Lang/en-US.axaml b/Mirivoice/Assets/Lang/en-US.axaml index 2c3ae13..02c975c 100644 --- a/Mirivoice/Assets/Lang/en-US.axaml +++ b/Mirivoice/Assets/Lang/en-US.axaml @@ -38,6 +38,8 @@ Files Open... + Project file is newer than software. + Please upgrade MiriVoice! Recent... An Error occured during open file. Please Check Log file for more informations. diff --git a/Mirivoice/Assets/Lang/ko-KR.axaml b/Mirivoice/Assets/Lang/ko-KR.axaml index fc43355..ddf5810 100644 --- a/Mirivoice/Assets/Lang/ko-KR.axaml +++ b/Mirivoice/Assets/Lang/ko-KR.axaml @@ -10,6 +10,11 @@ 프로젝트가 저장되지 않았어요. 저장할까요? 프로젝트 열기 + + 프로젝트의 버전이 소프트웨어의 버전보다 높아요. + MiriVoice를 업데이트해 주세요! + + 프로젝트 저장 새 프로젝트 diff --git a/Mirivoice/Assets/UI/inton-bos.png b/Mirivoice/Assets/UI/inton-bos.png new file mode 100644 index 0000000..54f7cc0 Binary files /dev/null and b/Mirivoice/Assets/UI/inton-bos.png differ diff --git a/Mirivoice/Assets/UI/inton-eos.png b/Mirivoice/Assets/UI/inton-eos.png new file mode 100644 index 0000000..bc69402 Binary files /dev/null and b/Mirivoice/Assets/UI/inton-eos.png differ diff --git a/Mirivoice/Assets/UI/inton-no.png b/Mirivoice/Assets/UI/inton-no.png new file mode 100644 index 0000000..3414f7a Binary files /dev/null and b/Mirivoice/Assets/UI/inton-no.png differ diff --git a/Mirivoice/Assets/UI/inton-space.png b/Mirivoice/Assets/UI/inton-space.png new file mode 100644 index 0000000..9f41dd2 Binary files /dev/null and b/Mirivoice/Assets/UI/inton-space.png differ diff --git a/Mirivoice/Commands/DuplicateLineBoxReceiver.cs b/Mirivoice/Commands/DuplicateLineBoxReceiver.cs index 27f7c39..0982d8f 100644 --- a/Mirivoice/Commands/DuplicateLineBoxReceiver.cs +++ b/Mirivoice/Commands/DuplicateLineBoxReceiver.cs @@ -30,7 +30,7 @@ public override void DoAction() } metaIndex++; } - var lineBox = new LineBoxView(mLinePrototype, l.v, LineNoToBeAdded + 1, l.viewModel.voicerSelector.CurrentDefaultVoicerIndex, metaIndex); + var lineBox = new LineBoxView(mLinePrototype, l.v, LineNoToBeAdded + 1, l.viewModel.voicerSelector.CurrentDefaultVoicerIndex, metaIndex, true); l.v.LineBoxCollection.Insert(LineNoToBeAdded, lineBox); diff --git a/Mirivoice/Commands/MOriginator.cs b/Mirivoice/Commands/MOriginator.cs index c2046c8..c2b82c9 100644 --- a/Mirivoice/Commands/MOriginator.cs +++ b/Mirivoice/Commands/MOriginator.cs @@ -42,35 +42,35 @@ public bool CanRedo() } public void ClearRedoMemento() { - Log.Debug("[Clearing redo memento]"); + //Log.Debug("[Clearing redo memento]"); _redoMemento.states.Clear(); } public void RestoreFromUndoMemento() { - Log.Debug($"Restoring from undo memento: {_undoMemento}, obj: {obj}"); + //Log.Debug($"Restoring from undo memento: {_undoMemento}, obj: {obj}"); _redoMemento.states.Push(obj); obj = _undoMemento.states.Pop(); UpdateProperties(); - Log.Debug("redo Memento: {memento}", _redoMemento); + //Log.Debug("redo Memento: {memento}", _redoMemento); } public void Backup(T backup) { - Log.Debug($"Backup: {backup}"); + //Log.Debug($"Backup: {backup}"); this.backup = backup; _undoMemento.states.Push(backup); } public void BackupRedo(T backup) { - Log.Debug($"Backup Redo: {backup}"); + //Log.Debug($"Backup Redo: {backup}"); _redoMemento.states.Push(backup); } public void RestoreFromRedoMemento() { - Log.Debug($"Restoring from memento: {_redoMemento}"); + //Log.Debug($"Restoring from memento: {_redoMemento}"); _undoMemento.states.Push(obj); obj = _redoMemento.states.Pop(); UpdateProperties(); @@ -80,7 +80,7 @@ public void RestoreFromRedoMemento() public virtual void UpdateProperties() { - Log.Information("Updating Properties -- {obj}", obj); + //Log.Information("Updating Properties -- {obj}", obj); // update ui here throw new NotImplementedException(); } diff --git a/Mirivoice/Commands/MementoCommand.cs b/Mirivoice/Commands/MementoCommand.cs index a5e69f9..59c1eae 100644 --- a/Mirivoice/Commands/MementoCommand.cs +++ b/Mirivoice/Commands/MementoCommand.cs @@ -24,7 +24,7 @@ public void Execute(bool isRedoing) if (!isRedoing) { _originator.ClearRedoMemento(); - //_originator.PrintMemento(); + _originator.PrintMemento(); //Log.Debug($"-- exec"); } @@ -37,7 +37,7 @@ public void Execute(bool isRedoing) } //Log.Debug($"-- Redoing"); _originator.RestoreFromRedoMemento(); - //_originator.PrintMemento(); + _originator.PrintMemento(); } } @@ -65,7 +65,7 @@ public void UnExecute() { //Log.Debug("--- Undo blocked"); } - //_originator.PrintMemento(); + _originator.PrintMemento(); } } } diff --git a/Mirivoice/Commands/SetComboboxOriginator.cs b/Mirivoice/Commands/SetComboboxOriginator.cs new file mode 100644 index 0000000..110ea7f --- /dev/null +++ b/Mirivoice/Commands/SetComboboxOriginator.cs @@ -0,0 +1,28 @@ +using Mirivoice.Mirivoice.Core.Format; +using Mirivoice.ViewModels; +using Serilog; +using System; + +namespace Mirivoice.Commands +{ + public class SetProsodyOriginator : MOriginator + { + private int index; + + private readonly MResult v; + + public SetProsodyOriginator(ref int index, MResult v) : base(ref index) + { + this.index = index; + this.v = v; + } + + + public override void UpdateProperties() + { + Log.Debug("[Updating Properties] -- {obj}", obj); + v.NotProcessingSetProsodyCommand = true; // prevent recursion loop + v.Prosody = obj; + } + } +} diff --git a/Mirivoice/Mirivoice.Core/Editor/VoicerSelector.cs b/Mirivoice/Mirivoice.Core/Editor/VoicerSelector.cs index f188081..e96b5eb 100644 --- a/Mirivoice/Mirivoice.Core/Editor/VoicerSelector.cs +++ b/Mirivoice/Mirivoice.Core/Editor/VoicerSelector.cs @@ -228,8 +228,9 @@ public int CurrentDefaultVoicerIndex OnPropertyChanged(nameof(CurrentVoicer)); OnPropertyChanged(nameof(CurrentDefaultVoicerIndex)); - if (lastCulture != new CultureInfo(CurrentVoicer.Info.LangCode)) + if (! lastCulture.Equals(new CultureInfo(CurrentVoicer.Info.LangCode))) { + Log.Debug($"Culture Changed: {lastCulture} -> {new CultureInfo(CurrentVoicer.Info.LangCode)}"); CultureChanged = true; v.OnVoicerCultureChanged(new CultureInfo(CurrentVoicer.Info.LangCode)); } diff --git a/Mirivoice/Mirivoice.Core/Format/MResult.cs b/Mirivoice/Mirivoice.Core/Format/MResult.cs index 4ded1b1..7edb81e 100644 --- a/Mirivoice/Mirivoice.Core/Format/MResult.cs +++ b/Mirivoice/Mirivoice.Core/Format/MResult.cs @@ -1,13 +1,26 @@ -using Mirivoice.Mirivoice.Core.Editor; +using Avalonia.Media; +using Mirivoice.Commands; +using Mirivoice.Mirivoice.Core.Editor; using Mirivoice.ViewModels; +using Mirivoice.Views; +using ReactiveUI; +using Serilog; +using System.Drawing; using System.Globalization; using VYaml.Annotations; namespace Mirivoice.Mirivoice.Core.Format { - + public enum ProsodyType + { + Undefined = -1, + Bos = 0, + None = 1, + Space = 2, + Eos = 3 + } public class MResult : VoicerSelectingViewModelBase { // each phoneme blocks will own One Syllable only @@ -32,21 +45,143 @@ public class MResult : VoicerSelectingViewModelBase //public string[] TTSPhonemes { get; set; } // [kæt] -- [ne], [.ko] -- ʨip̚, [k͈o], jaŋ, .i public string Word { get; set; } + public int _currentProsodyIndex; + public bool NotProcessingSetProsodyCommand = false; + int lastProsodyIndex; public override MTextBoxEditor mTextBoxEditor { get; set; } + private ImageBrush _bosIcon; + public ImageBrush BosIcon + { + get => _bosIcon; + set + { + this.RaiseAndSetIfChanged(ref _bosIcon, value); + OnPropertyChanged(nameof(BosIcon)); + } + + } + private ImageBrush _eosIcon; + public ImageBrush EosIcon + { + get => _eosIcon; + set + { + this.RaiseAndSetIfChanged(ref _eosIcon, value); + OnPropertyChanged(nameof(EosIcon)); + } + } + + private ImageBrush _nonIcon; + public ImageBrush NonIcon + { + get => _nonIcon; + set + { + this.RaiseAndSetIfChanged(ref _nonIcon, value); + OnPropertyChanged(nameof(NonIcon)); + } + } + + private ImageBrush _spaceIcon; + public ImageBrush SpaceIcon + { + get => _spaceIcon; + set + { + this.RaiseAndSetIfChanged(ref _spaceIcon, value); + OnPropertyChanged(nameof(SpaceIcon)); + } + } + + private int _prosody; + + bool UndobackupedProsody = false; + + public int Prosody + { + get + { + return _prosody; + } + set + { + if (value == -1) + { + return; + } + lastProsodyIndex = _prosody; + + + + if (NotProcessingSetProsodyCommand) + { + if (!UndobackupedProsody) + { + + + setProsodyCommand.Backup(lastProsodyIndex); + UndobackupedProsody = true; + } + MainManager.Instance.cmd.ExecuteCommand(setProsodyCommand); + + UndobackupedProsody = false; + } + else + { + NotProcessingSetProsodyCommand = false; + + } + this.RaiseAndSetIfChanged(ref _prosody, value); + + OnPropertyChanged(nameof(Prosody)); + + if (l is not null) + { + l.IsCacheIsVaild = false; + + } + } + } public bool IsEditable { get; set; } = false; - public MResult(string word, string phoneme, bool isEditable): base(phoneme, false) + + private readonly LineBoxView l; + public MResult(string word, string phoneme, bool isEditable, ProsodyType prosodyType, LineBoxView l=null): base(phoneme, false) { this.Word = word; this.IsEditable = isEditable; + Prosody = (int)prosodyType; + SetIcons(); + this.l = l; + SetCommands(); } - public MResult(MResultPrototype mResultPrototype): base(mResultPrototype.Phoneme, false) + public MResult(MResultPrototype mResultPrototype, LineBoxView l) : base(mResultPrototype.Phoneme, false) { this.Word = mResultPrototype.Word; this.IsEditable = mResultPrototype.IsEditable; + this.Prosody = mResultPrototype.ProsodyType; + SetIcons(); + this.l = l; + SetCommands(); + } + void SetIcons() + { + BosIcon = MainManager.Instance.IconM.BosIcon; + EosIcon = MainManager.Instance.IconM.EosIcon; + NonIcon = MainManager.Instance.IconM.NonIcon; + SpaceIcon = MainManager.Instance.IconM.SpaceIcon; + } + + MOriginator setProsodyOriginator; + MementoCommand setProsodyCommand; + void SetCommands() + { + setProsodyOriginator = new SetProsodyOriginator(ref _prosody, this); + setProsodyCommand = new MementoCommand(setProsodyOriginator); + NotProcessingSetProsodyCommand = true; } public override void OnVoicerChanged(Voicer value) { } public override void OnVoicerCultureChanged(CultureInfo culture) { } diff --git a/Mirivoice/Mirivoice.Core/Format/Mrp.cs b/Mirivoice/Mirivoice.Core/Format/Mrp.cs index a5cac62..3d793ee 100644 --- a/Mirivoice/Mirivoice.Core/Format/Mrp.cs +++ b/Mirivoice/Mirivoice.Core/Format/Mrp.cs @@ -11,6 +11,8 @@ using System.Threading.Tasks; using VYaml.Annotations; using VYaml.Serialization; +using Mirivoice.Mirivoice.Core.Format; +using Mirivoice.Mirivoice.Plugins.Builtin.Phonemizers; namespace Mirivoice.Mirivoice.Core.Format { @@ -20,6 +22,7 @@ public partial class MResultPrototype public string Word { get; set; } public string Phoneme { get; set; } public bool IsEditable { get; set; } + public int ProsodyType { get; set; } = -1; [YamlConstructor] public MResultPrototype() { } @@ -28,6 +31,15 @@ public MResultPrototype(MResult mResult) Word = mResult.Word; Phoneme = mResult.mTextBoxEditor.CurrentScript; IsEditable = mResult.IsEditable; + ProsodyType = mResult.Prosody; + } + + public MResultPrototype(string word, string phoneme, bool isEditable, ProsodyType prosodyType ) + { + Word = word; + Phoneme = phoneme; + IsEditable = isEditable; + ProsodyType = (int)prosodyType; } } @@ -76,6 +88,8 @@ public partial class Mrp public string MultEditScript { get; set; } = ""; public string DefaultVoicerName { get; set; } = ""; public int DefaultVoicerStyleId { get; set; } = 0; + [YamlIgnore] + public Version CurrentVersion = new Version(0, 2); [YamlConstructor] public Mrp() @@ -84,18 +98,38 @@ public Mrp() } public Mrp(MainViewModel v) { - Version = new Version(0, 1); // mirivoice 1.0.0 + Version = CurrentVersion; mLines = v.LineBoxCollection.Select(l => new MLinePrototype(l)).ToArray(); MultEditScript = v.mTextBoxEditor.CurrentScript; DefaultVoicerName = v.voicerSelector.CurrentVoicer.Info.Name; DefaultVoicerStyleId = v.voicerSelector.CurrentVoicer.CurrentVoicerMeta.SpeakerId; } + + public async Task Load(MainViewModel v) { Log.Information("Loading Project"); + + if (Version > CurrentVersion) + { + var res = await v.ShowConfirmWindow("menu.files.open.upgrade"); + return; + } v.project = this; + if (Version < CurrentVersion) + { + Log.Information($"Upgrading project from {Version} to {CurrentVersion}."); + } + if (Version < new Version(0, 2)) + { + foreach (var m in mLines) + { + m.PhonemeEdit = BasePhonemizer.SetUpProsody(null, m.PhonemeEdit.ToList(), false); + } + } + LineBoxView[] lines = new LineBoxView[mLines.Length]; await Dispatcher.UIThread.InvokeAsync(async () => @@ -131,7 +165,7 @@ await Dispatcher.UIThread.InvokeAsync(async () => } metaIndex++; } - lines[index] = new LineBoxView(l, v, index, voicerIndex, metaIndex); + lines[index] = new LineBoxView(l, v, index, voicerIndex, metaIndex, false); }); diff --git a/Mirivoice/Mirivoice.Core/Format/Voicer.cs b/Mirivoice/Mirivoice.Core/Format/Voicer.cs index 8c85530..5b088d8 100644 --- a/Mirivoice/Mirivoice.Core/Format/Voicer.cs +++ b/Mirivoice/Mirivoice.Core/Format/Voicer.cs @@ -106,15 +106,15 @@ public VoicerMeta CurrentVoicerMeta lastVoicerMeta = _currentVoicerMeta; - Log.Debug("CurrentVoicerMeta: {value}", value); + //Log.Debug("CurrentVoicerMeta: {value}", value); - Log.Debug($"CurrentVoicerMeta: {value.Style}"); - Log.Debug($"lastVoicerMeta: {lastVoicerMeta.Style}"); + //Log.Debug($"CurrentVoicerMeta: {value.Style}"); + //Log.Debug($"lastVoicerMeta: {lastVoicerMeta.Style}"); if (!NotProcessingSetVoicerMetaCommand) { if (!Undobackuped) { - Log.Debug($"Backup: {lastVoicerMeta}"); + //Log.Debug($"Backup: {lastVoicerMeta}"); SetVoicerMetaCommand.Backup(lastVoicerMeta); Undobackuped = true; } diff --git a/Mirivoice/Mirivoice.Core/Managers/CommandManager.cs b/Mirivoice/Mirivoice.Core/Managers/CommandManager.cs index 41f88b6..1666822 100644 --- a/Mirivoice/Mirivoice.Core/Managers/CommandManager.cs +++ b/Mirivoice/Mirivoice.Core/Managers/CommandManager.cs @@ -56,7 +56,7 @@ void ClearStacks() } public void ExecuteCommand(ICommand command) { - //Log.Debug("======== ExecuteCommand ========"); + Log.Debug("======== ExecuteCommand ========"); command.Execute(false); _undoStack.Push(command); _redoStack.Clear(); @@ -71,7 +71,7 @@ public void ExecuteCommand(ICommand command) StreamWriter writer = new StreamWriter("filePath", false, Encoding.UTF8); public void Undo() { - //Log.Debug("======== Undo ========"); + Log.Debug("======== Undo ========"); if (_undoStack.Count > 0) { var command = _undoStack.Pop(); @@ -89,7 +89,7 @@ public void Undo() public void Redo() { - //Log.Debug("======== Redo ========"); + Log.Debug("======== Redo ========"); if (_redoStack.Count > 0) { var command = _redoStack.Pop(); diff --git a/Mirivoice/Mirivoice.Core/Managers/IconManager.cs b/Mirivoice/Mirivoice.Core/Managers/IconManager.cs new file mode 100644 index 0000000..6266780 --- /dev/null +++ b/Mirivoice/Mirivoice.Core/Managers/IconManager.cs @@ -0,0 +1,45 @@ +using Mirivoice.Mirivoice.Core.Utils; +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Serilog; +using Avalonia.Media; +using System.Drawing; +using Avalonia.Platform; +using Avalonia.Media.Imaging; + +namespace Mirivoice.Mirivoice.Core.Managers +{ + public class IconManager + { + public ImageBrush BosIcon; + public ImageBrush NonIcon; + public ImageBrush SpaceIcon; + public ImageBrush EosIcon; + public IconManager() + { + LoadIcon(new Uri("avares://Mirivoice.Main/Assets/UI/inton-bos.png"), out BosIcon); + LoadIcon(new Uri("avares://Mirivoice.Main/Assets/UI/inton-no.png"), out NonIcon); + LoadIcon(new Uri("avares://Mirivoice.Main/Assets/UI/inton-space.png"), out SpaceIcon); + LoadIcon(new Uri("avares://Mirivoice.Main/Assets/UI/inton-eos.png"), out EosIcon); + + } + + public void LoadIcon(Uri uri, out ImageBrush icon) + { + var assets = AssetLoader.Open(uri); + + using (var stream = assets) + { + var bitmap = new Avalonia.Media.Imaging.Bitmap(stream); + icon = new ImageBrush(bitmap) + { + Stretch = Stretch.UniformToFill + }; + } + } + } +} diff --git a/Mirivoice/Mirivoice.Core/Managers/MainManager.cs b/Mirivoice/Mirivoice.Core/Managers/MainManager.cs index 0e35175..cf06b4b 100644 --- a/Mirivoice/Mirivoice.Core/Managers/MainManager.cs +++ b/Mirivoice/Mirivoice.Core/Managers/MainManager.cs @@ -24,6 +24,8 @@ public class MainManager : SingletonBase public VoicerManager VoicerM = new VoicerManager(); + public IconManager IconM;// will be initialized in MainViewModel + public AudioManager AudioM; // will be initialized in MainViewModel public int DefaultVoicerIndex = 0; @@ -48,6 +50,7 @@ public void Initialize() UpdateDefaultVoicers(); LoadVoicerManager(); LoadSetting(); + InitializationTask = Task.Run(() => { Log.Information("MainManager Initialize Start"); }); diff --git a/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/BasePhonemizer.cs b/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/BasePhonemizer.cs index f53acc1..24fe86e 100644 --- a/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/BasePhonemizer.cs +++ b/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/BasePhonemizer.cs @@ -3,6 +3,7 @@ using Mirivoice.Mirivoice.Plugins.Builtin.IPAConverters; using Mirivoice.Views; using Serilog; +using SharpCompress; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -17,7 +18,7 @@ public abstract class BasePhonemizer List IPAPhonemes = new List(); public virtual BaseIPAConverter IPAConverter { get; set; } // should be overrided public virtual bool UseWordDivider { get; set; } = false; // if true, insert blank block between words - private List endPuncs = new List { ".", "!", "?", "。" }; + private static List endPuncs = new List { ".", "!", "?", "。" }; protected virtual string[] SplitToWords(string sentence) { // In default, split sentence to words by character @@ -56,26 +57,28 @@ protected virtual string Variate(string sentence) { return sentence; } - public bool IsNumber(string word) + public static bool IsNumber(string word) { return word.All(char.IsDigit); } - public bool IsPunctuation(string word) + public static bool IsPunctuation(string word) { return word.All(char.IsPunctuation); } - protected virtual string ToPhoneme(string word, out bool isEditable) + protected virtual string ToPhoneme(string word, out bool isEditable, out ProsodyType prosodyType) { - if (word.Trim() == string.Empty) + if (word.Trim().Equals(string.Empty)) { - isEditable = false; + isEditable = false; + prosodyType = ProsodyType.None; return string.Empty; } isEditable = true; + prosodyType = ProsodyType.Undefined; return word; } @@ -99,7 +102,7 @@ public async Task ConvertToIPA(string sentence, DispatcherPriority dispa string[] variatedWords = VariateAndSplitToWords(sentence.Trim()); List IPAPhonemes = new List(); bool _ = false; - + ProsodyType prosodyType = ProsodyType.None; await Dispatcher.UIThread.InvokeAsync(async () => { IPAPhonemes = new string[variatedWords.Length].ToList(); @@ -107,13 +110,14 @@ await Dispatcher.UIThread.InvokeAsync(async () => var wordTasks = variatedWords .Select(async (word, index) => await Task.Run(()=> { - string phoneme = ToPhoneme(word, out _); + string phoneme = ToPhoneme(word, out _, out prosodyType); if (words.Length != variatedWords.Length) { Log.Error($"[ConvertToIPA: Variated Sentence({words.Length})] - [Sentence length({variatedWords.Length})] mismatch"); if (UseWordDivider && phoneme.Trim() == string.Empty) { IPAPhonemes[index] = " "; + } else { @@ -168,7 +172,8 @@ await Dispatcher.UIThread.InvokeAsync(async () => { if (!string.IsNullOrEmpty(unitSentence)) { - if ( !IsPunctuation(unitSentence) && ( i == 0 || i-1 > 0 && endPuncs.Contains(unitSentences[i-1]))) + if ( !IsPunctuation(unitSentence) && + ( i == 0 || i-1 > 0 && endPuncs.Contains(unitSentences[i-1]))) { IPAStringTokensAdded.Add(""); @@ -227,49 +232,178 @@ await Dispatcher.UIThread.InvokeAsync(() => //int wordBorderWidth = 1; bool editable = true; - + ProsodyType prosodyType = ProsodyType.None; string[] words = SplitToWords(sentence.Trim()); string[] variatedWords = VariateAndSplitToWords(sentence.Trim()); - - List results = new List(); + + MResultPrototype[] results = new MResultPrototype[variatedWords.Length]; + await Dispatcher.UIThread.InvokeAsync(async () => { IPAPhonemes.Clear(); - bool divideWord = false; + var wordTasks = variatedWords .Select(async (word, index) => { - string phoneme = ToPhoneme(word, out editable); - if (words.Length != variatedWords.Length) + string phoneme = ToPhoneme(word, out editable, out prosodyType); + if (index != 0 && variatedWords[index-1].Trim().Equals(string.Empty) && ! word.Trim().Equals(string.Empty)) + { + if (prosodyType == ProsodyType.Undefined) { - Log.Error($"[Variated Sentence({words.Length})] - [Sentence length({variatedWords.Length})] mismatch"); - results.Add(new MResult(word.Trim(), phoneme.Trim(), editable)); + prosodyType = ProsodyType.Space; + } + results[index] = new MResultPrototype(word.Trim(), phoneme.Trim(), editable, prosodyType); } else { - results.Add(new MResult(words[index].Trim(), phoneme.Trim(), editable)); + if (word.Trim().Equals(string.Empty)) + { + phoneme = string.Empty; + results[index] = new MResultPrototype(word, phoneme.Trim(), editable, ProsodyType.None); + } + else if (words.Length != variatedWords.Length) + { + Log.Error($"[Variated Sentence({words.Length})] - [Sentence length({variatedWords.Length})] mismatch"); + + results[index] = new MResultPrototype(word.Trim(), phoneme.Trim(), editable, ProsodyType.Undefined); + } + else + { + results[index] = new MResultPrototype(word.Trim(), phoneme.Trim(), editable, ProsodyType.Undefined); + } } - - divideWord = true; + + }); await Task.WhenAll(wordTasks); - if (l != null) + SetUpProsody(l, results.ToList()); + + }, dispatcherPriority); + //Log.Debug("DOne"); + } + public static MResultPrototype[] SetUpProsody(LineBoxView l, List results, bool ApplyToCurrentEdit=true) + { + List mResults = new List(); + + if (l != null) + { + + MResultPrototype prev = null; + MResultPrototype next = null; + + for (int i = 0; i < results.Count; ++i) { - l.MResultsCollection = new ObservableCollection(results); - if (ApplyToCurrentEdit) + MResultPrototype mResultPrototype = results[i]; + if (mResultPrototype.Word.Trim() == string.Empty) { - l.UpdateMResultsCollection(); + mResultPrototype.IsEditable = false; } + + if (i == results.Count - 1) + { + Log.Debug("Last"); + next = null; + } + else + { + next = results[i + 1]; + } + if (i == 0) + { + mResultPrototype.ProsodyType = (int)ProsodyType.Bos; + } + else + { + if (prev is not null) + { + if (IsPunctuation(prev.Phoneme) && endPuncs.Contains(prev.Phoneme) && mResultPrototype.Phoneme is not null && !mResultPrototype.Phoneme.Trim().Equals(string.Empty)) + { + mResultPrototype.ProsodyType = (int)ProsodyType.Bos; + } + } + } + + if (next is not null && IsPunctuation(next.Phoneme) && endPuncs.Contains(next.Phoneme)) + { + Log.Debug($"Next is punctuation: {next.Phoneme}"); + Log.Debug($"Current: {mResultPrototype.Phoneme}"); + mResultPrototype.ProsodyType = (int)ProsodyType.Eos; + } + if (i == results.Count - 1 && prev is not null && prev.ProsodyType != (int)ProsodyType.Eos) + { + + mResultPrototype.ProsodyType = (int)ProsodyType.Eos; + } + + + if (mResultPrototype.ProsodyType == (int)ProsodyType.Undefined) // if not set + { + mResultPrototype.ProsodyType = (int)ProsodyType.None; + } + + mResults.Add(mResultPrototype); + + + prev = mResultPrototype; + } + - }, dispatcherPriority); - + if (ApplyToCurrentEdit) + { + List mResultsFinal = new List (); + foreach (MResultPrototype mp in mResults) + { + mResultsFinal.Add(new MResult(mp, l)); + } + l.MResultsCollection = new ObservableCollection(mResultsFinal); + l.UpdateMResultsCollection(); + } + } + return mResults.ToArray(); } + + public string ApplyProsody(string phoneme, ProsodyType prosodyType, bool addPadLeft=false, bool addPadRight=false) + { + string res; + switch (prosodyType) + { + // TODO add pad + case ProsodyType.Undefined: + res = string.Empty; // do not pronounce + break; + case ProsodyType.Bos: + res = $"\t{phoneme}"; + break; + case ProsodyType.Eos: + res = $"{phoneme}\t"; + break; + case ProsodyType.Space: + res= $"\t{phoneme}"; + break; + case ProsodyType.None: + res = phoneme; + break; + default: + res = string.Empty; + break; + + } + if (addPadLeft) + { + res = "\t" + res; + } + if (addPadRight) + { + res = res + "\t"; + } + return res; + } public async Task GenerateIPAAsync(LineBoxView l, DispatcherPriority dispatcherPriority) { //Log.Debug("Generating IPA with current phonemes"); @@ -291,7 +425,7 @@ public async Task GenerateIPAAsync(LineBoxView l, DispatcherPriority dispatcherP await Dispatcher.UIThread.InvokeAsync(async () => { - IPAPhonemes = new string[l.MResultsCollection.Count].ToList(); + string[] IPAPhonemes = new string[l.MResultsCollection.Count]; bool divideWord = false; var phonemeTasks = l.MResultsCollection @@ -302,17 +436,26 @@ await Dispatcher.UIThread.InvokeAsync(async () => if (UseWordDivider && (phoneme == null || phoneme.Trim() == string.Empty)) { - IPAPhonemes[index] = " "; + string phone = ""; + + IPAPhonemes[index] = ApplyProsody(phone, (ProsodyType)mResult.Prosody); } else { if (index == 0) { - IPAPhonemes[index] = IPAConverter.ConvertToIPA(phoneme.Trim(), true); + string phone = IPAConverter.ConvertToIPA(phoneme.Trim(), true); + IPAPhonemes[index] = ApplyProsody(phone, (ProsodyType)mResult.Prosody, true); + } + else if (index == l.MResultsCollection.Count - 1) + { + string phone = IPAConverter.ConvertToIPA(phoneme.Trim(), false); + IPAPhonemes[index] = ApplyProsody(phone, (ProsodyType)mResult.Prosody, false, true); } else { - IPAPhonemes[index] = IPAConverter.ConvertToIPA(phoneme.Trim(), false); + string phone = IPAConverter.ConvertToIPA(phoneme.Trim(), false); + IPAPhonemes[index] = ApplyProsody(phone, (ProsodyType)mResult.Prosody); } } @@ -325,38 +468,15 @@ await Dispatcher.UIThread.InvokeAsync(async () => await Task.WhenAll(phonemeTasks); if (l != null) { - // add bos eos - string IPAString = string.Join("\t", IPAPhonemes); - string[] unitSentences = Regex.Split(IPAString, @"([^\w\s])"); - - - - List IPAStringTokensAdded = new List(); - IPAStringTokensAdded.Add(""); - foreach (string unitSentence in unitSentences) - { - if (!string.IsNullOrEmpty(unitSentence)) - { - if (!IsPunctuation(unitSentence)) - { - IPAStringTokensAdded.Add(""); - } - IPAStringTokensAdded.Add(unitSentence); - if (endPuncs.Contains(unitSentence)) - { - IPAStringTokensAdded.Add(""); - } - } - - } - IPAStringTokensAdded.Add(""); + + l.IPAText = string.Join("\t", - string.Join(" ", string.Join("\t", IPAStringTokensAdded) + string.Join(" ", string.Join("\t", IPAPhonemes) .Split(" ", StringSplitOptions.RemoveEmptyEntries)) .Replace(" ", "") .Split("\t", StringSplitOptions.RemoveEmptyEntries)); - //Log.Debug($"IPA generated: {l.IPAText}"); + Log.Debug($"IPA generated: {l.IPAText}"); } diff --git a/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/EnglishUSPhonemizer.cs b/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/EnglishUSPhonemizer.cs index 8d1965f..94df8f3 100644 --- a/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/EnglishUSPhonemizer.cs +++ b/Mirivoice/Mirivoice.Plugins.Builtin/Phonemizers/EnglishUSPhonemizer.cs @@ -1,4 +1,5 @@ -using Mirivoice.Mirivoice.Plugins.Builtin.IPAConverters; +using Mirivoice.Mirivoice.Core.Format; +using Mirivoice.Mirivoice.Plugins.Builtin.IPAConverters; using Mirivoice.Mirivoice.Plugins.Builtin.Phonemizers.Utils; using System.Collections.Generic; using System.Text; @@ -55,15 +56,17 @@ protected override string[] SplitToWords(string sentence) return words.ToArray(); } - protected override string ToPhoneme(string word, out bool isEditable) + protected override string ToPhoneme(string word, out bool isEditable, out ProsodyType prosodyType) { if (word.Trim() == string.Empty) { isEditable = false; + prosodyType = ProsodyType.None; return word; } isEditable = true; + prosodyType = ProsodyType.Undefined; return EnglishUSPhonemizerUtil.WordToArpabet(word); // k ae t } } diff --git a/Mirivoice/ViewModels/LineBoxViewModel.cs b/Mirivoice/ViewModels/LineBoxViewModel.cs index f73d94f..5f51148 100644 --- a/Mirivoice/ViewModels/LineBoxViewModel.cs +++ b/Mirivoice/ViewModels/LineBoxViewModel.cs @@ -147,7 +147,11 @@ public override void OnVoicerChanged(Voicer voicer) //Log.Debug($"OnVoicerChanged: {voicer.NickAndStyle}"); VoicerInfo vInfo = voicer.Info; - phonemizer = GetPhonemizer(voicer.Info.LangCode); + if (phonemizer is null) + { + phonemizer = GetPhonemizer(voicerSelector.CurrentVoicer.Info.LangCode); + } + l.IsCacheIsVaild = false; LangCode = voicer.Info.LangCode.ToUpper().Substring(0, 2); @@ -187,10 +191,17 @@ public override void OnVoicerChanged(Voicer voicer) bool cultureChangedFirst = false; public override void OnVoicerCultureChanged(CultureInfo culture) { - if (LineText == lineTextBeforeChangedCulture && cultureChangedFirst) + phonemizer = GetPhonemizer(voicerSelector.CurrentVoicer.Info.LangCode); // get phonemizer + if (lineTextBeforeChangedCulture is not null && LineText is not null && + LineText.Equals(lineTextBeforeChangedCulture)) { - l.ShowBackUp = true; // restore backup MResults - cultureChangedFirst = false; + if (cultureChangedFirst) + { + l.ShowBackUp = true; // restore backup MResults + cultureChangedFirst = false; + } + + } else { diff --git a/Mirivoice/ViewModels/MainViewModel.cs b/Mirivoice/ViewModels/MainViewModel.cs index 4e8f9fa..1ac8ecf 100644 --- a/Mirivoice/ViewModels/MainViewModel.cs +++ b/Mirivoice/ViewModels/MainViewModel.cs @@ -627,6 +627,7 @@ public async void OnNewButtonClick() public MainViewModel(MainWindow window) : base(true) { MainManager.Instance.AudioM = new AudioManager(this); + MainManager.Instance.IconM = new IconManager(); MainManager.Instance.cmd.SetMainViewModel(this); diff --git a/Mirivoice/ViewModels/VoicerSelectingViewModelBase.cs b/Mirivoice/ViewModels/VoicerSelectingViewModelBase.cs index 7629a71..8869c29 100644 --- a/Mirivoice/ViewModels/VoicerSelectingViewModelBase.cs +++ b/Mirivoice/ViewModels/VoicerSelectingViewModelBase.cs @@ -15,6 +15,7 @@ public abstract class VoicerSelectingViewModelBase: ViewModelBase public bool NotProcessingSetDefVoicerCommand = false; protected int lastDefaultVoicerIndex; + public string _currentScript; public bool NotProcessingSetScriptCommand = false; protected string lastScript; diff --git a/Mirivoice/Views/LineBoxView.axaml.cs b/Mirivoice/Views/LineBoxView.axaml.cs index 2298a3c..bceda5f 100644 --- a/Mirivoice/Views/LineBoxView.axaml.cs +++ b/Mirivoice/Views/LineBoxView.axaml.cs @@ -133,8 +133,17 @@ private async Task PhonemizeLine(bool ApplyToCurrentEdit = true) { Log.Warning("Skip Phonemizing: phonemizer is not null, but LineText is empty"); } + else if (IsCacheIsVaild) + { + Log.Information("Skip Phonemizing: Cache is valid"); + } + else if (lastPhonemizedText.Equals(textChanged)) + { + Log.Information("Skip Phonemizing: text is not changed"); + } else { + Log.Debug("Phonemizing: {0}", textChanged); await Task.Run(() => viewModel.phonemizer.PhonemizeAsync(textChanged, this)); lastPhonemizedText = textChanged; } @@ -208,7 +217,7 @@ public LineBoxView(MainViewModel v, string line="") - public LineBoxView(MLinePrototype mLinePrototype, MainViewModel v, int index, int voicerIndex, int metaIndex) + public LineBoxView(MLinePrototype mLinePrototype, MainViewModel v, int index, int voicerIndex, int metaIndex, bool isDuplicating) { this.v = v; @@ -226,8 +235,12 @@ public LineBoxView(MLinePrototype mLinePrototype, MainViewModel v, int index, in if (mLinePrototype.PhonemeEdit is not null || mLinePrototype.PhonemeEdit.Length > 0) { this.MResultsCollection = new ObservableCollection( - mLinePrototype.PhonemeEdit.Select(m => new MResult(m)).ToArray() + mLinePrototype.PhonemeEdit.Select(m => new MResult(m, this)).ToArray() ); + if (!isDuplicating) + { + UpdateMResultsCollection(); + } ShouldPhonemize = false; } else @@ -345,8 +358,7 @@ await Dispatcher.UIThread.InvokeAsync(async () => DeActivatePhonemizer = false; ShouldPhonemize = true; - Task res = viewModel.phonemizer.ConvertToIPA(viewModel.LineText, DispatcherPriority.ApplicationIdle); - IPAText = await res; + await viewModel.phonemizer.GenerateIPAAsync(this); } if (viewModel.voicerSelector.CurrentVoicer is not null) @@ -513,6 +525,7 @@ private void OnDragStart(object sender, RoutedEventArgs e) { v.CurrentSingleLineEditor = singleLineEditorView; + UpdateMResultsCollection(); Log.Debug("CurrentLineBox: {0}", v.CurrentLineBox); Log.Debug("CurrentSingleLineTextBox: {0}", v.CurrentSingleLineEditor); diff --git a/Mirivoice/Views/PhonemeEditView.axaml b/Mirivoice/Views/PhonemeEditView.axaml index d3fb0a0..3bad7e6 100644 --- a/Mirivoice/Views/PhonemeEditView.axaml +++ b/Mirivoice/Views/PhonemeEditView.axaml @@ -38,6 +38,32 @@ VerticalAlignment="Center" MaxLines="1" TextAlignment="Center" UndoLimit="0" CornerRadius="0" HorizontalAlignment="Stretch" FontSize="22" IsEnabled="{Binding IsEditable}" MinHeight="48"/> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mirivoice/Views/PhonemeEditView.axaml.cs b/Mirivoice/Views/PhonemeEditView.axaml.cs index 59dc980..ff8a3db 100644 --- a/Mirivoice/Views/PhonemeEditView.axaml.cs +++ b/Mirivoice/Views/PhonemeEditView.axaml.cs @@ -1,5 +1,7 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Mirivoice.Mirivoice.Core.Format; +using Mirivoice.ViewModels; namespace Mirivoice; diff --git a/Mirivoice/Views/VoicersStyleBox.axaml.cs b/Mirivoice/Views/VoicersStyleBox.axaml.cs index b169190..44072df 100644 --- a/Mirivoice/Views/VoicersStyleBox.axaml.cs +++ b/Mirivoice/Views/VoicersStyleBox.axaml.cs @@ -9,6 +9,7 @@ using Mirivoice.ViewModels; using System; using System.IO; +using Serilog; namespace Mirivoice; @@ -45,6 +46,11 @@ private void InitializeComponent() public async void OnSamplePlayButtonClick(object sender, RoutedEventArgs e) { + if (phrase is null || phrase.Trim().Equals(string.Empty)) + { + Log.Warning("Current phrase is null or empty. Skip sample play."); + return; + } if (MainManager.Instance.AudioM.IsPlaying) { v.StopAudio();