diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index 098bc564b2..864f3b5e7a 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -370,7 +370,6 @@
-
@@ -1584,6 +1583,7 @@
+
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs
deleted file mode 100644
index 55df2265f9..0000000000
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs
+++ /dev/null
@@ -1,213 +0,0 @@
-using BizHawk.Emulation.Common;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
-{
- ///
- /// My attempt at mixing multiple ISoundProvider sources together and outputting another ISoundProvider
- /// Currently only supports SyncSoundMode.Sync
- /// Attached ISoundProvider sources must already be stereo 44.1khz and ideally sound buffers should be the same length (882)
- /// (if not, only 882 samples of their buffer will be used)
- ///
- internal sealed class SoundProviderMixer : ISoundProvider
- {
- private class Provider
- {
- public ISoundProvider SoundProvider { get; set; }
- public string ProviderDescription { get; set; }
- public int MaxVolume { get; set; }
- public short[] Buffer { get; set; }
- public int NSamp { get; set; }
- }
-
- private bool _stereo = true;
- public bool Stereo
- {
- get { return _stereo; }
- set { _stereo = value; }
- }
-
- private readonly List SoundProviders;
-
- public SoundProviderMixer(params ISoundProvider[] soundProviders)
- {
- SoundProviders = new List();
-
- foreach (var s in soundProviders)
- {
- SoundProviders.Add(new Provider
- {
- SoundProvider = s,
- MaxVolume = short.MaxValue,
- });
- }
-
- EqualizeVolumes();
- }
-
- public SoundProviderMixer(short maxVolume, string description, params ISoundProvider[] soundProviders)
- {
- SoundProviders = new List();
-
- foreach (var s in soundProviders)
- {
- SoundProviders.Add(new Provider
- {
- SoundProvider = s,
- MaxVolume = maxVolume,
- ProviderDescription = description
- });
- }
-
- EqualizeVolumes();
- }
-
- public void AddSource(ISoundProvider source, string description)
- {
- SoundProviders.Add(new Provider
- {
- SoundProvider = source,
- MaxVolume = short.MaxValue,
- ProviderDescription = description
- });
-
- EqualizeVolumes();
- }
-
- public void AddSource(ISoundProvider source, short maxVolume, string description)
- {
- SoundProviders.Add(new Provider
- {
- SoundProvider = source,
- MaxVolume = maxVolume,
- ProviderDescription = description
- });
-
- EqualizeVolumes();
- }
-
- public void DisableSource(ISoundProvider source)
- {
- var sp = SoundProviders.Where(a => a.SoundProvider == source);
- if (sp.Count() == 1)
- SoundProviders.Remove(sp.First());
- else if (sp.Count() > 1)
- foreach (var s in sp)
- SoundProviders.Remove(s);
-
- EqualizeVolumes();
- }
-
- public void EqualizeVolumes()
- {
- if (SoundProviders.Count < 1)
- return;
-
- int eachVolume = short.MaxValue / SoundProviders.Count;
- foreach (var source in SoundProviders)
- {
- source.MaxVolume = eachVolume;
- }
- }
-
- #region ISoundProvider
-
- public bool CanProvideAsync => false;
- public SyncSoundMode SyncMode => SyncSoundMode.Sync;
-
- public void SetSyncMode(SyncSoundMode mode)
- {
- if (mode != SyncSoundMode.Sync)
- throw new InvalidOperationException("Only Sync mode is supported.");
- }
-
- public void GetSamplesAsync(short[] samples)
- {
- throw new NotSupportedException("Async is not available");
- }
-
- public void DiscardSamples()
- {
- foreach (var soundSource in SoundProviders)
- {
- soundSource.SoundProvider.DiscardSamples();
- }
- }
-
- public void GetSamplesSync(out short[] samples, out int nsamp)
- {
- samples = null;
- nsamp = 0;
-
- // get samples from all the providers
- foreach (var sp in SoundProviders)
- {
- int sampCount;
- short[] samp;
- sp.SoundProvider.GetSamplesSync(out samp, out sampCount);
- sp.NSamp = sampCount;
- sp.Buffer = samp;
- }
-
- // are all the sample lengths the same?
- var firstEntry = SoundProviders.First();
- bool sameCount = SoundProviders.All(s => s.NSamp == firstEntry.NSamp);
-
- if (!sameCount)
- {
- // this is a bit hacky, really all ISoundProviders should be supplying 44100 with 882 samples per frame.
- // we will make sure this happens (no matter how it sounds)
- if (SoundProviders.Count > 1)
- {
- for (int i = 0; i < SoundProviders.Count; i++)
- {
- int ns = SoundProviders[i].NSamp;
- short[] buff = new short[882 * 2];
-
- for (int b = 0; b < 882 * 2; b++)
- {
- if (b == SoundProviders[i].Buffer.Length - 1)
- {
- // end of source buffer
- break;
- }
-
- buff[b] = SoundProviders[i].Buffer[b];
- }
-
- // save back to the soundprovider
- SoundProviders[i].NSamp = 882;
- SoundProviders[i].Buffer = buff;
- }
- }
- else
- {
- // just process what we have as-is
- }
- }
-
- // mix the soundproviders together
- nsamp = 882;
- samples = new short[nsamp * 2];
-
- for (int i = 0; i < samples.Length; i++)
- {
- short sectorVal = 0;
- foreach (var sp in SoundProviders)
- {
- if (sp.Buffer[i] > sp.MaxVolume)
- sectorVal += (short)sp.MaxVolume;
- else
- sectorVal += sp.Buffer[i];
- }
-
- samples[i] = sectorVal;
- }
- }
-
- #endregion
-
- }
-}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
index 5fe2b30996..1c51cbbc10 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using BizHawk.Emulation.Cores.Components;
using BizHawk.Emulation.Cores.Sound;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
@@ -47,11 +48,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings());
List joysticks = new List();
- joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType1);
- joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType2);
- joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType3);
+ joysticks.Add(((ZXSpectrumSyncSettings)syncSettings).JoystickType1);
+ joysticks.Add(((ZXSpectrumSyncSettings)syncSettings).JoystickType2);
+ joysticks.Add(((ZXSpectrumSyncSettings)syncSettings).JoystickType3);
- deterministicEmulation = ((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).DeterministicEmulation;
+ deterministicEmulation = ((ZXSpectrumSyncSettings)syncSettings).DeterministicEmulation;
if (deterministic != null && deterministic == true)
{
@@ -117,26 +118,29 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ser.Register(_machine.ULADevice);
// initialize sound mixer and attach the various ISoundProvider devices
- SoundMixer = new SoundProviderMixer((int)(32767 / 10), "System Beeper", (ISoundProvider)_machine.BuzzerDevice);
- SoundMixer.AddSource((ISoundProvider)_machine.TapeBuzzer, "Tape Audio");
- if (_machine.AYDevice != null)
- SoundMixer.AddSource(_machine.AYDevice, "AY-3-3912");
+ SoundMixer = new SyncSoundMixer(targetSampleCount: 882);
+ SoundMixer.PinSource(_machine.BuzzerDevice, "System Beeper", (int)(32767 / 10));
+ SoundMixer.PinSource(_machine.TapeBuzzer, "Tape Audio", (int)(32767 / 10));
+ if (_machine.AYDevice != null)
+ {
+ SoundMixer.PinSource(_machine.AYDevice, "AY-3-3912");
+ }
// set audio device settings
if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AY38912))
{
- ((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
- _machine.AYDevice.Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYVolume;
+ ((AY38912)_machine.AYDevice).PanningConfiguration = ((ZXSpectrumSettings)settings).AYPanConfig;
+ _machine.AYDevice.Volume = ((ZXSpectrumSettings)settings).AYVolume;
}
if (_machine.BuzzerDevice != null)
{
- ((OneBitBeeper)_machine.BuzzerDevice as OneBitBeeper).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume;
+ _machine.BuzzerDevice.Volume = ((ZXSpectrumSettings)settings).EarVolume;
}
if (_machine.TapeBuzzer != null)
{
- ((OneBitBeeper)_machine.TapeBuzzer as OneBitBeeper).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).TapeVolume;
+ _machine.TapeBuzzer.Volume = ((ZXSpectrumSettings)settings).TapeVolume;
}
DCFilter dc = new DCFilter(SoundMixer, 512);
@@ -160,7 +164,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public List _tapeInfo = new List();
public List _diskInfo = new List();
- private SoundProviderMixer SoundMixer;
+ private SyncSoundMixer SoundMixer;
private readonly List _files;
diff --git a/BizHawk.Emulation.Cores/Sound/SyncSoundMixer.cs b/BizHawk.Emulation.Cores/Sound/SyncSoundMixer.cs
new file mode 100644
index 0000000000..2052546040
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Sound/SyncSoundMixer.cs
@@ -0,0 +1,340 @@
+using BizHawk.Emulation.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Components
+{
+ ///
+ /// ISoundProvider mixer that generates a single ISoundProvider output from multiple ISoundProvider sources
+ /// Currently only supports sync (not async)
+ ///
+ /// Bizhawk expects ISoundProviders to output at 44100KHz, so this is what SyncSoundMixer does. Therefore, try to make
+ /// sure that your child ISoundProviders also do this I guess.
+ ///
+ /// This is currently used in the ZX Spectrum and CPC cores but others may find it useful in future
+ ///
+ public sealed class SyncSoundMixer : ISoundProvider
+ {
+ ///
+ /// Currently attached ChildProviders
+ ///
+ private readonly List _soundProviders = new List();
+
+ ///
+ /// The final output max volume
+ ///
+ public short FinalMaxVolume
+ {
+ get { return _finalMaxVolume; }
+ set
+ {
+ _finalMaxVolume = value;
+ EqualizeVolumes();
+ }
+ }
+ private short _finalMaxVolume;
+
+ ///
+ /// How the sound sources are balanced against each other
+ ///
+ public SoundMixBalance MixBalanceMethod
+ {
+ get { return _mixBalanceMethod; }
+ set
+ {
+ _mixBalanceMethod = value;
+ EqualizeVolumes();
+ }
+ }
+ private SoundMixBalance _mixBalanceMethod;
+
+ ///
+ /// If specified the output buffer of the SyncSoundMixer will always contain this many samples
+ /// You should probably nearly always specify a value for this and get your ISoundProvider sources
+ /// to get as close to this nsamp value as possible. Otherwise the number of samples will
+ /// be based on the highest nsamp out of all the child providers for that specific frame
+ /// Useful examples:
+ /// 882 - 44100KHz - 50Hz
+ /// 735 - 44100Khz - 60Hz
+ ///
+ private int? _targetSampleCount;
+
+ ///
+ /// Constructor
+ ///
+ /// Whether each providers MaxVolume is reduced to an equal share of the final max volume value
+ /// The final 'master' max volume
+ ///
+ /// If specified the output buffer of the SyncSoundMixer will always contain this many samples
+ /// If left null the output buffer will contain the highest number of samples out of each of the providers every frame
+ ///
+ public SyncSoundMixer(SoundMixBalance mixBalanceMethod = SoundMixBalance.Equalize, short maxVolume = short.MaxValue, int? targetSampleCount = null)
+ {
+ _mixBalanceMethod = mixBalanceMethod;
+ _finalMaxVolume = maxVolume;
+ _targetSampleCount = targetSampleCount;
+ }
+
+ ///
+ /// Adds an ISoundProvider to the SyncSoundMixer
+ ///
+ /// The source ISoundProvider
+ /// An ident string for the ISoundProvider (useful when debugging)
+ /// If this is true then only half the samples should be present
+ public void PinSource(ISoundProvider source, string sourceDescription)
+ {
+ PinSource(source, sourceDescription, FinalMaxVolume);
+ }
+
+ ///
+ /// Adds an ISoundProvider to the SyncSoundMixer
+ ///
+ /// The source ISoundProvider
+ /// An ident string for the ISoundProvider (useful when debugging)
+ /// The MaxVolume level for this particular ISoundProvider
+ /// If this is true then only half the samples should be present
+ public void PinSource(ISoundProvider source, string sourceDescription, short sourceMaxVolume)
+ {
+ _soundProviders.Add(new ChildProvider
+ {
+ SoundProvider = source,
+ ProviderDescription = sourceDescription,
+ MaxVolume = sourceMaxVolume
+ });
+
+ EqualizeVolumes();
+ }
+
+ ///
+ /// Removes an existing ISoundProvider from the SyncSoundMixer
+ ///
+ ///
+ public void UnPinSource(ISoundProvider source)
+ {
+ var sp = _soundProviders.Where(a => a.SoundProvider == source);
+
+ if (sp.Count() == 1)
+ {
+ _soundProviders.Remove(sp.First());
+ }
+ else if (sp.Count() > 1)
+ {
+ foreach (var s in sp)
+ {
+ _soundProviders.Remove(s);
+ }
+ }
+
+ EqualizeVolumes();
+ }
+
+ ///
+ /// Sets each pinned sound provider's MaxVolume based on the MixBalanceMethod
+ ///
+ public void EqualizeVolumes()
+ {
+ if (_soundProviders.Count < 1)
+ return;
+
+ switch (MixBalanceMethod)
+ {
+ case SoundMixBalance.Equalize:
+ var eachVolume = FinalMaxVolume / _soundProviders.Count;
+ foreach (var source in _soundProviders)
+ {
+ source.MaxVolume = eachVolume;
+ }
+ break;
+ case SoundMixBalance.MasterHardLimit:
+ foreach (var source in _soundProviders)
+ {
+ if (source.MaxVolume > FinalMaxVolume)
+ {
+ source.MaxVolume = FinalMaxVolume;
+ }
+ }
+ break;
+ }
+ }
+
+ ///
+ /// Returns the value of the highest nsamp in the SoundProviders collection
+ ///
+ ///
+ private int GetHigestSampleCount()
+ {
+ var lookup = _soundProviders.OrderByDescending(x => x.InputNSamp)
+ .FirstOrDefault();
+
+ if (lookup == null)
+ {
+ return 0;
+ }
+
+ return lookup.InputNSamp;
+ }
+
+ #region ISoundProvider
+
+ public bool CanProvideAsync => false;
+ public SyncSoundMode SyncMode => SyncSoundMode.Sync;
+
+ public void SetSyncMode(SyncSoundMode mode)
+ {
+ if (mode != SyncSoundMode.Sync)
+ throw new InvalidOperationException("Only Sync mode is supported.");
+ }
+
+ public void GetSamplesAsync(short[] samples)
+ {
+ throw new NotSupportedException("Async is not available");
+ }
+
+ public void DiscardSamples()
+ {
+ foreach (var soundSource in _soundProviders)
+ {
+ soundSource.SoundProvider.DiscardSamples();
+ }
+ }
+
+ public void GetSamplesSync(out short[] samples, out int nsamp)
+ {
+ // fetch samples from all the providers
+ foreach (var sp in _soundProviders)
+ {
+ sp.GetSamples();
+ }
+
+ nsamp = _targetSampleCount ?? GetHigestSampleCount();
+ samples = new short[nsamp * 2];
+
+ // process the output buffers
+ foreach (var sp in _soundProviders)
+ {
+ sp.PrepareOutput(nsamp);
+ }
+
+ // mix the child providers together
+ for (int i = 0; i < samples.Length; i++)
+ {
+ int sampleVal = 0;
+ foreach (var sp in _soundProviders)
+ {
+ if (sp.OutputBuffer[i] > sp.MaxVolume)
+ {
+ sampleVal += (short)sp.MaxVolume;
+ }
+ else
+ {
+ sampleVal += sp.OutputBuffer[i];
+ }
+ }
+
+ // final hard limit
+ if (sampleVal > (int)FinalMaxVolume)
+ {
+ sampleVal = (int)FinalMaxVolume;
+ }
+
+ samples[i] = (short)sampleVal;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Instantiated for every ISoundProvider source that is added to the mixer
+ ///
+ private class ChildProvider
+ {
+ ///
+ /// The Child ISoundProvider
+ ///
+ public ISoundProvider SoundProvider;
+
+ ///
+ /// Identification string
+ ///
+ public string ProviderDescription;
+
+ ///
+ /// The max volume for this provider
+ ///
+ public int MaxVolume;
+
+ ///
+ /// Stores the incoming samples
+ ///
+ public short[] InputBuffer;
+
+ ///
+ /// The incoming number of samples
+ ///
+ public int InputNSamp;
+
+ ///
+ /// Stores the processed samples ready for mixing
+ ///
+ public short[] OutputBuffer;
+
+ ///
+ /// The output number of samples
+ ///
+ public int OutputNSamp;
+
+ ///
+ /// Fetches sample data from the child ISoundProvider
+ ///
+ public void GetSamples()
+ {
+ SoundProvider.GetSamplesSync(out InputBuffer, out InputNSamp);
+ }
+
+ ///
+ /// Ensures the output buffer is ready for mixing based on the supplied nsamp value
+ /// Overflow samples will be omitted and underflow samples will be empty air
+ ///
+ ///
+ public void PrepareOutput(int nsamp)
+ {
+ OutputNSamp = nsamp;
+ var outputBuffSize = OutputNSamp * 2;
+
+ if (OutputNSamp != InputNSamp || InputBuffer.Length != outputBuffSize)
+ {
+ OutputBuffer = new short[outputBuffSize];
+
+ var i = 0;
+ while (i < InputBuffer.Length && i < outputBuffSize)
+ {
+ OutputBuffer[i] = InputBuffer[i];
+ i++;
+ }
+ }
+ else
+ {
+ // buffer needs no modification
+ OutputBuffer = InputBuffer;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Defines how mixed sound sources should be balanced
+ ///
+ public enum SoundMixBalance
+ {
+ ///
+ /// Each sound source's max volume will be set to MaxVolume / nSources
+ ///
+ Equalize,
+ ///
+ /// Each sound source's individual max volume will be respected but the final MaxVolume will be limited to MaxVolume
+ ///
+ MasterHardLimit
+ }
+}