Audio mixer window

This commit is contained in:
Washi 2017-08-20 21:47:05 +02:00
parent 9db0b5024d
commit 1b4df02938
6 changed files with 182 additions and 35 deletions

View File

@ -1,4 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Navigation;
using Emux.Gui;
namespace Emux namespace Emux
{ {
@ -11,7 +13,7 @@ namespace Emux
{ {
DeviceManager = new DeviceManager(); DeviceManager = new DeviceManager();
} }
public new static App Current public new static App Current
{ {
get { return (App) Application.Current; } get { return (App) Application.Current; }
@ -21,5 +23,7 @@ namespace Emux
{ {
get; get;
} }
} }
} }

View File

@ -1,28 +1,35 @@
using System.Collections; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using Emux.GameBoy.Audio; using Emux.GameBoy.Audio;
using NAudio.Wave; using NAudio.Wave;
namespace Emux.Audio namespace Emux.Audio
{ {
public class GameBoyAudioMixer : IWaveProvider public class GameBoyAudioMixer : IWaveProvider, INotifyPropertyChanged
{ {
public event PropertyChangedEventHandler PropertyChanged;
private readonly MixingWaveProvider32 _mixer = new MixingWaveProvider32(); private readonly MixingWaveProvider32 _mixer = new MixingWaveProvider32();
private bool _enabled = true;
private WaveFileWriter _writer;
private bool _isRecording;
public GameBoyAudioMixer() public GameBoyAudioMixer()
{ {
Channels = new List<NAudioChannelOutput> Channels = new List<NAudioChannelOutput>
{ {
new NAudioChannelOutput(this), new NAudioChannelOutput(this, "Square + Sweep"),
new NAudioChannelOutput(this), new NAudioChannelOutput(this, "Square"),
new NAudioChannelOutput(this), new NAudioChannelOutput(this, "Wave"),
new NAudioChannelOutput(this), new NAudioChannelOutput(this, "Noise"),
}.AsReadOnly(); }.AsReadOnly();
foreach (var channel in Channels) foreach (var channel in Channels)
_mixer.AddInputStream(channel); _mixer.AddInputStream(channel);
} }
public WaveFormat WaveFormat public WaveFormat WaveFormat
@ -35,20 +42,87 @@ namespace Emux.Audio
get; get;
} }
public bool IsRecording
{
get { return _isRecording; }
private set
{
if (_isRecording != value)
{
_isRecording = value;
OnPropertyChanged(nameof(IsRecording));
}
}
}
public bool Enabled
{
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
}
public int Read(byte[] buffer, int offset, int count) public int Read(byte[] buffer, int offset, int count)
{ {
_mixer.Read(buffer, offset, count); _mixer.Read(buffer, offset, count);
if (!Enabled)
Array.Clear(buffer, offset, count);
lock (this)
{
_writer?.Write(buffer, offset, count);
}
return count; return count;
} }
public void Connect(GameBoySpu spu) public void Connect(GameBoySpu spu)
{ {
for (var i = 0; i < spu.Channels.Count; i++) for (var i = 0; i < spu.Channels.Count; i++)
spu.Channels[i].ChannelOutput = Channels[i];
}
public void StartRecording(Stream outputStream)
{
if (outputStream == null)
throw new ArgumentNullException(nameof(outputStream));
lock (this)
{ {
var channel = spu.Channels[i]; if (IsRecording)
if (channel != null) // TODO: remove null check if all channels are implemented. throw new InvalidOperationException("Cannot start a recording when a recording is already happening.");
channel.ChannelOutput = Channels[i]; _writer = new WaveFileWriter(outputStream, WaveFormat);
IsRecording = true;
} }
} }
public void StopRecording()
{
lock (this)
{
if (IsRecording)
throw new InvalidOperationException("Cannot stop a recording when a recording is not happening.");
try
{
_writer.Flush();
}
finally
{
_writer.Dispose();
_writer = null;
IsRecording = false;
}
}
}
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
} }
} }

View File

@ -1,28 +1,47 @@
using System; using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Emux.GameBoy.Audio; using Emux.GameBoy.Audio;
using NAudio.Wave; using NAudio.Wave;
namespace Emux.Audio namespace Emux.Audio
{ {
public class NAudioChannelOutput : BufferedWaveProvider, IAudioChannelOutput public class NAudioChannelOutput : BufferedWaveProvider, IAudioChannelOutput, INotifyPropertyChanged
{ {
private readonly GameBoyAudioMixer _mixer; public event PropertyChangedEventHandler PropertyChanged;
public NAudioChannelOutput(GameBoyAudioMixer mixer) private readonly GameBoyAudioMixer _mixer;
private bool _enabled;
public NAudioChannelOutput(GameBoyAudioMixer mixer, string name)
: base(mixer.WaveFormat) : base(mixer.WaveFormat)
{ {
if (mixer == null) if (mixer == null)
throw new ArgumentNullException(nameof(mixer)); throw new ArgumentNullException(nameof(mixer));
_mixer = mixer; _mixer = mixer;
Name = name;
Enabled = true; Enabled = true;
} }
public bool Enabled public bool Enabled
{ {
get; get { return _enabled; }
set; set
{
if (_enabled != value)
{
_enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
} }
public string Name
{
get;
}
public int SampleRate public int SampleRate
{ {
get { return WaveFormat.SampleRate; } get { return WaveFormat.SampleRate; }
@ -35,5 +54,10 @@ namespace Emux.Audio
Buffer.BlockCopy(sampleData, offset * sizeof(float), newSampleData, 0, length * sizeof(float)); Buffer.BlockCopy(sampleData, offset * sizeof(float), newSampleData, 0, length * sizeof(float));
AddSamples(newSampleData, 0, newSampleData.Length); AddSamples(newSampleData, 0, newSampleData.Length);
} }
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
} }
} }

View File

@ -66,6 +66,9 @@
<DependentUpon>AboutDialog.xaml</DependentUpon> <DependentUpon>AboutDialog.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Audio\NAudioChannelOutput.cs" /> <Compile Include="Audio\NAudioChannelOutput.cs" />
<Compile Include="Gui\AudioMixerWindow.xaml.cs">
<DependentUpon>AudioMixerWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Gui\Converters\HexadecimalConverter.cs" /> <Compile Include="Gui\Converters\HexadecimalConverter.cs" />
<Compile Include="Gui\Converters\InverseConverter.cs" /> <Compile Include="Gui\Converters\InverseConverter.cs" />
<Compile Include="Gui\RegisterItem.cs" /> <Compile Include="Gui\RegisterItem.cs" />
@ -91,6 +94,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Gui\AudioMixerWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Gui\TitledOverlay.xaml"> <Page Include="Gui\TitledOverlay.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@ -5,7 +5,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Emux.Gui" xmlns:local="clr-namespace:Emux.Gui"
mc:Ignorable="d" mc:Ignorable="d"
Title="Emux" Height="440.245" Width="707.465" Closing="MainWindow_OnClosing"> Title="Emux" Height="440.245" Width="707.465" Closing="MainWindow_OnClosing"
x:Name="Root">
<Window.CommandBindings> <Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="OpenCommandOnExecuted" /> <CommandBinding Command="ApplicationCommands.Open" Executed="OpenCommandOnExecuted" />
<CommandBinding Command="local:MainWindow.RunCommand" CanExecute="PausingOnCanExecute" Executed="RunCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.RunCommand" CanExecute="PausingOnCanExecute" Executed="RunCommandOnExecuted" />
@ -17,7 +18,8 @@
<CommandBinding Command="local:MainWindow.KeyPadCommand" CanExecute="GameBoyExistsCanExecute" Executed="KeyPadCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.KeyPadCommand" CanExecute="GameBoyExistsCanExecute" Executed="KeyPadCommandOnExecuted" />
<CommandBinding Command="local:MainWindow.VideoOutputCommand" CanExecute="GameBoyExistsCanExecute" Executed="VideoOutputCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.VideoOutputCommand" CanExecute="GameBoyExistsCanExecute" Executed="VideoOutputCommandOnExecuted" />
<CommandBinding Command="local:MainWindow.IOMemoryCommand" CanExecute="GameBoyExistsCanExecute" Executed="IOMemoryCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.IOMemoryCommand" CanExecute="GameBoyExistsCanExecute" Executed="IOMemoryCommandOnExecuted" />
<CommandBinding Command="local:MainWindow.EnableSoundCommand" CanExecute="GameBoyExistsCanExecute" Executed="EnableSoundCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.MixerCommand" CanExecute="GameBoyExistsCanExecute" Executed="MixerCommandOnExecuted" />
<CommandBinding Command="local:MainWindow.EnableSoundCommand" CanExecute="GameBoyExistsCanExecute" />
<CommandBinding Command="local:MainWindow.SourceCodeCommand" Executed="SourceCodeCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.SourceCodeCommand" Executed="SourceCodeCommandOnExecuted" />
<CommandBinding Command="local:MainWindow.AboutCommand" Executed="AboutCommandOnExecuted" /> <CommandBinding Command="local:MainWindow.AboutCommand" Executed="AboutCommandOnExecuted" />
</Window.CommandBindings> </Window.CommandBindings>
@ -49,7 +51,8 @@
<MenuItem Header="Video output" Command="local:MainWindow.VideoOutputCommand"/> <MenuItem Header="Video output" Command="local:MainWindow.VideoOutputCommand"/>
<MenuItem Header="Virtual keypad" Command="local:MainWindow.KeyPadCommand"/> <MenuItem Header="Virtual keypad" Command="local:MainWindow.KeyPadCommand"/>
<MenuItem Header="IO memory view" Command="local:MainWindow.IOMemoryCommand"/> <MenuItem Header="IO memory view" Command="local:MainWindow.IOMemoryCommand"/>
<MenuItem x:Name="EnableSoundsMenuItem" Header="Enable sound" IsCheckable="True" IsChecked="False" Command="local:MainWindow.EnableSoundCommand" /> <MenuItem Header="Audio Mixer" Command="local:MainWindow.MixerCommand"/>
<MenuItem x:Name="EnableSoundsMenuItem" Header="Enable sound" IsCheckable="True" IsChecked="{Binding ElementName=Root, Path=DeviceManager.AudioMixer.Enabled}" Command="local:MainWindow.EnableSoundCommand" />
</MenuItem.Items> </MenuItem.Items>
</MenuItem> </MenuItem>
<MenuItem Header="Help"> <MenuItem Header="Help">

View File

@ -75,7 +75,11 @@ namespace Emux.Gui
public static readonly RoutedUICommand IOMemoryCommand = new RoutedUICommand( public static readonly RoutedUICommand IOMemoryCommand = new RoutedUICommand(
"Open the IO memory view", "Open the IO memory view",
"IO Memory", "IO Memory",
typeof(MainWindow)); typeof(MainWindow),
new InputGestureCollection(new[]
{
new KeyGesture(Key.F3)
}));
public static readonly RoutedUICommand KeyPadCommand = new RoutedUICommand( public static readonly RoutedUICommand KeyPadCommand = new RoutedUICommand(
"Open the virtual keypad window", "Open the virtual keypad window",
@ -86,6 +90,15 @@ namespace Emux.Gui
new KeyGesture(Key.F12) new KeyGesture(Key.F12)
})); }));
public static readonly RoutedUICommand MixerCommand = new RoutedUICommand(
"Open the audio mixer",
"Audio Mixer",
typeof(MainWindow),
new InputGestureCollection(new[]
{
new KeyGesture(Key.F4)
}));
public static readonly RoutedUICommand EnableSoundCommand = new RoutedUICommand( public static readonly RoutedUICommand EnableSoundCommand = new RoutedUICommand(
"Enable or disable sound", "Enable or disable sound",
"Enable sound", "Enable sound",
@ -105,22 +118,42 @@ namespace Emux.Gui
new KeyGesture(Key.F1) new KeyGesture(Key.F1)
})); }));
public static readonly DependencyProperty DeviceManagerProperty = DependencyProperty.Register(
"DeviceManager", typeof(DeviceManager), typeof(MainWindow),
new PropertyMetadata((o, e) => ((MainWindow) o).OnDeviceManagerChanged(e)));
private readonly VideoWindow _videoWindow; private readonly VideoWindow _videoWindow;
private readonly AudioMixerWindow _mixerWindow;
private readonly KeypadWindow _keypadWindow; private readonly KeypadWindow _keypadWindow;
private readonly IOWindow _ioWindow; private readonly IOWindow _ioWindow;
private GameBoy.GameBoy _currentDevice; private GameBoy.GameBoy _currentDevice;
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
_videoWindow = new VideoWindow(); _videoWindow = new VideoWindow();
_mixerWindow = new AudioMixerWindow();
_keypadWindow = new KeypadWindow(); _keypadWindow = new KeypadWindow();
_ioWindow = new IOWindow(); _ioWindow = new IOWindow();
App.Current.DeviceManager.DeviceChanged += DeviceManagerOnDeviceChanged; DeviceManager = App.Current.DeviceManager;
} }
private void DeviceManagerOnDeviceChanged(object sender, EventArgs e) public DeviceManager DeviceManager
{
get { return (DeviceManager) GetValue(DeviceManagerProperty); }
set { SetValue(DeviceManagerProperty, value); }
}
private void OnDeviceManagerChanged(DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
((DeviceManager) e.OldValue).DeviceChanged -= OnDeviceChanged;
if (e.NewValue != null)
((DeviceManager) e.NewValue).DeviceChanged += OnDeviceChanged;
}
private void OnDeviceChanged(object sender, EventArgs e)
{ {
if (_currentDevice != null) if (_currentDevice != null)
{ {
@ -129,7 +162,7 @@ namespace Emux.Gui
RunningOverlay.DisableOverlay(); RunningOverlay.DisableOverlay();
} }
_currentDevice = App.Current.DeviceManager.CurrentDevice; _currentDevice = DeviceManager.CurrentDevice;
_currentDevice.Cpu.Paused += GameBoyOnPaused; _currentDevice.Cpu.Paused += GameBoyOnPaused;
_currentDevice.Cpu.Resumed += GameBoyOnResumed; _currentDevice.Cpu.Resumed += GameBoyOnResumed;
_currentDevice.Gpu.VideoOutput = _videoWindow; _currentDevice.Gpu.VideoOutput = _videoWindow;
@ -137,15 +170,11 @@ namespace Emux.Gui
_videoWindow.Device = _currentDevice; _videoWindow.Device = _currentDevice;
_keypadWindow.Device = _currentDevice; _keypadWindow.Device = _currentDevice;
_ioWindow.Device = _currentDevice; _ioWindow.Device = _currentDevice;
_mixerWindow.Mixer = DeviceManager.AudioMixer;
_videoWindow.Show(); _videoWindow.Show();
RefreshView(); RefreshView();
if (EnableSoundsMenuItem.IsChecked)
_currentDevice.Spu.ActivateAllChannels();
else
_currentDevice.Spu.DeactivateAllChannels();
} }
public void RefreshView() public void RefreshView()
@ -301,22 +330,28 @@ namespace Emux.Gui
_videoWindow.Device = null; _videoWindow.Device = null;
_keypadWindow.Device = null; _keypadWindow.Device = null;
_ioWindow.Device = null; _ioWindow.Device = null;
_mixerWindow.Mixer = null;
_videoWindow.Close(); _videoWindow.Close();
_keypadWindow.Close(); _keypadWindow.Close();
_ioWindow.Close(); _ioWindow.Close();
_mixerWindow.Close();
} }
private void EnableSoundCommandOnExecuted(object sender, ExecutedRoutedEventArgs e) private void EnableSoundCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{ {
if (EnableSoundsMenuItem.IsChecked) App.Current.DeviceManager.AudioMixer.Enabled = EnableSoundsMenuItem.IsChecked;
_currentDevice.Spu.ActivateAllChannels();
else
_currentDevice.Spu.DeactivateAllChannels();
} }
private void IOMemoryCommandOnExecuted(object sender, ExecutedRoutedEventArgs e) private void IOMemoryCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{ {
_ioWindow.Show(); _ioWindow.Show();
_ioWindow.Focus();
}
private void MixerCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
_mixerWindow.Show();
_mixerWindow.Focus();
} }
} }
} }