From 12d950382afc5dadfcf58166335b38b4e262d4f0 Mon Sep 17 00:00:00 2001
From: Jonko <69772986+jonko0493@users.noreply.github.com>
Date: Sat, 25 Jan 2025 05:37:31 -0800
Subject: [PATCH] Update voiced line editor to account for new subtitle
features (#452)
---
src/SerialLoops.Lib/Items/VoicedLineItem.cs | 24 +++--
src/SerialLoops.Lib/SerialLoops.Lib.csproj | 4 +-
src/SerialLoops/Assets/Strings.Designer.cs | 63 ++++++++++++
src/SerialLoops/Assets/Strings.resx | 21 ++++
src/SerialLoops/SerialLoops.csproj | 4 +-
.../Editors/VoicedLineEditorViewModel.cs | 98 +++++++++++++++++--
.../Views/Editors/VoicedLineEditorView.axaml | 20 +++-
.../SerialLoops.Tests.Headless.csproj | 6 +-
.../SerialLoops.Tests.Shared.csproj | 2 +-
9 files changed, 215 insertions(+), 27 deletions(-)
diff --git a/src/SerialLoops.Lib/Items/VoicedLineItem.cs b/src/SerialLoops.Lib/Items/VoicedLineItem.cs
index ba68be6b..916efaeb 100644
--- a/src/SerialLoops.Lib/Items/VoicedLineItem.cs
+++ b/src/SerialLoops.Lib/Items/VoicedLineItem.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using HaruhiChokuretsuLib.Archive.Event;
using HaruhiChokuretsuLib.Audio.ADX;
using HaruhiChokuretsuLib.Util;
using NAudio.Flac;
@@ -24,7 +25,7 @@ public VoicedLineItem(string voiceFile, int index, Project project) : base(Path.
_vceFile = voiceFile;
Index = index;
}
-
+
public IWaveProvider GetWaveProvider(ILogger log, bool loop = false)
{
byte[] adxBytes = [];
@@ -61,7 +62,7 @@ public IWaveProvider GetWaveProvider(ILogger log, bool loop = false)
return new AdxWaveProvider(decoder);
}
- public void Replace(string audioFile, string baseDirectory, string iterativeDirectory, string vceCachedFile, ILogger log)
+ public void Replace(string audioFile, string baseDirectory, string iterativeDirectory, string vceCachedFile, ILogger log, VoiceMapFile.VoiceMapEntry vceMapEntry = null)
{
// The MP3 decoder is able to create wave files but for whatever reason messes with the ADX encoder
// So we just convert to WAV AOT
@@ -78,18 +79,21 @@ public void Replace(string audioFile, string baseDirectory, string iterativeDire
WaveFileWriter.CreateWaveFile(vceCachedFile, vorbisReader.ToSampleProvider().ToWaveProvider16());
audioFile = vceCachedFile;
}
+
using WaveStream audio = Path.GetExtension(audioFile).ToLower() switch
{
".wav" => new WaveFileReader(audioFile),
".flac" => new FlacReader(audioFile),
_ => null,
};
+
if (audio is null)
{
log.LogError("Invalid audio file selected.");
log.LogWarning(audioFile);
return;
}
+
if (audio.WaveFormat.Channels > 1 || audio.WaveFormat.SampleRate > SoundItem.MAX_SAMPLERATE)
{
string newAudioFile = "";
@@ -115,19 +119,25 @@ public void Replace(string audioFile, string baseDirectory, string iterativeDire
}
}
- log.Log($"Encoding audio to AHX...");
- audioFile = newAudioFile;
+ log.Log("Encoding audio to AHX...");
AdxUtil.EncodeWav(newAudioFile, Path.Combine(baseDirectory, VoiceFile), true);
}
else
{
- log.Log($"Encoding audio to AHX...");
+ log.Log("Encoding audio to AHX...");
AdxUtil.EncodeAudio(audio, Path.Combine(baseDirectory, VoiceFile), true);
}
File.Copy(Path.Combine(baseDirectory, VoiceFile), Path.Combine(iterativeDirectory, VoiceFile), true);
+
+ // Adjust subtitle length to new voice item length
+ if (vceMapEntry is not null)
+ {
+ vceMapEntry.Timer = (int)(audio.TotalTime.TotalSeconds * 180 + 30);
+ UnsavedChanges = true;
+ }
}
public override void Refresh(Project project, ILogger log)
{
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/src/SerialLoops.Lib/SerialLoops.Lib.csproj b/src/SerialLoops.Lib/SerialLoops.Lib.csproj
index 15cb300d..b7dc43f7 100644
--- a/src/SerialLoops.Lib/SerialLoops.Lib.csproj
+++ b/src/SerialLoops.Lib/SerialLoops.Lib.csproj
@@ -12,14 +12,14 @@
-
+
-
+
diff --git a/src/SerialLoops/Assets/Strings.Designer.cs b/src/SerialLoops/Assets/Strings.Designer.cs
index f5f13919..284314ec 100644
--- a/src/SerialLoops/Assets/Strings.Designer.cs
+++ b/src/SerialLoops/Assets/Strings.Designer.cs
@@ -2915,6 +2915,15 @@ public static string FADE_TO_CENTER {
}
}
+ ///
+ /// Looks up a localized string similar to Faded Gray.
+ ///
+ public static string FADED_GRAY {
+ get {
+ return ResourceManager.GetString("FADED_GRAY", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Failed attempting to cache audio file.
///
@@ -3626,6 +3635,15 @@ public static string Flags {
}
}
+ ///
+ /// Looks up a localized string similar to Force Drop Shadow.
+ ///
+ public static string Force_Drop_Shadow {
+ get {
+ return ResourceManager.GetString("Force Drop Shadow", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Frames.
///
@@ -3770,6 +3788,15 @@ public static string GIF_file {
}
}
+ ///
+ /// Looks up a localized string similar to Gray.
+ ///
+ public static string GRAY {
+ get {
+ return ResourceManager.GetString("GRAY", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Grayscale.
///
@@ -4330,6 +4357,15 @@ public static string Language_Template {
}
}
+ ///
+ /// Looks up a localized string similar to Lavender.
+ ///
+ public static string LAVENDER {
+ get {
+ return ResourceManager.GetString("LAVENDER", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Layout.
///
@@ -5124,6 +5160,15 @@ public static string Off {
}
}
+ ///
+ /// Looks up a localized string similar to Off White.
+ ///
+ public static string OFF_WHITE {
+ get {
+ return ResourceManager.GetString("OFF_WHITE", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to On.
///
@@ -5844,6 +5889,15 @@ public static string Recents {
}
}
+ ///
+ /// Looks up a localized string similar to Red.
+ ///
+ public static string RED {
+ get {
+ return ResourceManager.GetString("RED", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to References to {0}.
///
@@ -8568,6 +8622,15 @@ public static string XDelta_patch {
}
}
+ ///
+ /// Looks up a localized string similar to Yellow.
+ ///
+ public static string YELLOW {
+ get {
+ return ResourceManager.GetString("YELLOW", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Yes.
///
diff --git a/src/SerialLoops/Assets/Strings.resx b/src/SerialLoops/Assets/Strings.resx
index 0ca2232a..dc879eda 100644
--- a/src/SerialLoops/Assets/Strings.resx
+++ b/src/SerialLoops/Assets/Strings.resx
@@ -3086,4 +3086,25 @@ This action is irreversible.
Import Item Image
+
+ Force Drop Shadow
+
+
+ Yellow
+
+
+ Off White
+
+
+ Gray
+
+
+ Lavender
+
+
+ Red
+
+
+ Faded Gray
+
diff --git a/src/SerialLoops/SerialLoops.csproj b/src/SerialLoops/SerialLoops.csproj
index 3568c9a8..8daa9f95 100644
--- a/src/SerialLoops/SerialLoops.csproj
+++ b/src/SerialLoops/SerialLoops.csproj
@@ -73,13 +73,13 @@
-
+
-
+
diff --git a/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs b/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs
index 7d359b60..8b751658 100644
--- a/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs
+++ b/src/SerialLoops/ViewModels/Editors/VoicedLineEditorViewModel.cs
@@ -1,9 +1,12 @@
-using System.IO;
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Platform.Storage;
using HaruhiChokuretsuLib.Archive.Event;
+using HaruhiChokuretsuLib.Audio.ADX;
using HaruhiChokuretsuLib.Util;
using NAudio.Wave;
using ReactiveUI;
@@ -38,6 +41,23 @@ public class VoicedLineEditorViewModel : EditorViewModel
[Reactive]
public SKBitmap SubtitlesPreview { get; set; } = new(256, 384);
+ public ObservableCollection SubtitleColors { get; }
+ private LocalizedDialogueColor _subtitleColor;
+ public LocalizedDialogueColor SubtitleColor
+ {
+ get => _subtitleColor;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _subtitleColor, value);
+ if (_voiceMapEntry is not null)
+ {
+ _voiceMapEntry.Color = _subtitleColor.Color;
+ UpdatePreview();
+ Description.UnsavedChanges = true;
+ }
+ }
+ }
+
private DsScreen _subtitleScreen;
public DsScreen SubtitleScreen
{
@@ -47,13 +67,40 @@ public DsScreen SubtitleScreen
this.RaiseAndSetIfChanged(ref _subtitleScreen, value);
if (_voiceMapEntry is not null)
{
- _voiceMapEntry.TargetScreen = SubtitleScreen == DsScreen.BOTTOM ? VoiceMapEntry.Screen.BOTTOM : VoiceMapEntry.Screen.TOP;
+ _voiceMapEntry.TargetScreen = SubtitleScreen == DsScreen.BOTTOM ? VoiceMapEntry.Screen.BOTTOM : _forceDropShadow ? VoiceMapEntry.Screen.TOP_FORCE_SHADOW : VoiceMapEntry.Screen.TOP;
UpdatePreview();
Description.UnsavedChanges = true;
}
}
}
+ private bool _forceDropShadow;
+ public bool ForceDropShadow
+ {
+ get => _forceDropShadow;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _forceDropShadow, value);
+ if (_voiceMapEntry is not null)
+ {
+ if ((_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP ||
+ _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW) && _forceDropShadow)
+ {
+ _voiceMapEntry.TargetScreen = VoiceMapEntry.Screen.TOP_FORCE_SHADOW;
+ UpdatePreview();
+ Description.UnsavedChanges = true;
+ }
+ else if (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP ||
+ _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW)
+ {
+ _voiceMapEntry.TargetScreen = VoiceMapEntry.Screen.TOP;
+ UpdatePreview();
+ Description.UnsavedChanges = true;
+ }
+ }
+ }
+ }
+
private VoiceMapEntry.YPosition _yPos;
public bool TopY
@@ -121,12 +168,13 @@ public string Subtitle
this.RaiseAndSetIfChanged(ref _subtitle, _project.LangCode.Equals("ja") ? value : value.GetOriginalString(_project));
if (_voiceMapEntry is null)
{
+ AdxHeader header = new(File.ReadAllBytes(Path.Combine(_project.IterativeDirectory, _vce.VoiceFile)), _log);
_project.VoiceMap.VoiceMapEntries.Add(new()
{
VoiceFileName = Path.GetFileNameWithoutExtension(_vce.VoiceFile),
Color = DialogueColor.WHITE,
TargetScreen = SubtitleScreen == DsScreen.BOTTOM ? VoiceMapEntry.Screen.BOTTOM : VoiceMapEntry.Screen.TOP,
- Timer = 350,
+ Timer = (int)((double)header.TotalSamples / header.SampleRate * 180 + 30),
});
_project.VoiceMap.VoiceMapEntries[^1].SetSubtitle(_subtitle, _project.FontReplacement);
_project.VoiceMap.VoiceMapEntries[^1].YPos = _yPos;
@@ -160,8 +208,11 @@ public VoicedLineEditorViewModel(VoicedLineItem item, MainWindowViewModel window
_voiceMapEntry = _project.VoiceMap.VoiceMapEntries.FirstOrDefault(v => v.VoiceFileName.Equals(Path.GetFileNameWithoutExtension(_vce.VoiceFile)));
if (_voiceMapEntry is not null)
{
+ SubtitleColors = new(Enum.GetValues().Select(c => new LocalizedDialogueColor(c)));
+ _subtitleColor = SubtitleColors.First(c => c.Color == _voiceMapEntry.Color);
_subtitle = _voiceMapEntry.Subtitle;
_subtitleScreen = _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM ? DsScreen.BOTTOM : DsScreen.TOP;
+ _forceDropShadow = _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW;
_yPos = _voiceMapEntry.YPos;
UpdatePreview();
}
@@ -176,8 +227,10 @@ private async Task Replace()
{
LoopyProgressTracker tracker = new();
VcePlayer.Stop();
- await new ProgressDialog(() => _vce.Replace(openFile.Path.LocalPath, _project.BaseDirectory, _project.IterativeDirectory, Path.Combine(_project.Config.CachesDirectory, "vce", $"{_vce.Name}.wav"), _log),
+ await new ProgressDialog(() => _vce.Replace(openFile.Path.LocalPath, _project.BaseDirectory, _project.IterativeDirectory, Path.Combine(_project.Config.CachesDirectory, "vce", $"{_vce.Name}.wav"), _log,
+ _voiceMapEntry),
() => { }, tracker, Strings.Replace_voiced_line).ShowDialog(Window.Window);
+ VcePlayer.Stop();
}
}
@@ -192,7 +245,16 @@ private async Task Export()
private void Restore()
{
-
+ VcePlayer.Stop();
+ File.Copy(Path.Combine(_project.BaseDirectory, "original", "vce", Path.GetFileName(_vce.VoiceFile)), Path.Combine(_project.BaseDirectory, _vce.VoiceFile), true);
+ File.Copy(Path.Combine(_project.IterativeDirectory, "original", "vce", Path.GetFileName(_vce.VoiceFile)), Path.Combine(_project.IterativeDirectory, _vce.VoiceFile), true);
+ AdxHeader header = new(File.ReadAllBytes(Path.Combine(_project.IterativeDirectory, _vce.VoiceFile)), _log);
+ if (_voiceMapEntry is not null)
+ {
+ _voiceMapEntry.Timer = (int)((double)header.TotalSamples / header.SampleRate * 180 + 30);
+ _vce.UnsavedChanges = true;
+ }
+ VcePlayer.Stop();
}
private void UpdatePreview()
@@ -202,8 +264,7 @@ private void UpdatePreview()
canvas.DrawColor(SKColors.DarkGray);
canvas.DrawLine(new() { X = 0, Y = 192 }, new() { X = 256, Y = 192 }, DialogueScriptParameter.Paint00);
- bool bottomScreen = _voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM;
- if (bottomScreen)
+ if (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM)
{
for (int i = 0; i <= 1; i++)
{
@@ -212,11 +273,22 @@ private void UpdatePreview()
DialogueScriptParameter.Paint07,
_project,
i + _voiceMapEntry.X,
- 1 + _voiceMapEntry.Y + (bottomScreen ? 192 : 0),
+ 1 + _voiceMapEntry.Y + 192,
false
);
}
}
+ else if (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.TOP_FORCE_SHADOW)
+ {
+ canvas.DrawHaroohieText(
+ _subtitle,
+ DialogueScriptParameter.Paint07,
+ _project,
+ 1 + _voiceMapEntry.X,
+ 1 + _voiceMapEntry.Y,
+ false
+ );
+ }
canvas.DrawHaroohieText(
_subtitle,
@@ -234,10 +306,18 @@ private void UpdatePreview()
},
_project,
_voiceMapEntry.X,
- _voiceMapEntry.Y + (bottomScreen ? 192 : 0),
+ _voiceMapEntry.Y + (_voiceMapEntry.TargetScreen == VoiceMapEntry.Screen.BOTTOM ? 192 : 0),
false
);
canvas.Flush();
}
}
+
+public class LocalizedDialogueColor(DialogueColor color) : ReactiveObject
+{
+ [Reactive]
+ public DialogueColor Color { get; set; } = color;
+
+ public string DisplayText => Strings.ResourceManager.GetString(Color.ToString());
+}
diff --git a/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml b/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml
index d0db3e14..830c25fe 100644
--- a/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml
+++ b/src/SerialLoops/Views/Editors/VoicedLineEditorView.axaml
@@ -13,9 +13,9 @@
-
-
-
+
+
+
@@ -25,10 +25,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/SerialLoops.Tests.Headless/SerialLoops.Tests.Headless.csproj b/test/SerialLoops.Tests.Headless/SerialLoops.Tests.Headless.csproj
index feef566b..858e4347 100644
--- a/test/SerialLoops.Tests.Headless/SerialLoops.Tests.Headless.csproj
+++ b/test/SerialLoops.Tests.Headless/SerialLoops.Tests.Headless.csproj
@@ -13,14 +13,14 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/SerialLoops.Tests.Shared/SerialLoops.Tests.Shared.csproj b/test/SerialLoops.Tests.Shared/SerialLoops.Tests.Shared.csproj
index 8cc8ec3f..c3200537 100644
--- a/test/SerialLoops.Tests.Shared/SerialLoops.Tests.Shared.csproj
+++ b/test/SerialLoops.Tests.Shared/SerialLoops.Tests.Shared.csproj
@@ -7,7 +7,7 @@
-
+