From 1b4df02938b3b8fe4a04ed3c53362aa63058f5e3 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 20 Aug 2017 21:47:05 +0200 Subject: [PATCH] Audio mixer window --- Emux/App.xaml.cs | 6 +- Emux/Audio/GameBoyAudioMixer.cs | 96 +++++++++++++++++++++++++++---- Emux/Audio/NAudioChannelOutput.cs | 34 +++++++++-- Emux/Emux.csproj | 7 +++ Emux/Gui/MainWindow.xaml | 9 ++- Emux/Gui/MainWindow.xaml.cs | 65 ++++++++++++++++----- 6 files changed, 182 insertions(+), 35 deletions(-) diff --git a/Emux/App.xaml.cs b/Emux/App.xaml.cs index ec8334a..fde0bfe 100644 --- a/Emux/App.xaml.cs +++ b/Emux/App.xaml.cs @@ -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; } + + } } diff --git a/Emux/Audio/GameBoyAudioMixer.cs b/Emux/Audio/GameBoyAudioMixer.cs index 3b0e228..5595729 100644 --- a/Emux/Audio/GameBoyAudioMixer.cs +++ b/Emux/Audio/GameBoyAudioMixer.cs @@ -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 { - 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)); + } } } diff --git a/Emux/Audio/NAudioChannelOutput.cs b/Emux/Audio/NAudioChannelOutput.cs index 660b1dc..95330ee 100644 --- a/Emux/Audio/NAudioChannelOutput.cs +++ b/Emux/Audio/NAudioChannelOutput.cs @@ -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)); + } } } \ No newline at end of file diff --git a/Emux/Emux.csproj b/Emux/Emux.csproj index b325c95..4a3f688 100644 --- a/Emux/Emux.csproj +++ b/Emux/Emux.csproj @@ -66,6 +66,9 @@ AboutDialog.xaml + + AudioMixerWindow.xaml + @@ -91,6 +94,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/Emux/Gui/MainWindow.xaml b/Emux/Gui/MainWindow.xaml index 8883deb..fc3c606 100644 --- a/Emux/Gui/MainWindow.xaml +++ b/Emux/Gui/MainWindow.xaml @@ -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"> @@ -17,7 +18,8 @@ - + + @@ -49,7 +51,8 @@ - + + diff --git a/Emux/Gui/MainWindow.xaml.cs b/Emux/Gui/MainWindow.xaml.cs index 9cc2e7a..9a7361c 100644 --- a/Emux/Gui/MainWindow.xaml.cs +++ b/Emux/Gui/MainWindow.xaml.cs @@ -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(); } } }