cleanup IAsyncSoundProvider hacks and consolidate code that is only used for PCE
This commit is contained in:
parent
b16684b4c7
commit
976ea4967b
|
@ -1,15 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
using BizHawk.Emulation.Cores.Components;
|
using BizHawk.Emulation.Cores.Components;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.PCEngine
|
namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
{
|
{
|
||||||
public sealed class ADPCM : IMixedSoundProvider
|
public sealed class ADPCM : IMixedSoundProvider
|
||||||
{
|
{
|
||||||
ScsiCDBus SCSI;
|
private readonly ScsiCDBus _scsi;
|
||||||
PCEngine pce;
|
private readonly PCEngine _pce;
|
||||||
MetaspuSoundProvider SoundProvider = new MetaspuSoundProvider();
|
private readonly VecnaSynchronizer _synchronizer = new VecnaSynchronizer();
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
|
||||||
|
@ -60,8 +61,8 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
|
|
||||||
public ADPCM(PCEngine pcEngine, ScsiCDBus scsi)
|
public ADPCM(PCEngine pcEngine, ScsiCDBus scsi)
|
||||||
{
|
{
|
||||||
pce = pcEngine;
|
_pce = pcEngine;
|
||||||
SCSI = scsi;
|
_scsi = scsi;
|
||||||
MaxVolume = 24576;
|
MaxVolume = 24576;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,24 +157,24 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
|
|
||||||
if (AdpcmCdDmaRequested)
|
if (AdpcmCdDmaRequested)
|
||||||
{
|
{
|
||||||
if (SCSI.REQ && SCSI.IO && !SCSI.CD && !SCSI.ACK)
|
if (_scsi.REQ && _scsi.IO && !_scsi.CD && !_scsi.ACK)
|
||||||
{
|
{
|
||||||
byte dmaByte = SCSI.DataBits;
|
byte dmaByte = _scsi.DataBits;
|
||||||
RAM[WriteAddress++] = dmaByte;
|
RAM[WriteAddress++] = dmaByte;
|
||||||
AdpcmLength++;
|
AdpcmLength++;
|
||||||
|
|
||||||
SCSI.ACK = false;
|
_scsi.ACK = false;
|
||||||
SCSI.REQ = false;
|
_scsi.REQ = false;
|
||||||
SCSI.Think();
|
_scsi.Think();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SCSI.DataTransferInProgress == false)
|
if (_scsi.DataTransferInProgress == false)
|
||||||
Port180B = 0;
|
Port180B = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pce.IntADPCM = HalfReached;
|
_pce.IntADPCM = HalfReached;
|
||||||
pce.IntStop = EndReached;
|
_pce.IntStop = EndReached;
|
||||||
pce.RefreshIRQ2();
|
_pce.RefreshIRQ2();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
@ -281,7 +282,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
private void AdpcmEmitSample()
|
private void AdpcmEmitSample()
|
||||||
{
|
{
|
||||||
if (AdpcmIsPlaying == false)
|
if (AdpcmIsPlaying == false)
|
||||||
SoundProvider.Buffer.EnqueueSample(0, 0);
|
_synchronizer.EnqueueSample(0, 0);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (nextSampleTimer <= 0)
|
if (nextSampleTimer <= 0)
|
||||||
|
@ -301,21 +302,37 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
short adjustedSample = (short)((playingSample - 2048) * MaxVolume / 2048);
|
short adjustedSample = (short)((playingSample - 2048) * MaxVolume / 2048);
|
||||||
SoundProvider.Buffer.EnqueueSample(adjustedSample, adjustedSample);
|
_synchronizer.EnqueueSample(adjustedSample, adjustedSample);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GetSamples(short[] samples)
|
public void GetSamplesAsync(short[] samples)
|
||||||
{
|
{
|
||||||
SoundProvider.GetSamplesAsync(samples);
|
_synchronizer.OutputSamples(samples, samples.Length / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DiscardSamples()
|
public void DiscardSamples()
|
||||||
{
|
{
|
||||||
SoundProvider.DiscardSamples();
|
_synchronizer.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MaxVolume { get; set; }
|
public int MaxVolume { get; set; }
|
||||||
|
public bool CanProvideAsync => true;
|
||||||
|
|
||||||
|
public void SetSyncMode(SyncSoundMode mode)
|
||||||
|
{
|
||||||
|
if (mode != SyncSoundMode.Async)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Only async currently supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncSoundMode SyncMode => SyncSoundMode.Async;
|
||||||
|
|
||||||
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Sync sound not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
|
||||||
|
@ -351,9 +368,9 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
if (ser.IsReader)
|
if (ser.IsReader)
|
||||||
{
|
{
|
||||||
Port180E = port180E;
|
Port180E = port180E;
|
||||||
pce.IntADPCM = HalfReached;
|
_pce.IntADPCM = HalfReached;
|
||||||
pce.IntStop = EndReached;
|
_pce.IntStop = EndReached;
|
||||||
pce.RefreshIRQ2();
|
_pce.RefreshIRQ2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
Cpu = new HuC6280(MemoryCallbacks);
|
Cpu = new HuC6280(MemoryCallbacks);
|
||||||
VCE = new VCE();
|
VCE = new VCE();
|
||||||
VDC1 = new VDC(this, Cpu, VCE);
|
VDC1 = new VDC(this, Cpu, VCE);
|
||||||
PSG = new HuC6280PSG();
|
PSG = new HuC6280PSG(735);
|
||||||
SCSI = new ScsiCDBus(this, disc);
|
SCSI = new ScsiCDBus(this, disc);
|
||||||
|
|
||||||
Cpu.Logger = s => Tracer.Put(s);
|
Cpu.Logger = s => Tracer.Put(s);
|
||||||
|
@ -166,7 +166,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
Cpu.ReadMemory21 = ReadMemory;
|
Cpu.ReadMemory21 = ReadMemory;
|
||||||
Cpu.WriteMemory21 = WriteMemory;
|
Cpu.WriteMemory21 = WriteMemory;
|
||||||
Cpu.WriteVDC = VDC1.WriteVDC;
|
Cpu.WriteVDC = VDC1.WriteVDC;
|
||||||
_soundProvider = new FakeSyncSound(PSG, 735);
|
_soundProvider = PSG;
|
||||||
CDAudio = new CDAudio(null, 0);
|
CDAudio = new CDAudio(null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
Cpu.ReadMemory21 = ReadMemorySGX;
|
Cpu.ReadMemory21 = ReadMemorySGX;
|
||||||
Cpu.WriteMemory21 = WriteMemorySGX;
|
Cpu.WriteMemory21 = WriteMemorySGX;
|
||||||
Cpu.WriteVDC = VDC1.WriteVDC;
|
Cpu.WriteVDC = VDC1.WriteVDC;
|
||||||
_soundProvider = new FakeSyncSound(PSG, 735);
|
_soundProvider = PSG;
|
||||||
CDAudio = new CDAudio(null, 0);
|
CDAudio = new CDAudio(null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,9 +193,9 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
||||||
CDAudio = new CDAudio(disc);
|
CDAudio = new CDAudio(disc);
|
||||||
SetCDAudioCallback();
|
SetCDAudioCallback();
|
||||||
PSG.MaxVolume = short.MaxValue * 3 / 4;
|
PSG.MaxVolume = short.MaxValue * 3 / 4;
|
||||||
SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM);
|
SoundMixer = new SoundMixer(735, PSG, CDAudio, ADPCM);
|
||||||
_soundProvider = new FakeSyncSound(SoundMixer, 735);
|
_soundProvider = SoundMixer;
|
||||||
Cpu.ThinkAction = (cycles) => { SCSI.Think(); ADPCM.Think(cycles); };
|
Cpu.ThinkAction = cycles => { SCSI.Think(); ADPCM.Think(cycles); };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rom.Length == 0x60000)
|
if (rom.Length == 0x60000)
|
||||||
|
|
|
@ -1,127 +1,114 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Common
|
namespace BizHawk.Emulation.Cores.Components
|
||||||
{
|
{
|
||||||
public interface ISynchronizingAudioBuffer
|
public class VecnaSynchronizer
|
||||||
{
|
{
|
||||||
void EnqueueSample(short left, short right);
|
// vecna's attempt at a fully synchronous sound provider.
|
||||||
void Clear();
|
// It's similar in philosophy to my "BufferedAsync" provider, but BufferedAsync is not
|
||||||
|
// fully synchronous.
|
||||||
// returns the number of samples actually supplied, which may not match the number requested
|
|
||||||
// ^^ what the hell is that supposed to mean.
|
// Like BufferedAsync, it tries to make most frames 100% correct and just suck it up
|
||||||
// the entire point of an ISynchronizingAudioBuffer
|
// periodically and have a big bad-sounding mistake frame if it has to.
|
||||||
// is to provide exact amounts of output samples,
|
|
||||||
// even when the input provided varies....
|
// It is significantly less ambitious and elaborate than the other methods.
|
||||||
int OutputSamples(short[] buf, int samplesRequested);
|
// We'll see if it works better or not!
|
||||||
}
|
|
||||||
|
// It has a min and maximum amount of excess buffer to deal with minor overflows.
|
||||||
public class VecnaSynchronizer : ISynchronizingAudioBuffer
|
// When fast-forwarding, it will discard samples above the maximum excess buffer.
|
||||||
{
|
|
||||||
// vecna's attempt at a fully synchronous sound provider.
|
// When underflowing, it will attempt to resample to a certain thresh
|
||||||
// It's similar in philosophy to my "BufferedAsync" provider, but BufferedAsync is not
|
// old.
|
||||||
// fully synchronous.
|
// If it underflows beyond that threshold, it will give up and output silence.
|
||||||
|
// Since it has done this, it will go ahead and generate some excess silence in order
|
||||||
// Like BufferedAsync, it tries to make most frames 100% correct and just suck it up
|
// to restock its excess buffer.
|
||||||
// periodically and have a big bad-sounding mistake frame if it has to.
|
private struct Sample
|
||||||
|
{
|
||||||
// It is significantly less ambitious and elaborate than the other methods.
|
public readonly short Left;
|
||||||
// We'll see if it works better or not!
|
public readonly short Right;
|
||||||
|
|
||||||
// It has a min and maximum amount of excess buffer to deal with minor overflows.
|
public Sample(short left, short right)
|
||||||
// When fast-forwarding, it will discard samples above the maximum excess buffer.
|
{
|
||||||
|
Left = left;
|
||||||
// When underflowing, it will attempt to resample to a certain thresh
|
Right = right;
|
||||||
// old.
|
}
|
||||||
// If it underflows beyond that threshold, it will give up and output silence.
|
}
|
||||||
// Since it has done this, it will go ahead and generate some excess silence in order
|
|
||||||
// to restock its excess buffer.
|
private const int MaxExcessSamples = 2048;
|
||||||
private struct Sample
|
|
||||||
{
|
private readonly Queue<Sample> _buffer;
|
||||||
public readonly short Left;
|
private readonly Sample[] _resampleBuffer;
|
||||||
public readonly short Right;
|
|
||||||
|
public VecnaSynchronizer()
|
||||||
public Sample(short left, short right)
|
{
|
||||||
{
|
_buffer = new Queue<Sample>(2048);
|
||||||
Left = left;
|
_resampleBuffer = new Sample[2730]; // 2048 * 1.25
|
||||||
Right = right;
|
|
||||||
}
|
// Give us a little buffer wiggle-room
|
||||||
}
|
for (int i = 0; i < 367; i++)
|
||||||
|
{
|
||||||
private const int MaxExcessSamples = 2048;
|
_buffer.Enqueue(new Sample(0, 0));
|
||||||
|
}
|
||||||
private readonly Queue<Sample> _buffer;
|
}
|
||||||
private readonly Sample[] _resampleBuffer;
|
|
||||||
|
public void EnqueueSample(short left, short right)
|
||||||
public VecnaSynchronizer()
|
{
|
||||||
{
|
if (_buffer.Count >= MaxExcessSamples - 1)
|
||||||
_buffer = new Queue<Sample>(2048);
|
{
|
||||||
_resampleBuffer = new Sample[2730]; // 2048 * 1.25
|
// if buffer is overfull, dequeue old samples to make room for new samples.
|
||||||
|
_buffer.Dequeue();
|
||||||
// Give us a little buffer wiggle-room
|
}
|
||||||
for (int i = 0; i < 367; i++)
|
|
||||||
{
|
_buffer.Enqueue(new Sample(left, right));
|
||||||
_buffer.Enqueue(new Sample(0, 0));
|
}
|
||||||
}
|
|
||||||
}
|
public void Clear()
|
||||||
|
{
|
||||||
public void EnqueueSample(short left, short right)
|
_buffer.Clear();
|
||||||
{
|
}
|
||||||
if (_buffer.Count >= MaxExcessSamples - 1)
|
|
||||||
{
|
public int OutputSamples(short[] buf, int samplesRequested)
|
||||||
// if buffer is overfull, dequeue old samples to make room for new samples.
|
{
|
||||||
_buffer.Dequeue();
|
if (samplesRequested > _buffer.Count)
|
||||||
}
|
{
|
||||||
|
// underflow!
|
||||||
_buffer.Enqueue(new Sample(left, right));
|
if (_buffer.Count > samplesRequested * 3 / 4)
|
||||||
}
|
{
|
||||||
|
// if we're within 75% of target, then I guess we suck it up and resample.
|
||||||
public void Clear()
|
// we sample in a goofy way, we could probably do it a bit smarter, if we cared more.
|
||||||
{
|
int samplesAvailable = _buffer.Count;
|
||||||
_buffer.Clear();
|
for (int i = 0; _buffer.Count > 0; i++)
|
||||||
}
|
{
|
||||||
|
_resampleBuffer[i] = _buffer.Dequeue();
|
||||||
public int OutputSamples(short[] buf, int samplesRequested)
|
}
|
||||||
{
|
|
||||||
if (samplesRequested > _buffer.Count)
|
int index = 0;
|
||||||
{
|
for (int i = 0; i < samplesRequested; i++)
|
||||||
// underflow!
|
{
|
||||||
if (_buffer.Count > samplesRequested * 3 / 4)
|
Sample sample = _resampleBuffer[i * samplesAvailable / samplesRequested];
|
||||||
{
|
buf[index++] += sample.Left;
|
||||||
// if we're within 75% of target, then I guess we suck it up and resample.
|
buf[index++] += sample.Right;
|
||||||
// we sample in a goofy way, we could probably do it a bit smarter, if we cared more.
|
}
|
||||||
int samplesAvailable = _buffer.Count;
|
}
|
||||||
for (int i = 0; _buffer.Count > 0; i++)
|
else
|
||||||
{
|
{
|
||||||
_resampleBuffer[i] = _buffer.Dequeue();
|
// we're outside of a "reasonable" underflow. Give up and output silence.
|
||||||
}
|
// Do nothing. The whole frame will be excess buffer.
|
||||||
|
}
|
||||||
int index = 0;
|
}
|
||||||
for (int i = 0; i < samplesRequested; i++)
|
else
|
||||||
{
|
{
|
||||||
Sample sample = _resampleBuffer[i * samplesAvailable / samplesRequested];
|
// normal operation
|
||||||
buf[index++] += sample.Left;
|
int index = 0;
|
||||||
buf[index++] += sample.Right;
|
for (int i = 0; i < samplesRequested && _buffer.Count > 0; i++)
|
||||||
}
|
{
|
||||||
}
|
Sample sample = _buffer.Dequeue();
|
||||||
else
|
buf[index++] += sample.Left;
|
||||||
{
|
buf[index++] += sample.Right;
|
||||||
// we're outside of a "reasonable" underflow. Give up and output silence.
|
}
|
||||||
// Do nothing. The whole frame will be excess buffer.
|
}
|
||||||
}
|
|
||||||
}
|
return samplesRequested;
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
// normal operation
|
}
|
||||||
int index = 0;
|
|
||||||
for (int i = 0; i < samplesRequested && _buffer.Count > 0; i++)
|
|
||||||
{
|
|
||||||
Sample sample = _buffer.Dequeue();
|
|
||||||
buf[index++] += sample.Left;
|
|
||||||
buf[index++] += sample.Right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return samplesRequested;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
using BizHawk.Emulation.DiscSystem;
|
using BizHawk.Emulation.DiscSystem;
|
||||||
|
|
||||||
// The state of the cd player is quantized to the frame level.
|
// The state of the cd player is quantized to the frame level.
|
||||||
|
@ -139,7 +140,23 @@ namespace BizHawk.Emulation.Cores.Components
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GetSamples(short[] samples)
|
public bool CanProvideAsync => true;
|
||||||
|
public void SetSyncMode(SyncSoundMode mode)
|
||||||
|
{
|
||||||
|
if (mode != SyncSoundMode.Async)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Only async currently supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncSoundMode SyncMode => SyncSoundMode.Async;
|
||||||
|
|
||||||
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Sync sound not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesAsync(short[] samples)
|
||||||
{
|
{
|
||||||
if (Mode != CDAudioMode_Playing)
|
if (Mode != CDAudioMode_Playing)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,8 +11,10 @@ namespace BizHawk.Emulation.Cores.Components
|
||||||
// It is embedded on the CPU and doesn't have its own part number. None the less, it is emulated separately from the 6280 CPU.
|
// It is embedded on the CPU and doesn't have its own part number. None the less, it is emulated separately from the 6280 CPU.
|
||||||
|
|
||||||
// Sound refactor TODO: IMixedSoundProvider must inherit ISoundProvider
|
// Sound refactor TODO: IMixedSoundProvider must inherit ISoundProvider
|
||||||
public sealed class HuC6280PSG : IMixedSoundProvider
|
// TODo: this provides "fake" sync sound by hardcoding the number of samples
|
||||||
|
public sealed class HuC6280PSG : ISoundProvider, IMixedSoundProvider
|
||||||
{
|
{
|
||||||
|
private readonly int _spf;
|
||||||
public class PSGChannel
|
public class PSGChannel
|
||||||
{
|
{
|
||||||
public ushort Frequency;
|
public ushort Frequency;
|
||||||
|
@ -44,11 +46,11 @@ namespace BizHawk.Emulation.Cores.Components
|
||||||
|
|
||||||
public byte MainVolumeLeft;
|
public byte MainVolumeLeft;
|
||||||
public byte MainVolumeRight;
|
public byte MainVolumeRight;
|
||||||
public int MaxVolume { get; set; }
|
public int MaxVolume { get; set; } = short.MaxValue;
|
||||||
|
|
||||||
public HuC6280PSG()
|
public HuC6280PSG(int spf)
|
||||||
{
|
{
|
||||||
MaxVolume = short.MaxValue;
|
_spf = spf;
|
||||||
Waves.InitWaves();
|
Waves.InitWaves();
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
{
|
{
|
||||||
|
@ -147,7 +149,25 @@ namespace BizHawk.Emulation.Cores.Components
|
||||||
|
|
||||||
public void DiscardSamples() { }
|
public void DiscardSamples() { }
|
||||||
|
|
||||||
public void GetSamples(short[] samples)
|
public bool CanProvideAsync => true;
|
||||||
|
|
||||||
|
public void SetSyncMode(SyncSoundMode mode) => SyncMode = mode;
|
||||||
|
public SyncSoundMode SyncMode { get; private set; }
|
||||||
|
|
||||||
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
if (SyncMode != SyncSoundMode.Sync)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Must be in sync mode to call a sync method");
|
||||||
|
}
|
||||||
|
|
||||||
|
short[] ret = new short[_spf * 2];
|
||||||
|
GetSamplesAsync(ret);
|
||||||
|
samples = ret;
|
||||||
|
nsamp = _spf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesAsync(short[] samples)
|
||||||
{
|
{
|
||||||
int elapsedCycles = (int)(frameStopTime - frameStartTime);
|
int elapsedCycles = (int)(frameStopTime - frameStartTime);
|
||||||
int start = 0;
|
int start = 0;
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
using System;
|
|
||||||
using BizHawk.Emulation.Common;
|
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Components
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This interface is for legacy sound implementations in some older cores
|
|
||||||
/// This needs to go away, but is provided here, for now
|
|
||||||
/// </summary>
|
|
||||||
internal interface IAsyncSoundProvider
|
|
||||||
{
|
|
||||||
void GetSamples(short[] samples);
|
|
||||||
void DiscardSamples();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// TODO: this is a shim for now, and needs to go away
|
|
||||||
/// turns an <seealso cref="IAsyncSoundProvider"/> into a full ISoundProvider
|
|
||||||
/// This is used in cores that have an async only sound implementation
|
|
||||||
/// better is implement a sync sound option in those cores without the need for
|
|
||||||
/// this class or an IAsyncSoundProvider interface
|
|
||||||
/// </summary>
|
|
||||||
internal class FakeSyncSound : ISoundProvider
|
|
||||||
{
|
|
||||||
private readonly IAsyncSoundProvider _source;
|
|
||||||
private readonly int _spf;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="FakeSyncSound"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The async sound provider</param>
|
|
||||||
/// <param name="spf">number of sample pairs to request and provide on each GetSamples() call</param>
|
|
||||||
public FakeSyncSound(IAsyncSoundProvider source, int spf)
|
|
||||||
{
|
|
||||||
_source = source;
|
|
||||||
_spf = spf;
|
|
||||||
SyncMode = SyncSoundMode.Sync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
|
||||||
{
|
|
||||||
if (SyncMode != SyncSoundMode.Sync)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Must be in sync mode to call a sync method");
|
|
||||||
}
|
|
||||||
|
|
||||||
short[] ret = new short[_spf * 2];
|
|
||||||
_source.GetSamples(ret);
|
|
||||||
samples = ret;
|
|
||||||
nsamp = _spf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DiscardSamples()
|
|
||||||
{
|
|
||||||
_source.DiscardSamples();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetSamplesAsync(short[] samples)
|
|
||||||
{
|
|
||||||
if (SyncMode != SyncSoundMode.Async)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Must be in async mode to call an async method");
|
|
||||||
}
|
|
||||||
|
|
||||||
_source.GetSamples(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanProvideAsync => true;
|
|
||||||
|
|
||||||
public SyncSoundMode SyncMode { get; private set; }
|
|
||||||
|
|
||||||
public void SetSyncMode(SyncSoundMode mode)
|
|
||||||
{
|
|
||||||
SyncMode = mode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An async sound provider
|
|
||||||
// This class needs to go away, it takes an IAsyncSoundProvider
|
|
||||||
// and is only used by legacy sound implementations
|
|
||||||
internal class MetaspuSoundProvider : ISoundProvider
|
|
||||||
{
|
|
||||||
public ISynchronizingAudioBuffer Buffer { get; } = new VecnaSynchronizer();
|
|
||||||
|
|
||||||
public bool CanProvideAsync => true;
|
|
||||||
|
|
||||||
public SyncSoundMode SyncMode => SyncSoundMode.Async;
|
|
||||||
|
|
||||||
public void SetSyncMode(SyncSoundMode mode)
|
|
||||||
{
|
|
||||||
if (mode != SyncSoundMode.Async)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Only Async mode is supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Sync mode is not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GetSamplesAsync(short[] samples)
|
|
||||||
{
|
|
||||||
Buffer.OutputSamples(samples, samples.Length / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DiscardSamples()
|
|
||||||
{
|
|
||||||
Buffer.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,35 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Components
|
namespace BizHawk.Emulation.Cores.Components
|
||||||
{
|
{
|
||||||
// TODO: Sound mixer is a good concept, but it needs to be refactored to use an ISoundProvider, it perhaps can enforce only receiving providers in Async mode
|
// TODO: Sound mixer is a good concept, but it needs to support sync mode
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An interface that extends a sound provider to provide mixing capabilities through the SoundMixer class
|
/// An interface that extends a sound provider to provide mixing capabilities through the SoundMixer class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IMixedSoundProvider : IAsyncSoundProvider
|
internal interface IMixedSoundProvider : ISoundProvider
|
||||||
{
|
{
|
||||||
int MaxVolume { get; set; }
|
int MaxVolume { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a straightforward class to mix/chain multiple ISoundProvider sources.
|
// This is a straightforward class to mix/chain multiple ISoundProvider sources.
|
||||||
internal sealed class SoundMixer : IAsyncSoundProvider
|
// Relies on a hack of passing in the samples per frame for sync sound abilities
|
||||||
|
internal sealed class SoundMixer : ISoundProvider
|
||||||
{
|
{
|
||||||
|
private readonly int _spf;
|
||||||
private readonly List<IMixedSoundProvider> _soundProviders;
|
private readonly List<IMixedSoundProvider> _soundProviders;
|
||||||
|
|
||||||
public SoundMixer(params IMixedSoundProvider[] soundProviders)
|
public SoundMixer(int spf, params IMixedSoundProvider[] soundProviders)
|
||||||
{
|
{
|
||||||
|
if (soundProviders.Any(s => !s.CanProvideAsync))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Sound mixer only works with async sound currently");
|
||||||
|
}
|
||||||
|
|
||||||
_soundProviders = new List<IMixedSoundProvider>(soundProviders);
|
_soundProviders = new List<IMixedSoundProvider>(soundProviders);
|
||||||
|
_spf = spf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisableSource(IMixedSoundProvider source)
|
public void DisableSource(IMixedSoundProvider source)
|
||||||
|
@ -34,11 +45,29 @@ namespace BizHawk.Emulation.Cores.Components
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GetSamples(short[] samples)
|
public bool CanProvideAsync => true;
|
||||||
|
|
||||||
|
public void SetSyncMode(SyncSoundMode mode) => SyncMode = mode;
|
||||||
|
public SyncSoundMode SyncMode { get; private set; }
|
||||||
|
|
||||||
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
if (SyncMode != SyncSoundMode.Sync)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Must be in sync mode to call a sync method");
|
||||||
|
}
|
||||||
|
|
||||||
|
short[] ret = new short[_spf * 2];
|
||||||
|
GetSamplesAsync(ret);
|
||||||
|
samples = ret;
|
||||||
|
nsamp = _spf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesAsync(short[] samples)
|
||||||
{
|
{
|
||||||
foreach (var soundSource in _soundProviders)
|
foreach (var soundSource in _soundProviders)
|
||||||
{
|
{
|
||||||
soundSource.GetSamples(samples);
|
soundSource.GetSamplesAsync(samples);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue