cleanup IAsyncSoundProvider hacks and consolidate code that is only used for PCE

This commit is contained in:
adelikat 2020-05-09 11:01:02 -05:00
parent b16684b4c7
commit 976ea4967b
7 changed files with 238 additions and 280 deletions

View File

@ -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();
} }
} }
} }

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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);
} }
} }