Skip to content

Commit 6577b77

Browse files
authored
Add music UI (#125)
* working music and sfx viewmodels * add exporting sounds * add importing sounds, saving the dat files * remove old files, fix duration calculation * add friendly name for music tracks
1 parent 29d4b0f commit 6577b77

File tree

12 files changed

+273
-173
lines changed

12 files changed

+273
-173
lines changed

Dat/FileParsing/SawyerStreamReader.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,9 @@ public static ILocoStruct GetLocoStruct(ObjectType objectType, ReadOnlySpan<byte
502502
_ => throw new InvalidDataException("Unknown chunk encoding scheme"),
503503
};
504504

505+
public static (RiffWavHeader header, byte[] data) LoadWavFile(string filename)
506+
=> LoadWavFile(File.ReadAllBytes(filename));
507+
505508
public static (RiffWavHeader header, byte[] data) LoadWavFile(byte[] data)
506509
{
507510
using (var ms = new MemoryStream(data))
@@ -531,6 +534,9 @@ public static ReadOnlySpan<byte> ReadChunkCore(ref ReadOnlySpan<byte> data)
531534
return Decode(chunk.Encoding, chunkBytes);
532535
}
533536

537+
public static List<(WaveFormatEx header, byte[] data)> LoadSoundEffectsFromCSS(string filename)
538+
=> LoadSoundEffectsFromCSS(File.ReadAllBytes(filename));
539+
534540
public static List<(WaveFormatEx header, byte[] data)> LoadSoundEffectsFromCSS(byte[] data)
535541
{
536542
var result = new List<(WaveFormatEx, byte[])>();

Dat/Types/RiffWavHeader.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public record RiffWavHeader(
1818
[property: LocoStructOffset(0x24)] uint32_t DataMarker,
1919
[property: LocoStructOffset(0x28)] uint32_t DataLength) : ILocoStruct
2020
{
21+
public uint BytesPerSecond
22+
=> SampleRate * BitsPerSample * NumberOfChannels / 8;
23+
2124
public bool Validate()
2225
{
2326
if (Signature != 0x46464952) // "RIFF"

Gui/Models/UiSoundObject.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

Gui/Models/UiSoundObjectList.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

Gui/PlatformSpecific.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public static async Task<IReadOnlyList<IStorageFolder>> OpenFolderPicker()
7171
public static readonly IReadOnlyList<FilePickerFileType> DatFileTypes = [new("Locomotion DAT Files") { Patterns = ["*.dat", "*.DAT"] }];
7272
public static readonly IReadOnlyList<FilePickerFileType> PngFileTypes = [new("PNG Files") { Patterns = ["*.png", "*.PNG"] }];
7373
public static readonly IReadOnlyList<FilePickerFileType> SCV5FileTypes = [new("SC5/SV5 Files") { Patterns = ["*.sc5", "*.SC5", "*.sv5", "*.SV5"] }];
74+
public static readonly IReadOnlyList<FilePickerFileType> WavFileTypes = [new("WAV Files") { Patterns = ["*.wav", "*.WAV"] }];
7475

7576
public static async Task<IReadOnlyList<IStorageFile>> OpenFilePicker(IReadOnlyList<FilePickerFileType> filetypes)
7677
{
@@ -83,13 +84,13 @@ public static async Task<IReadOnlyList<IStorageFile>> OpenFilePicker(IReadOnlyLi
8384

8485
return await provider.OpenFilePickerAsync(new FilePickerOpenOptions()
8586
{
86-
Title = "Select a Locomotion object file",
87+
Title = "Select a file",
8788
AllowMultiple = false,
8889
FileTypeFilter = filetypes,
8990
});
9091
}
9192

92-
public static async Task<IStorageFile?> SaveFilePicker()
93+
public static async Task<IStorageFile?> SaveFilePicker(IReadOnlyList<FilePickerFileType> filetypes)
9394
{
9495
// See IoCFileOps project for an example of how to accomplish this.
9596
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop
@@ -101,8 +102,8 @@ public static async Task<IReadOnlyList<IStorageFile>> OpenFilePicker(IReadOnlyLi
101102
return await provider.SaveFilePickerAsync(new FilePickerSaveOptions()
102103
{
103104
ShowOverwritePrompt = true,
104-
DefaultExtension = "dat",
105105
Title = "Select a new file name",
106+
FileTypeChoices = filetypes,
106107
});
107108
}
108109
}

Gui/ViewModels/DatTypes/DatObjectEditorViewModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ public override void Load()
9595

9696
var imageNameProvider = (CurrentObject.LocoObject.Object is IImageTableNameProvider itnp) ? itnp : new DefaultImageTableNameProvider();
9797
StringTableViewModel = new(CurrentObject.LocoObject.StringTable);
98-
ExtraContentViewModel = CurrentObject.LocoObject.Object is SoundObject
99-
? new SoundViewModel(CurrentObject.LocoObject)
98+
ExtraContentViewModel = CurrentObject.LocoObject.Object is SoundObject soundObject
99+
? new SoundViewModel(CurrentObject.DatFileInfo.S5Header.Name, soundObject.SoundObjectData.PcmHeader, soundObject.PcmData)
100100
: new ImageTableViewModel(CurrentObject.LocoObject, imageNameProvider, Model.PaletteMap, CurrentObject.Images, Model.Logger);
101101

102102
var (treeView, annotationIdentifiers) = AnnotateFile(Path.Combine(Model.Settings.ObjDataDirectory, CurrentFile.Filename), Logger, false);
@@ -132,7 +132,7 @@ public override void Save()
132132

133133
public override void SaveAs()
134134
{
135-
var saveFile = Task.Run(PlatformSpecific.SaveFilePicker).Result;
135+
var saveFile = Task.Run(async () => await PlatformSpecific.SaveFilePicker(PlatformSpecific.DatFileTypes)).Result;
136136
if (saveFile != null)
137137
{
138138
SaveCore(saveFile.Path.LocalPath);

Gui/ViewModels/DatTypes/G1ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public override void SaveAs()
7171
return;
7272
}
7373

74-
var saveFile = Task.Run(PlatformSpecific.SaveFilePicker).Result;
74+
var saveFile = Task.Run(async () => await PlatformSpecific.SaveFilePicker(PlatformSpecific.DatFileTypes)).Result;
7575
if (saveFile == null)
7676
{
7777
return;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using OpenLoco.Dat.Data;
2+
using OpenLoco.Dat.FileParsing;
3+
using OpenLoco.Gui.Models;
4+
using ReactiveUI.Fody.Helpers;
5+
using System.IO;
6+
using System.Threading.Tasks;
7+
8+
namespace OpenLoco.Gui.ViewModels
9+
{
10+
public class MusicViewModel : BaseLocoFileViewModel
11+
{
12+
public MusicViewModel(FileSystemItem currentFile, ObjectEditorModel model)
13+
: base(currentFile, model)
14+
=> Load();
15+
16+
public override void Load()
17+
{
18+
var (header, data) = SawyerStreamReader.LoadWavFile(CurrentFile.Filename);
19+
SoundViewModel = new SoundViewModel(
20+
GetDisplayName(CurrentFile.DisplayName),
21+
header,
22+
data);
23+
}
24+
25+
string GetDisplayName(string filename)
26+
{
27+
if (OriginalDataFiles.Music.TryGetValue(CurrentFile.DisplayName, out var musicName))
28+
{
29+
return $"{musicName} ({CurrentFile.DisplayName})";
30+
}
31+
32+
if (OriginalDataFiles.MiscellaneousTracks.TryGetValue(CurrentFile.DisplayName, out var miscTrackName))
33+
{
34+
return $"{miscTrackName} ({CurrentFile.DisplayName})";
35+
}
36+
37+
return CurrentFile.DisplayName;
38+
}
39+
40+
[Reactive]
41+
public SoundViewModel SoundViewModel { get; set; }
42+
43+
public override void Save()
44+
{
45+
var savePath = CurrentFile.FileLocation == FileLocation.Local
46+
? Path.Combine(Model.Settings.DataDirectory, CurrentFile.Filename)
47+
: Path.Combine(Model.Settings.DownloadFolder, Path.ChangeExtension(CurrentFile.DisplayName, ".dat"));
48+
49+
Logger?.Info($"Saving music to {savePath}");
50+
var bytes = SawyerStreamWriter.SaveMusicToDat(SoundViewModel.Header, SoundViewModel.Data);
51+
File.WriteAllBytes(savePath, bytes);
52+
}
53+
54+
public override void SaveAs()
55+
{
56+
var saveFile = Task.Run(async () => await PlatformSpecific.SaveFilePicker(PlatformSpecific.DatFileTypes)).Result;
57+
if (saveFile == null)
58+
{
59+
return;
60+
}
61+
62+
var savePath = saveFile.Path.LocalPath;
63+
Logger?.Info($"Saving music to {savePath}");
64+
var bytes = SawyerStreamWriter.SaveMusicToDat(SoundViewModel.Header, SoundViewModel.Data);
65+
File.WriteAllBytes(savePath, bytes);
66+
}
67+
}
68+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using OpenLoco.Dat.Data;
2+
using OpenLoco.Dat.FileParsing;
3+
using OpenLoco.Gui.Models;
4+
using PropertyModels.Extensions;
5+
using ReactiveUI.Fody.Helpers;
6+
using System;
7+
using System.ComponentModel;
8+
using System.IO;
9+
using System.Linq;
10+
using System.Threading.Tasks;
11+
12+
namespace OpenLoco.Gui.ViewModels
13+
{
14+
public class SoundEffectsViewModel : BaseLocoFileViewModel
15+
{
16+
public SoundEffectsViewModel(FileSystemItem currentFile, ObjectEditorModel model)
17+
: base(currentFile, model) => Load();
18+
19+
public override void Load()
20+
{
21+
var soundIdNames = Enum.GetValues<SoundId>();
22+
SoundViewModels = SawyerStreamReader.LoadSoundEffectsFromCSS(CurrentFile.Filename)
23+
.Select((x, i) => new SoundViewModel(soundIdNames[i].ToString(), x.header, x.data))
24+
.ToBindingList();
25+
}
26+
27+
[Reactive]
28+
public BindingList<SoundViewModel> SoundViewModels { get; set; } = [];
29+
30+
public override void Save()
31+
{
32+
var savePath = CurrentFile.FileLocation == FileLocation.Local
33+
? Path.Combine(Model.Settings.DataDirectory, CurrentFile.Filename)
34+
: Path.Combine(Model.Settings.DownloadFolder, Path.ChangeExtension(CurrentFile.DisplayName, ".dat"));
35+
36+
Logger?.Info($"Saving sound effects to {savePath}");
37+
var bytes = SawyerStreamWriter.SaveSoundEffectsToCSS(SoundViewModels.Select(x => (x.Header, x.Data)).ToList());
38+
File.WriteAllBytes(savePath, bytes);
39+
}
40+
41+
public override void SaveAs()
42+
{
43+
var saveFile = Task.Run(async () => await PlatformSpecific.SaveFilePicker(PlatformSpecific.DatFileTypes)).Result;
44+
if (saveFile == null)
45+
{
46+
return;
47+
}
48+
49+
var savePath = saveFile.Path.LocalPath;
50+
Logger?.Info($"Saving sound effects to {savePath}");
51+
var bytes = SawyerStreamWriter.SaveSoundEffectsToCSS(SoundViewModels.Select(x => (x.Header, x.Data)).ToList());
52+
File.WriteAllBytes(savePath, bytes);
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)