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.Navigation;
using Emux.Gui;
namespace Emux
{
@ -11,7 +13,7 @@ namespace Emux
{
DeviceManager = new DeviceManager();
}
public new static App Current
{
get { return (App) Application.Current; }
@ -21,5 +23,7 @@ namespace Emux
{
get;
}
}
}

View File

@ -1,28 +1,35 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using Emux.GameBoy.Audio;
using NAudio.Wave;
namespace Emux.Audio
{
public class GameBoyAudioMixer : IWaveProvider
public class GameBoyAudioMixer : IWaveProvider, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly MixingWaveProvider32 _mixer = new MixingWaveProvider32();
private bool _enabled = true;
private WaveFileWriter _writer;
private bool _isRecording;
public GameBoyAudioMixer()
{
Channels = new List<NAudioChannelOutput>
{
new NAudioChannelOutput(this),
new NAudioChannelOutput(this),
new NAudioChannelOutput(this),
new NAudioChannelOutput(this),
new NAudioChannelOutput(this, "Square + Sweep"),
new NAudioChannelOutput(this, "Square"),
new NAudioChannelOutput(this, "Wave"),
new NAudioChannelOutput(this, "Noise"),
}.AsReadOnly();
foreach (var channel in Channels)
_mixer.AddInputStream(channel);
}
public WaveFormat WaveFormat
@ -35,20 +42,87 @@ namespace Emux.Audio
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)
{
_mixer.Read(buffer, offset, count);
if (!Enabled)
Array.Clear(buffer, offset, count);
lock (this)
{
_writer?.Write(buffer, offset, count);
}
return count;
}
public void Connect(GameBoySpu spu)
{
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 (channel != null) // TODO: remove null check if all channels are implemented.
channel.ChannelOutput = Channels[i];
if (IsRecording)
throw new InvalidOperationException("Cannot start a recording when a recording is already happening.");
_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.ComponentModel;
using System.Runtime.CompilerServices;
using Emux.GameBoy.Audio;
using NAudio.Wave;
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)
{
if (mixer == null)
throw new ArgumentNullException(nameof(mixer));
_mixer = mixer;
Name = name;
Enabled = true;
}
public bool Enabled
{
get;
set;
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
}
public string Name
{
get;
}
public int SampleRate
{
get { return WaveFormat.SampleRate; }
@ -35,5 +54,10 @@ namespace Emux.Audio
Buffer.BlockCopy(sampleData, offset * sizeof(float), newSampleData, 0, length * sizeof(float));
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>
</Compile>
<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\InverseConverter.cs" />
<Compile Include="Gui\RegisterItem.cs" />
@ -91,6 +94,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Gui\AudioMixerWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Gui\TitledOverlay.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -5,7 +5,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Emux.Gui"
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>
<CommandBinding Command="ApplicationCommands.Open" Executed="OpenCommandOnExecuted" />
<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.VideoOutputCommand" CanExecute="GameBoyExistsCanExecute" Executed="VideoOutputCommandOnExecuted" />
<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.AboutCommand" Executed="AboutCommandOnExecuted" />
</Window.CommandBindings>
@ -49,7 +51,8 @@
<MenuItem Header="Video output" Command="local:MainWindow.VideoOutputCommand"/>
<MenuItem Header="Virtual keypad" Command="local:MainWindow.KeyPadCommand"/>
<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>
<MenuItem Header="Help">

View File

@ -75,7 +75,11 @@ namespace Emux.Gui
public static readonly RoutedUICommand IOMemoryCommand = new RoutedUICommand(
"Open the IO memory view",
"IO Memory",
typeof(MainWindow));
typeof(MainWindow),
new InputGestureCollection(new[]
{
new KeyGesture(Key.F3)
}));
public static readonly RoutedUICommand KeyPadCommand = new RoutedUICommand(
"Open the virtual keypad window",
@ -86,6 +90,15 @@ namespace Emux.Gui
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(
"Enable or disable sound",
"Enable sound",
@ -105,22 +118,42 @@ namespace Emux.Gui
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 AudioMixerWindow _mixerWindow;
private readonly KeypadWindow _keypadWindow;
private readonly IOWindow _ioWindow;
private GameBoy.GameBoy _currentDevice;
public MainWindow()
{
InitializeComponent();
_videoWindow = new VideoWindow();
_mixerWindow = new AudioMixerWindow();
_keypadWindow = new KeypadWindow();
_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)
{
@ -129,7 +162,7 @@ namespace Emux.Gui
RunningOverlay.DisableOverlay();
}
_currentDevice = App.Current.DeviceManager.CurrentDevice;
_currentDevice = DeviceManager.CurrentDevice;
_currentDevice.Cpu.Paused += GameBoyOnPaused;
_currentDevice.Cpu.Resumed += GameBoyOnResumed;
_currentDevice.Gpu.VideoOutput = _videoWindow;
@ -137,15 +170,11 @@ namespace Emux.Gui
_videoWindow.Device = _currentDevice;
_keypadWindow.Device = _currentDevice;
_ioWindow.Device = _currentDevice;
_mixerWindow.Mixer = DeviceManager.AudioMixer;
_videoWindow.Show();
RefreshView();
if (EnableSoundsMenuItem.IsChecked)
_currentDevice.Spu.ActivateAllChannels();
else
_currentDevice.Spu.DeactivateAllChannels();
}
public void RefreshView()
@ -301,22 +330,28 @@ namespace Emux.Gui
_videoWindow.Device = null;
_keypadWindow.Device = null;
_ioWindow.Device = null;
_mixerWindow.Mixer = null;
_videoWindow.Close();
_keypadWindow.Close();
_ioWindow.Close();
_mixerWindow.Close();
}
private void EnableSoundCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
if (EnableSoundsMenuItem.IsChecked)
_currentDevice.Spu.ActivateAllChannels();
else
_currentDevice.Spu.DeactivateAllChannels();
App.Current.DeviceManager.AudioMixer.Enabled = EnableSoundsMenuItem.IsChecked;
}
private void IOMemoryCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
_ioWindow.Show();
_ioWindow.Focus();
}
private void MixerCommandOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
_mixerWindow.Show();
_mixerWindow.Focus();
}
}
}