Skip to content

Commit 0c8466f

Browse files
committed
enable sounds for sound objects
1 parent 393f0ae commit 0c8466f

File tree

4 files changed

+242
-43
lines changed

4 files changed

+242
-43
lines changed

AvaGui/AvaGui.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<PackageReference Include="bodong.Avalonia.PropertyGrid" Version="11.0.10.1" />
4444
<PackageReference Include="bodong.PropertyModels" Version="11.0.10.1" />
4545
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
46+
<PackageReference Include="NAudio" Version="2.2.1" />
4647
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
4748
<PackageReference Include="ReactiveUI.Validation" Version="4.0.9" />
4849
<PackageReference Include="Zenith.Core" Version="1.0.20" />

AvaGui/ViewModels/ImageTableViewModel.cs

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,183 @@
1515
using System.IO;
1616
using System.Threading.Tasks;
1717
using System.Windows.Input;
18+
using OpenLoco.ObjectEditor.Types;
19+
using System.ComponentModel;
20+
using Core.Objects.Sound;
21+
using System.Collections.ObjectModel;
22+
using OpenLoco.ObjectEditor.Data;
23+
using NAudio.Wave;
24+
using System.Threading;
1825

1926
namespace AvaGui.ViewModels
2027
{
21-
public class ImageTableViewModel : ReactiveObject
28+
public interface IExtraContentViewModel
29+
{ }
30+
31+
public record UiSoundObject(string SoundName)
32+
{
33+
public UiSoundObject(string soundName, RiffWavHeader header, byte[] data) : this(soundName)
34+
{
35+
Header = header;
36+
Data = data;
37+
Duration = $"{data.Length / (decimal)header.ByteRate:0.#}s";
38+
}
39+
40+
public string Duration { get; init; }
41+
42+
public RiffWavHeader Header { get; set; }
43+
public byte[] Data { get; set; }
44+
}
45+
46+
public class SoundViewModel : ReactiveObject, IExtraContentViewModel
47+
{
48+
ILocoObject parent; // currently not needed
49+
50+
WaveOutEvent? CurrentWOEvent { get; set; }
51+
52+
public SoundViewModel(ILocoObject parent)
53+
{
54+
if (parent.Object is not SoundObject soundObject)
55+
{
56+
return;
57+
}
58+
59+
this.parent = parent;
60+
61+
var hdr = soundObject.SoundObjectData.PcmHeader;
62+
var text = parent.StringTable.Table["Name"][LanguageId.English_UK] ?? "<null>";
63+
Sound = new UiSoundObject(text, SawyerStreamWriter.WaveFormatExToRiff(hdr, soundObject.PcmData.Length), soundObject.PcmData);
64+
65+
PlaySoundCommand = ReactiveCommand.Create(PlaySound);
66+
PauseSoundCommand = ReactiveCommand.Create(() => CurrentWOEvent?.Pause());
67+
StopSoundCommand = ReactiveCommand.Create(() => CurrentWOEvent?.Stop());
68+
69+
//ImportSoundCommand = ReactiveCommand.Create(ImportSound);
70+
//ExportSoundCommand = ReactiveCommand.Create(ExportSound);
71+
}
72+
73+
74+
public void PlaySound()
75+
{
76+
if (CurrentWOEvent != null)
77+
{
78+
if (CurrentWOEvent.PlaybackState == PlaybackState.Playing)
79+
{
80+
return;
81+
}
82+
83+
if (CurrentWOEvent.PlaybackState == PlaybackState.Paused)
84+
{
85+
CurrentWOEvent.Play();
86+
return;
87+
}
88+
}
89+
90+
// do it asyncly to a) give user ui control and b) allow multiple sounds to play at once
91+
_ = Task.Run(() =>
92+
{
93+
if (CurrentWOEvent?.PlaybackState == PlaybackState.Stopped)
94+
{
95+
Thread.Sleep(100); // give time to wait until previous sound is disposed
96+
}
97+
98+
CurrentWOEvent?.Dispose();
99+
100+
using (var ms = new MemoryStream(Sound.Data))
101+
using (var rs = new RawSourceWaveStream(ms, new WaveFormat((int)Sound.Header.SampleRate, Sound.Header.BitsPerSample, Sound.Header.NumberOfChannels)))
102+
using (CurrentWOEvent = new WaveOutEvent())
103+
{
104+
CurrentWOEvent.Init(rs);
105+
CurrentWOEvent.Play();
106+
107+
// need to wait for music to finish
108+
while (CurrentWOEvent?.PlaybackState != PlaybackState.Stopped)
109+
{
110+
if (CurrentWOEvent == null)
111+
{
112+
break;
113+
}
114+
Thread.Sleep(50);
115+
}
116+
}
117+
118+
CurrentWOEvent = null;
119+
});
120+
}
121+
122+
[Reactive]
123+
public UiSoundObject Sound { get; set; }
124+
125+
[Reactive]
126+
public ICommand PlaySoundCommand { get; set; }
127+
128+
[Reactive]
129+
public ICommand PauseSoundCommand { get; set; }
130+
131+
[Reactive]
132+
public ICommand StopSoundCommand { get; set; }
133+
134+
//public async Task ImportSound()
135+
//{
136+
// var folders = await PlatformSpecific.OpenFolderPicker();
137+
// var dir = folders.FirstOrDefault();
138+
// if (dir == null)
139+
// {
140+
// return;
141+
// }
142+
143+
// var dirPath = dir.Path.LocalPath;
144+
// if (Directory.Exists(dirPath) && Directory.EnumerateFiles(dirPath).Any())
145+
// {
146+
// var files = Directory.GetFiles(dirPath);
147+
// var sorted = files.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f).Split('-')[0]));
148+
149+
// var g1Elements = new List<G1Element32>();
150+
// var i = 0;
151+
// //foreach (var file in sorted)
152+
// //{
153+
// // var img = SixLabors.ImageSharp.Image.Load<Rgb24>(file);
154+
// // var data = PaletteMap.ConvertRgb24ImageToG1Data(img);
155+
// // var hasTransparency = data.Any(b => b == 0);
156+
// // var oldImage = Parent.G1Elements[i++];
157+
// // oldImage.ImageData = PaletteMap.ConvertRgb24ImageToG1Data(img); // simply overwrite existing pixel data
158+
// //}
159+
// }
160+
161+
// //this.RaisePropertyChanged(nameof(Images));
162+
//}
163+
164+
//public async Task ExportSound()
165+
//{
166+
// var folders = await PlatformSpecific.OpenFolderPicker();
167+
// var dir = folders.FirstOrDefault();
168+
// if (dir == null)
169+
// {
170+
// return;
171+
// }
172+
173+
// var dirPath = dir.Path.LocalPath;
174+
// if (Directory.Exists(dirPath))
175+
// {
176+
// var counter = 0;
177+
// //foreach (var image in Images)
178+
// //{
179+
// // var imageName = counter++.ToString(); // todo: use GetImageName from winforms project
180+
// // var path = Path.Combine(dir.Path.LocalPath, $"{imageName}.png");
181+
// // //logger.Debug($"Saving image to {path}");
182+
// // image.Save(path);
183+
// //}
184+
// }
185+
//}
186+
187+
//[Reactive]
188+
//public ICommand ImportSoundCommand { get; set; }
189+
190+
//[Reactive]
191+
//public ICommand ExportSoundCommand { get; set; }
192+
}
193+
194+
public class ImageTableViewModel : ReactiveObject, IExtraContentViewModel
22195
{
23196
ILocoObject Parent;
24197

AvaGui/ViewModels/ObjectEditorViewModel.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using OpenLoco.ObjectEditor.Logging;
1111
using System.Linq;
1212
using System.Threading.Tasks;
13+
using Core.Objects.Sound;
1314

1415
namespace AvaGui.ViewModels
1516
{
@@ -23,7 +24,7 @@ public class ObjectEditorViewModel : ReactiveObject
2324
public StringTableViewModel? StringTableViewModel { get; set; }
2425

2526
[Reactive]
26-
public ImageTableViewModel? ImageTableViewModel { get; set; }
27+
public IExtraContentViewModel? ExtraContentViewModel { get; set; }
2728

2829
ObjectEditorModel Model { get; }
2930

@@ -65,12 +66,14 @@ public void SelectedObjectChanged()
6566
if (CurrentObject?.LocoObject != null)
6667
{
6768
StringTableViewModel = new(CurrentObject.LocoObject.StringTable);
68-
ImageTableViewModel = new(CurrentObject.LocoObject, Model.PaletteMap);
69+
ExtraContentViewModel = CurrentObject.LocoObject.Object is SoundObject
70+
? new SoundViewModel(CurrentObject.LocoObject)
71+
: new ImageTableViewModel(CurrentObject.LocoObject, Model.PaletteMap);
6972
}
7073
else
7174
{
7275
StringTableViewModel = null;
73-
ImageTableViewModel = null;
76+
ExtraContentViewModel = null;
7477
}
7578
}
7679

AvaGui/Views/MainWindow.axaml

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -108,45 +108,8 @@
108108
</ListBox>
109109
</StackPanel>
110110
</TabItem>
111-
<TabItem Header="Image Table">
112-
<DockPanel DataContext="{Binding ImageTableViewModel}">
113-
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
114-
<Button Grid.Row="3" BorderBrush="Gray" Command="{Binding ImportImagesCommand}" HorizontalAlignment="Stretch" Margin="2" Padding="2" ToolTip.Tip="Import from Directory">
115-
<DockPanel>
116-
<materialIcons:MaterialIcon Kind="FolderUpload" Width="24" Height="24" Margin="2" />
117-
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="4">Import from Directory</TextBlock>
118-
</DockPanel>
119-
</Button>
120-
<Button Grid.Row="3" BorderBrush="Gray" Command="{Binding ExportImagesCommand}" HorizontalAlignment="Stretch" Margin="2" Padding="2" ToolTip.Tip="Export to Directory">
121-
<DockPanel>
122-
<materialIcons:MaterialIcon Kind="FolderDownload" Width="24" Height="24" Margin="2" />
123-
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="4">Export to Directory</TextBlock>
124-
</DockPanel>
125-
</Button>
126-
<!--<Label VerticalAlignment="Center">🔍 Image Scaling (1-10):</Label>
127-
<NumericUpDown Value="{Binding Zoom}" FormatString="0" Watermark="1" Width="108" Minimum="1" Maximum="10" />-->
128-
</StackPanel>
129-
<ItemsControl ItemsSource="{Binding Images, Mode=OneWay}">
130-
<ItemsControl.ItemsPanel>
131-
<ItemsPanelTemplate>
132-
<WrapPanel/>
133-
</ItemsPanelTemplate>
134-
</ItemsControl.ItemsPanel>
135-
<ItemsControl.ItemTemplate>
136-
<DataTemplate>
137-
<ContentControl Padding="2" >
138-
<Border BorderBrush="Gray" Background="Magenta">
139-
<Image Source="{Binding}" Stretch="None" Margin="0">
140-
<!--<Image.RenderTransform>
141-
<ScaleTransform ScaleX="{Binding Zoom}" ScaleY="{Binding Zoom}" />
142-
</Image.RenderTransform>-->
143-
</Image>
144-
</Border>
145-
</ContentControl>
146-
</DataTemplate>
147-
</ItemsControl.ItemTemplate>
148-
</ItemsControl>
149-
</DockPanel>
111+
<TabItem Header="Image/Sound Table">
112+
<ContentControl Content="{Binding ExtraContentViewModel}"/>
150113
</TabItem>
151114
</TabControl>
152115
</StackPanel>
@@ -160,6 +123,65 @@
160123
<!--<pgc:PropertyGrid x:Name="propertyGrid_0" Margin="2" DataContext="{Binding CurrentFileName}" DockPanel.Dock="Top" ShowTitle="False" AllowFilter="False" AllowQuickFilter="False"></pgc:PropertyGrid>-->
161124
</DataTemplate>
162125

126+
<DataTemplate DataType="vm:ImageTableViewModel">
127+
<DockPanel>
128+
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
129+
<Button Grid.Row="3" BorderBrush="Gray" Command="{Binding ImportImagesCommand}" HorizontalAlignment="Stretch" Margin="2" Padding="2" ToolTip.Tip="Import from Directory">
130+
<DockPanel>
131+
<materialIcons:MaterialIcon Kind="FolderUpload" Width="24" Height="24" Margin="2" />
132+
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="4">Import from Directory</TextBlock>
133+
</DockPanel>
134+
</Button>
135+
<Button Grid.Row="3" BorderBrush="Gray" Command="{Binding ExportImagesCommand}" HorizontalAlignment="Stretch" Margin="2" Padding="2" ToolTip.Tip="Export to Directory">
136+
<DockPanel>
137+
<materialIcons:MaterialIcon Kind="FolderDownload" Width="24" Height="24" Margin="2" />
138+
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="4">Export to Directory</TextBlock>
139+
</DockPanel>
140+
</Button>
141+
<!--<Label VerticalAlignment="Center">🔍 Image Scaling (1-10):</Label>
142+
<NumericUpDown Value="{Binding Zoom}" FormatString="0" Watermark="1" Width="108" Minimum="1" Maximum="10" />-->
143+
</StackPanel>
144+
<ItemsControl ItemsSource="{Binding Images, Mode=OneWay}">
145+
<ItemsControl.ItemsPanel>
146+
<ItemsPanelTemplate>
147+
<WrapPanel/>
148+
</ItemsPanelTemplate>
149+
</ItemsControl.ItemsPanel>
150+
<ItemsControl.ItemTemplate>
151+
<DataTemplate>
152+
<ContentControl Padding="2" >
153+
<Border BorderBrush="Gray" Background="Magenta">
154+
<Image Source="{Binding}" Stretch="None" Margin="0">
155+
<!--<Image.RenderTransform>
156+
<ScaleTransform ScaleX="{Binding Zoom}" ScaleY="{Binding Zoom}" />
157+
</Image.RenderTransform>-->
158+
</Image>
159+
</Border>
160+
</ContentControl>
161+
</DataTemplate>
162+
</ItemsControl.ItemTemplate>
163+
</ItemsControl>
164+
</DockPanel>
165+
</DataTemplate>
166+
167+
<DataTemplate DataType="vm:SoundViewModel">
168+
<Border BorderThickness="1" BorderBrush="Gray" CornerRadius="4">
169+
<StackPanel Orientation="Vertical" >
170+
<StackPanel Margin="4" Orientation="Horizontal">
171+
<TextBlock Text="{Binding Sound.SoundName}" VerticalAlignment="Center" Margin="4"/>
172+
<TextBlock Text="{Binding Sound.Duration}" VerticalAlignment="Center" Margin="4"/>
173+
</StackPanel>
174+
<StackPanel Orientation="Horizontal" Margin="4">
175+
<Button Command="{Binding PlaySoundCommand}">Play</Button>
176+
<Button Command="{Binding PauseSoundCommand}">Pause</Button>
177+
<Button Command="{Binding StopSoundCommand}">Stop</Button>
178+
<!--<Button>Export</Button>
179+
<Button>Import</Button>-->
180+
</StackPanel>
181+
</StackPanel>
182+
</Border>
183+
</DataTemplate>
184+
163185
</Window.DataTemplates>
164186

165187
<DockPanel>

0 commit comments

Comments
 (0)