sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.

This commit is contained in:
goyuken 2012-10-11 00:44:59 +00:00
parent f234e15df6
commit b40897bb77
21 changed files with 349 additions and 173 deletions

View File

@ -353,6 +353,7 @@
<Compile Include="Interfaces\Base Implementations\NullController.cs" />
<Compile Include="Interfaces\Base Implementations\NullEmulator.cs" />
<Compile Include="Interfaces\CoreComms.cs" />
<Compile Include="Interfaces\ISyncSoundProvider.cs" />
<Compile Include="Properties\svnrev.cs" />
<Compile Include="QuickCollections.cs" />
<Compile Include="Sound\CDAudio.cs" />

View File

@ -13,6 +13,9 @@ namespace BizHawk
public CoreOutputComm CoreOutputComm { get; private set; }
public IVideoProvider VideoProvider { get { return tia; } }
public ISoundProvider SoundProvider { get { return dcfilter; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(dcfilter, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public Atari2600(GameInfo game, byte[] rom)
{

View File

@ -393,6 +393,9 @@ namespace BizHawk.Emulation.Consoles.Calculator
get { return new MyVideoProvider(this); } }
public ISoundProvider SoundProvider { get { return NullSound.SilenceProvider; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(NullSound.SilenceProvider, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public static readonly ControllerDefinition TI83Controller =
new ControllerDefinition

View File

@ -18,6 +18,9 @@ namespace BizHawk.Emulation.Consoles.Coleco
public CoreOutputComm CoreOutputComm { get; private set; }
public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { get { return this; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(this, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public byte[] ram = new byte[2048];
public DisplayType DisplayType { get; set; } //TOOD: delete me

View File

@ -85,6 +85,9 @@ namespace BizHawk.Emulation.Consoles.Intellivision
public IVideoProvider VideoProvider { get { return Stic; } }
public ISoundProvider SoundProvider { get { return NullSound.SilenceProvider; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(NullSound.SilenceProvider, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public static readonly ControllerDefinition IntellivisionController =
new ControllerDefinition

View File

@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Consoles.GB
/// <summary>
/// a gameboy/gameboy color emulator wrapped around native C++ libgambatte
/// </summary>
public class Gameboy : IEmulator, IVideoProvider, ISoundProvider
public class Gameboy : IEmulator, IVideoProvider, ISyncSoundProvider
{
/// <summary>
/// internal gambatte state
@ -572,8 +572,11 @@ namespace BizHawk.Emulation.Consoles.GB
public ISoundProvider SoundProvider
{
get { return this; }
get { return null; }
}
public ISyncSoundProvider SyncSoundProvider { get { return dcfilter; } }
public bool StartAsyncSound() { return false; }
public void EndAsyncSound() { }
/// <summary>
/// sample pairs before resampling
@ -585,15 +588,12 @@ namespace BizHawk.Emulation.Consoles.GB
int soundbuffcontains = 0;
Sound.Utilities.SpeexResampler resampler;
Sound.MetaspuSoundProvider metaspu;
Sound.Utilities.DCFilter dcfilter;
void InitSound()
{
dcfilter = new Sound.Utilities.DCFilter();
metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
resampler = new Sound.Utilities.SpeexResampler(2, 2097152, 44100, 2097152, 44100, LoadThroughSamples);
resampler = new Sound.Utilities.SpeexResampler(2, 2097152, 44100, 2097152, 44100, null, this);
dcfilter = new Sound.Utilities.DCFilter(resampler, 65536);
}
void DisposeSound()
@ -602,29 +602,16 @@ namespace BizHawk.Emulation.Consoles.GB
resampler = null;
}
void LoadThroughSamples(short[] buff, int length)
{
dcfilter.PushThroughSamples(buff, length * 2);
metaspu.buffer.enqueue_samples(buff, length);
}
public void GetSamples(short[] samples)
{
if (soundbuffcontains > 0)
{
resampler.EnqueueSamples(soundbuff, soundbuffcontains);
soundbuffcontains = 0;
resampler.Flush();
metaspu.GetSamples(samples);
}
}
public void DiscardSamples()
{
metaspu.DiscardSamples();
soundbuffcontains = 0;
}
public int MaxVolume { get; set; }
public void GetSamples(out short[] samples, out int nsamp)
{
samples = soundbuff;
nsamp = soundbuffcontains;
}
#endregion
}

View File

@ -207,6 +207,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
MyVideoProvider videoProvider;
public IVideoProvider VideoProvider { get { return videoProvider; } }
public ISoundProvider SoundProvider { get { return magicSoundProvider; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(magicSoundProvider, 734); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public static readonly ControllerDefinition NESController =
new ControllerDefinition

View File

@ -282,7 +282,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
}
}
public unsafe class LibsnesCore : IEmulator, IVideoProvider, ISoundProvider
public unsafe class LibsnesCore : IEmulator, IVideoProvider
{
public bool IsSGB { get; private set; }
@ -643,7 +643,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
int vidWidth = 256, vidHeight = 224;
public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { get { return this; } }
public ControllerDefinition ControllerDefinition { get { return SNESController; } }
IController controller;
@ -992,39 +991,23 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
#region audio stuff
void InitAudio()
{
metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
resampler = new Sound.Utilities.SpeexResampler(6, 64081, 88200, 32041, 44100, new Action<short[], int>(metaspu.buffer.enqueue_samples));
}
Sound.Utilities.SpeexResampler resampler;
Sound.MetaspuSoundProvider metaspu;
void InitAudio()
{
resampler = new Sound.Utilities.SpeexResampler(6, 64081, 88200, 32041, 44100);
}
void snes_audio_sample(ushort left, ushort right)
{
resampler.EnqueueSample((short)left, (short)right);
}
//BinaryWriter dbgs = new BinaryWriter(File.Open("dbgwav.raw", FileMode.Create, FileAccess.Write));
public void GetSamples(short[] samples)
{
resampler.Flush();
metaspu.GetSamples(samples);
}
public void DiscardSamples()
{
metaspu.DiscardSamples();
}
public int MaxVolume { get; set; }
public ISoundProvider SoundProvider { get { return null; } }
public ISyncSoundProvider SyncSoundProvider { get { return resampler; } }
public bool StartAsyncSound() { return false; }
public void EndAsyncSound() { }
#endregion audio stuff
}
}

View File

@ -277,6 +277,9 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
{
get { return soundProvider; }
}
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(soundProvider, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public string SystemId { get { return systemid; } }
public string Region { get; set; }

View File

@ -260,6 +260,9 @@ namespace BizHawk.Emulation.Consoles.Sega
{
get { return SoundMixer; }
}
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(SoundMixer, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public int Frame { get; set; }
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }

View File

@ -353,6 +353,9 @@ namespace BizHawk.Emulation.Consoles.Sega
ISoundProvider ActiveSoundProvider;
public ISoundProvider SoundProvider { get { return ActiveSoundProvider; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(ActiveSoundProvider, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public string SystemId { get { return "SMS"; } }

View File

@ -15,6 +15,10 @@ namespace BizHawk
public CoreOutputComm CoreOutputComm { get; private set; }
public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { get { return this; } }
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(this, 735); } }
public bool StartAsyncSound() { return true; }
public void EndAsyncSound() { }
public NullEmulator()
{
var domains = new List<MemoryDomain>(1);

View File

@ -8,6 +8,11 @@ namespace BizHawk
{
IVideoProvider VideoProvider { get; }
ISoundProvider SoundProvider { get; }
ISyncSoundProvider SyncSoundProvider { get; }
/// <summary></summary>
/// <returns>false if core doesn't support async sound</returns>
bool StartAsyncSound();
void EndAsyncSound();
ControllerDefinition ControllerDefinition { get; }
IController Controller { get; set; }

View File

@ -0,0 +1,45 @@
namespace BizHawk
{
public interface ISyncSoundProvider
{
/// <summary>
///
/// </summary>
/// <param name="samples"></param>
/// <param name="nsamp">number of sample PAIRS available</param>
void GetSamples(out short[] samples, out int nsamp);
/// <summary>
///
/// </summary>
void DiscardSamples();
}
public class FakeSyncSound : ISyncSoundProvider
{
ISoundProvider source;
int spf;
/// <summary>
///
/// </summary>
/// <param name="source"></param>
/// <param name="spf">number of sample pairs to request for each call</param>
public FakeSyncSound(ISoundProvider source, int spf)
{
this.source = source;
this.spf = spf;
}
public void GetSamples(out short[] samples, out int nsamp)
{
short[] ret = new short[spf * 2];
source.GetSamples(ret);
samples = ret;
nsamp = spf;
}
public void DiscardSamples()
{
source.DiscardSamples();
}
}
}

View File

@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Sound.Utilities
/// <summary>
/// implements a DC block filter on top of an ISoundProvider. rather simple.
/// </summary>
public class DCFilter : ISoundProvider
public class DCFilter : ISoundProvider, ISyncSoundProvider
{
/*
* A note about accuracy:
@ -18,6 +18,7 @@ namespace BizHawk.Emulation.Sound.Utilities
*/
ISoundProvider input;
ISyncSoundProvider syncinput;
int sumL = 0;
int sumR = 0;
@ -35,35 +36,41 @@ namespace BizHawk.Emulation.Sound.Utilities
if (filterwidth < 1 || filterwidth > 65536)
throw new ArgumentOutOfRangeException();
this.input = input;
this.syncinput = null;
this.depth = filterwidth;
this.buffer = new Queue<short>(depth * 2);
for (int i = 0; i < depth * 2; i++)
buffer.Enqueue(0);
}
public DCFilter(ISyncSoundProvider input, int filterwidth)
{
if (filterwidth < 1 || filterwidth > 65536)
throw new ArgumentOutOfRangeException();
this.input = null;
this.syncinput = input;
this.depth = filterwidth;
this.buffer = new Queue<short>(depth * 2);
for (int i = 0; i < depth * 2; i++)
buffer.Enqueue(0);
}
/// <summary>
/// returns the original sound provider (in case you lost it).
/// after calling, the DCFilter is no longer valid
/// </summary>
/// <returns></returns>
public ISoundProvider Detatch()
{
var ret = input;
input = null;
return ret;
}
/// <summary>
/// pass a set of samples through the filter. should not be mixed with pull (ISoundProvider) mode
/// pass a set of samples through the filter. should not be mixed with pull (ISoundProvider) mode or sync mode
/// </summary>
public void PushThroughSamples(short[] samples, int length)
{
PushThroughSamples(samples, samples, length);
}
void PushThroughSamples(short[] samplesin, short[] samplesout, int length)
{
for (int i = 0; i < length; i += 2)
{
sumL -= buffer.Dequeue();
sumR -= buffer.Dequeue();
short L = samples[i];
short R = samples[i + 1];
short L = samplesin[i];
short R = samplesin[i + 1];
sumL += L;
sumR += R;
buffer.Enqueue(L);
@ -72,19 +79,19 @@ namespace BizHawk.Emulation.Sound.Utilities
int bigR = R - sumR / depth;
// check for clipping
if (bigL > 32767)
samples[i] = 32767;
samplesout[i] = 32767;
else if (bigL < -32768)
samples[i] = -32768;
samplesout[i] = -32768;
else
samples[i] = (short)bigL;
samplesout[i] = (short)bigL;
if (bigR > 32767)
samples[i + 1] = 32767;
samplesout[i + 1] = 32767;
else if (bigR < -32768)
samples[i + 1] = -32768;
samplesout[i + 1] = -32768;
else
samples[i + 1] = (short)bigR;
samplesout[i + 1] = (short)bigR;
}
}
public void GetSamples(short[] samples)
@ -95,7 +102,10 @@ namespace BizHawk.Emulation.Sound.Utilities
public void DiscardSamples()
{
input.DiscardSamples();
if (input != null)
input.DiscardSamples();
if (syncinput != null)
syncinput.DiscardSamples();
}
public int MaxVolume
@ -109,5 +119,16 @@ namespace BizHawk.Emulation.Sound.Utilities
input.MaxVolume = value;
}
}
public void GetSamples(out short[] samples, out int nsamp)
{
short[] sampin;
int nsampin;
syncinput.GetSamples(out sampin, out nsampin);
short[] ret = new short[nsampin * 2];
PushThroughSamples(sampin, ret, nsampin * 2);
samples = ret;
nsamp = nsampin;
}
}
}

View File

@ -3,6 +3,39 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.Sound
{
/// <summary>
/// uses Metaspu to have an ISyncSoundProvider input to a ISoundProvider
/// </summary>
public class MetaspuAsync : ISoundProvider
{
ISynchronizingAudioBuffer buffer;
ISyncSoundProvider input;
public MetaspuAsync(ISyncSoundProvider input, ESynchMethod method)
{
buffer = Metaspu.metaspu_construct(method);
this.input = input;
}
public void GetSamples(short[] samples)
{
short[] sampin;
int numsamp;
input.GetSamples(out sampin, out numsamp);
buffer.enqueue_samples(sampin, numsamp);
buffer.output_samples(samples, samples.Length / 2);
}
public void DiscardSamples()
{
input.DiscardSamples();
buffer.clear();
}
public int MaxVolume { get; set; }
}
public class MetaspuSoundProvider : ISoundProvider
{
public ISynchronizingAudioBuffer buffer;

View File

@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Sound.Utilities
/// <summary>
/// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced
/// </summary>
public class SpeexResampler : IDisposable
public class SpeexResampler : IDisposable, ISyncSoundProvider
{
static class LibSpeexDSP
{
@ -269,6 +269,13 @@ namespace BizHawk.Emulation.Sound.Utilities
short[] outbuf;
// for ISyncSoundProvider
short[] outbuf2 = new short[16];
int outbuf2pos = 0;
// to accept an ISyncSoundProvder input
ISyncSoundProvider input;
/// <summary>
/// in buffer position in samples (not sample pairs)
/// </summary>
@ -303,8 +310,9 @@ namespace BizHawk.Emulation.Sound.Utilities
/// <param name="ratioden">demonenator of srate change ratio (inrate / outrate)</param>
/// <param name="sratein">sampling rate in, rounded to nearest hz</param>
/// <param name="srateout">sampling rate out, rounded to nearest hz</param>
/// <param name="drainer">function which accepts output as produced</param>
public SpeexResampler(int quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action<short[], int> drainer)
/// <param name="drainer">function which accepts output as produced. if null, act as an ISyncSoundProvider</param>
/// <param name="input">source to take input from when output is requested. if null, no autofetching</param>
public SpeexResampler(int quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action<short[], int> drainer = null, ISyncSoundProvider input = null)
{
LibSpeexDSP.RESAMPLER_ERR err = LibSpeexDSP.RESAMPLER_ERR.SUCCESS;
st = LibSpeexDSP.speex_resampler_init_frac(2, rationum, ratioden, sratein, srateout, quality, ref err);
@ -314,13 +322,16 @@ namespace BizHawk.Emulation.Sound.Utilities
CheckError(err);
//System.Windows.Forms.MessageBox.Show(string.Format("inlat: {0} outlat: {1}", LibSpeexDSP.speex_resampler_get_input_latency(st), LibSpeexDSP.speex_resampler_get_output_latency(st)));
this.drainer = drainer;
if (drainer != null && input != null)
throw new ArgumentException("Can't autofetch without being an ISyncSoundProvider?");
if (drainer != null)
this.drainer = drainer;
else
this.drainer = InternalDrain;
this.input = input;
outbuf = new short[inbuf.Length * ratioden / rationum / 2 * 2 + 128];
//System.Windows.Forms.MessageBox.Show(string.Format("inbuf: {0} outbuf: {1}", inbuf.Length, outbuf.Length));
}
/// <summary>
@ -372,62 +383,54 @@ namespace BizHawk.Emulation.Sound.Utilities
// reset inbuf
Buffer.BlockCopy(inbuf, (int)inal * 2 * sizeof(short), inbuf, 0, inbufpos - (int)inal * 2);
inbufpos -= (int)inal * 2;
if (inal != inbufpos / 2)
throw new Exception("Speexresampler didn't eat the whole array?");
inbufpos = 0;
//Buffer.BlockCopy(inbuf, (int)inal * 2 * sizeof(short), inbuf, 0, inbufpos - (int)inal * 2);
//inbufpos -= (int)inal * 2;
// dispatch outbuf
drainer(outbuf, (int)outal);
}
/*
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
{
while (input.Count > 0)
{
short[] ina = input.ToArray();
short[] outa = new short[8192];
uint inal = (uint)ina.Length / 2;
uint outal = (uint)outa.Length / 2;
// very important: feeding too big a buffer at once causes garbage to come back
// don't know what "too big a buffer" is in general
if (inal > 512) inal = 512;
LibSpeexDSP.speex_resampler_process_interleaved_int(st, ina, ref inal, outa, ref outal);
while (inal > 0)
{
input.Dequeue();
input.Dequeue();
inal--;
}
int i = 0;
if (outal == 0)
{
// resampler refuses to make more data; bail
return;
}
while (outal > 0)
{
output.Enqueue(outa[i++]);
output.Enqueue(outa[i++]);
outal--;
}
}
}
*/
public void Dispose()
{
LibSpeexDSP.speex_resampler_destroy(st);
st = IntPtr.Zero;
}
void InternalDrain(short[] buf, int nsamp)
{
if (outbuf2pos + nsamp * 2 > outbuf2.Length)
{
short[] newbuf = new short[outbuf2pos + nsamp * 2];
Buffer.BlockCopy(outbuf2, 0, newbuf, 0, outbuf2pos * sizeof(short));
outbuf2 = newbuf;
}
Buffer.BlockCopy(buf, 0, outbuf2, outbuf2pos * sizeof(short), nsamp * 2 * sizeof(short));
outbuf2pos += nsamp * 2;
}
public void GetSamples(out short[] samples, out int nsamp)
{
if (input != null)
{
short[] sampin;
int nsampin;
input.GetSamples(out sampin, out nsampin);
EnqueueSamples(sampin, nsampin);
}
Flush();
nsamp = outbuf2pos / 2;
samples = outbuf2;
outbuf2pos = 0;
}
public void DiscardSamples()
{
outbuf2pos = 0;
}
}
}

View File

@ -140,7 +140,9 @@ namespace BizHawk.MultiClient
private void soundToolStripMenuItem_Click(object sender, EventArgs e)
{
SoundConfig s = new SoundConfig();
s.ShowDialog();
var result = s.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
RewireSound();
}
private void zoomMenuItem_Click(object sender, EventArgs e)

View File

@ -40,6 +40,7 @@ namespace BizHawk.MultiClient
public bool NeedsReboot = false;
//avi/wav state
IVideoWriter CurrAviWriter = null;
ISoundProvider AviSoundInput = null;
/// <summary>
/// an audio proxy used for dumping
/// </summary>
@ -1645,10 +1646,41 @@ namespace BizHawk.MultiClient
Global.StickyXORAdapter.ClearStickies();
Global.AutofireStickyXORAdapter.ClearStickies();
RewireSound();
return true;
}
}
void RewireSound()
{
if (DumpProxy != null)
{
// we're video dumping, so async mode only and use the DumpProxy.
// note that the avi dumper has already rewired the emulator itself in this case.
Global.Sound.SetAsyncInputPin(DumpProxy);
}
else if (Global.Config.SoundThrottle)
{
// for sound throttle, use sync mode
Global.Emulator.EndAsyncSound();
Global.Sound.SetSyncInputPin(Global.Emulator.SyncSoundProvider);
}
else
{
// for vsync\clock throttle modes, use async
if (!Global.Emulator.StartAsyncSound())
{
// if the core doesn't support async mode, use a standard vecna wrapper
Global.Sound.SetAsyncInputPin(new Emulation.Sound.MetaspuAsync(Global.Emulator.SyncSoundProvider, Emulation.Sound.ESynchMethod.ESynchMethod_V));
}
else
{
Global.Sound.SetAsyncInputPin(Global.Emulator.SoundProvider);
}
}
}
private void UpdateDumpIcon()
{
DumpStatus.Image = BizHawk.MultiClient.Properties.Resources.Blank;
@ -2259,10 +2291,8 @@ namespace BizHawk.MultiClient
SoundRemainder = nsampnum % Global.Emulator.CoreOutputComm.VsyncNum;
short[] temp = new short[nsamp * 2];
Global.Emulator.SoundProvider.GetSamples(temp);
AviSoundInput.GetSamples(temp);
DumpProxy.buffer.enqueue_samples(temp, (int)nsamp);
//DumpProxy.GetSamples(temp);
//genSound = false;
if (Global.Config.AVI_CaptureOSD)
{
@ -2314,14 +2344,10 @@ namespace BizHawk.MultiClient
if (genSound)
{
// change audio path if dumping is occuring
if (DumpProxy != null)
Global.Sound.UpdateSound(DumpProxy);
else
Global.Sound.UpdateSound(Global.Emulator.SoundProvider);
Global.Sound.UpdateSound();
}
else
Global.Sound.UpdateSound(NullSound.SilenceProvider);
Global.Sound.UpdateSilence();
}
@ -3377,6 +3403,7 @@ namespace BizHawk.MultiClient
AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.AVI;
AVIStatusLabel.ToolTipText = "A/V capture in progress";
AVIStatusLabel.Visible = true;
}
catch
{
@ -3385,9 +3412,14 @@ namespace BizHawk.MultiClient
throw;
}
// buffersize here is entirely guess
// do sound rewire. the plan is to eventually have AVI writing support syncsound input, but it doesn't for the moment
if (!Global.Emulator.StartAsyncSound())
AviSoundInput = new Emulation.Sound.MetaspuAsync(Global.Emulator.SyncSoundProvider, Emulation.Sound.ESynchMethod.ESynchMethod_V);
else
AviSoundInput = Global.Emulator.SoundProvider;
DumpProxy = new Emulation.Sound.MetaspuSoundProvider(Emulation.Sound.ESynchMethod.ESynchMethod_V);
SoundRemainder = 0;
RewireSound();
}
public void StopAVI()
@ -3395,6 +3427,7 @@ namespace BizHawk.MultiClient
if (CurrAviWriter == null)
{
DumpProxy = null;
RewireSound();
return;
}
CurrAviWriter.CloseFile();
@ -3404,8 +3437,10 @@ namespace BizHawk.MultiClient
AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.Blank;
AVIStatusLabel.ToolTipText = "";
AVIStatusLabel.Visible = false;
AviSoundInput = null;
DumpProxy = null; // return to normal sound output
SoundRemainder = 0;
RewireSound();
}
private void SwapBackupSavestate(string path)

View File

@ -12,7 +12,7 @@ namespace BizHawk.MultiClient
#if WINDOWS
public class Sound : IDisposable
{
public bool Muted = false;
private bool Muted = false;
private bool disposed = false;
private SecondarySoundBuffer DSoundBuffer;
@ -23,6 +23,9 @@ namespace BizHawk.MultiClient
private BufferedAsync semisync = new BufferedAsync();
private ISoundProvider asyncsoundProvider;
private ISyncSoundProvider syncsoundProvider;
public Sound(IntPtr handle, DirectSound device)
{
if (device != null)
@ -94,6 +97,20 @@ namespace BizHawk.MultiClient
}
}
public void SetSyncInputPin(ISyncSoundProvider source)
{
syncsoundProvider = source;
asyncsoundProvider = null;
semisync.DiscardSamples();
}
public void SetAsyncInputPin(ISoundProvider source)
{
syncsoundProvider = null;
asyncsoundProvider = source;
semisync.BaseSoundProvider = source;
}
static int circularDist(int from, int to, int size)
{
if (size == 0)
@ -121,53 +138,68 @@ namespace BizHawk.MultiClient
return curToPlay / 4;
}
public void UpdateSound(ISoundProvider soundProvider)
public void UpdateSilence()
{
Muted = true;
UpdateSound();
Muted = false;
}
public void UpdateSound()
{
if (Global.Config.SoundEnabled == false || disposed)
{
soundProvider.DiscardSamples();
if (asyncsoundProvider != null) asyncsoundProvider.DiscardSamples();
if (syncsoundProvider != null) syncsoundProvider.DiscardSamples();
return;
}
int samplesNeeded = SNDDXGetAudioSpace() * 2;
short[] samples;
if (Global.Config.SoundThrottle && !Global.ForceNoThrottle)
{
if (DSoundBuffer == null) return; // can cause SNDDXGetAudioSpace() = 0
int samplesWanted = 2 * (int) (0.8 + 44100.0 / Global.Emulator.CoreOutputComm.VsyncRate);
int samplesProvided = 0;
while (samplesNeeded < samplesWanted)
{
System.Threading.Thread.Sleep((samplesWanted - samplesNeeded) / 88); // let audio clock control sleep time
samplesNeeded = SNDDXGetAudioSpace() * 2;
}
samplesNeeded = samplesWanted;
samples = new short[samplesNeeded];
// no semisync
if (!Muted)
soundProvider.GetSamples(samples);
else
soundProvider.DiscardSamples();
}
else
if (Muted)
{
if (samplesNeeded == 0)
return;
samples = new short[samplesNeeded];
if (soundProvider != null && Muted == false)
{
semisync.BaseSoundProvider = soundProvider;
semisync.GetSamples(samples);
}
else soundProvider.DiscardSamples();
samplesProvided = samplesNeeded;
}
else if (syncsoundProvider != null)
{
if (DSoundBuffer == null) return; // can cause SNDDXGetAudioSpace() = 0
int nsampgot;
syncsoundProvider.GetSamples(out samples, out nsampgot);
samplesProvided = 2 * nsampgot;
while (samplesNeeded < samplesProvided)
{
System.Threading.Thread.Sleep((samplesProvided - samplesNeeded) / 88); // let audio clock control sleep time
samplesNeeded = SNDDXGetAudioSpace() * 2;
}
}
else if (asyncsoundProvider != null)
{
if (samplesNeeded == 0)
return;
samples = new short[samplesNeeded];
//if (asyncsoundProvider != null && Muted == false)
//{
semisync.BaseSoundProvider = asyncsoundProvider;
semisync.GetSamples(samples);
//}
//else asyncsoundProvider.DiscardSamples();
samplesProvided = samplesNeeded;
}
else
return;
//Console.WriteLine(samplesNeeded/2);
int cursor = soundoffset;
for (int i = 0; i < samples.Length; i++)
for (int i = 0; i < samplesProvided; i++)
{
short s = samples[i];
SoundBuffer[cursor++] = (byte)(s & 0xFF);
@ -179,7 +211,7 @@ namespace BizHawk.MultiClient
DSoundBuffer.Write(SoundBuffer, 0, LockFlags.EntireBuffer);
soundoffset += samplesNeeded * 2;
soundoffset += samplesProvided * 2;
soundoffset %= BufferSize;
}

View File

@ -56,6 +56,7 @@
// OK
//
this.OK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.OK.Location = new System.Drawing.Point(119, 209);
this.OK.Name = "OK";
this.OK.Size = new System.Drawing.Size(75, 23);