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\NullController.cs" />
<Compile Include="Interfaces\Base Implementations\NullEmulator.cs" /> <Compile Include="Interfaces\Base Implementations\NullEmulator.cs" />
<Compile Include="Interfaces\CoreComms.cs" /> <Compile Include="Interfaces\CoreComms.cs" />
<Compile Include="Interfaces\ISyncSoundProvider.cs" />
<Compile Include="Properties\svnrev.cs" /> <Compile Include="Properties\svnrev.cs" />
<Compile Include="QuickCollections.cs" /> <Compile Include="QuickCollections.cs" />
<Compile Include="Sound\CDAudio.cs" /> <Compile Include="Sound\CDAudio.cs" />

View File

@ -13,6 +13,9 @@ namespace BizHawk
public CoreOutputComm CoreOutputComm { get; private set; } public CoreOutputComm CoreOutputComm { get; private set; }
public IVideoProvider VideoProvider { get { return tia; } } public IVideoProvider VideoProvider { get { return tia; } }
public ISoundProvider SoundProvider { get { return dcfilter; } } 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) public Atari2600(GameInfo game, byte[] rom)
{ {

View File

@ -393,6 +393,9 @@ namespace BizHawk.Emulation.Consoles.Calculator
get { return new MyVideoProvider(this); } } get { return new MyVideoProvider(this); } }
public ISoundProvider SoundProvider { get { return NullSound.SilenceProvider; } } 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 = public static readonly ControllerDefinition TI83Controller =
new ControllerDefinition new ControllerDefinition

View File

@ -18,6 +18,9 @@ namespace BizHawk.Emulation.Consoles.Coleco
public CoreOutputComm CoreOutputComm { get; private set; } public CoreOutputComm CoreOutputComm { get; private set; }
public IVideoProvider VideoProvider { get { return this; } } public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { 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 byte[] ram = new byte[2048];
public DisplayType DisplayType { get; set; } //TOOD: delete me 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 IVideoProvider VideoProvider { get { return Stic; } }
public ISoundProvider SoundProvider { get { return NullSound.SilenceProvider; } } 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 = public static readonly ControllerDefinition IntellivisionController =
new ControllerDefinition new ControllerDefinition

View File

@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Consoles.GB
/// <summary> /// <summary>
/// a gameboy/gameboy color emulator wrapped around native C++ libgambatte /// a gameboy/gameboy color emulator wrapped around native C++ libgambatte
/// </summary> /// </summary>
public class Gameboy : IEmulator, IVideoProvider, ISoundProvider public class Gameboy : IEmulator, IVideoProvider, ISyncSoundProvider
{ {
/// <summary> /// <summary>
/// internal gambatte state /// internal gambatte state
@ -572,8 +572,11 @@ namespace BizHawk.Emulation.Consoles.GB
public ISoundProvider SoundProvider public ISoundProvider SoundProvider
{ {
get { return this; } get { return null; }
} }
public ISyncSoundProvider SyncSoundProvider { get { return dcfilter; } }
public bool StartAsyncSound() { return false; }
public void EndAsyncSound() { }
/// <summary> /// <summary>
/// sample pairs before resampling /// sample pairs before resampling
@ -585,15 +588,12 @@ namespace BizHawk.Emulation.Consoles.GB
int soundbuffcontains = 0; int soundbuffcontains = 0;
Sound.Utilities.SpeexResampler resampler; Sound.Utilities.SpeexResampler resampler;
Sound.MetaspuSoundProvider metaspu;
Sound.Utilities.DCFilter dcfilter; Sound.Utilities.DCFilter dcfilter;
void InitSound() void InitSound()
{ {
dcfilter = new Sound.Utilities.DCFilter(); resampler = new Sound.Utilities.SpeexResampler(2, 2097152, 44100, 2097152, 44100, null, this);
metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V); dcfilter = new Sound.Utilities.DCFilter(resampler, 65536);
resampler = new Sound.Utilities.SpeexResampler(2, 2097152, 44100, 2097152, 44100, LoadThroughSamples);
} }
void DisposeSound() void DisposeSound()
@ -602,29 +602,16 @@ namespace BizHawk.Emulation.Consoles.GB
resampler = null; 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() 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 #endregion
} }

View File

@ -207,6 +207,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
MyVideoProvider videoProvider; MyVideoProvider videoProvider;
public IVideoProvider VideoProvider { get { return videoProvider; } } public IVideoProvider VideoProvider { get { return videoProvider; } }
public ISoundProvider SoundProvider { get { return magicSoundProvider; } } 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 = public static readonly ControllerDefinition NESController =
new ControllerDefinition 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; } public bool IsSGB { get; private set; }
@ -643,7 +643,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
int vidWidth = 256, vidHeight = 224; int vidWidth = 256, vidHeight = 224;
public IVideoProvider VideoProvider { get { return this; } } public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { get { return this; } }
public ControllerDefinition ControllerDefinition { get { return SNESController; } } public ControllerDefinition ControllerDefinition { get { return SNESController; } }
IController controller; IController controller;
@ -992,39 +991,23 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
#region audio stuff #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.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) void snes_audio_sample(ushort left, ushort right)
{ {
resampler.EnqueueSample((short)left, (short)right); resampler.EnqueueSample((short)left, (short)right);
} }
public ISoundProvider SoundProvider { get { return null; } }
//BinaryWriter dbgs = new BinaryWriter(File.Open("dbgwav.raw", FileMode.Create, FileAccess.Write)); public ISyncSoundProvider SyncSoundProvider { get { return resampler; } }
public bool StartAsyncSound() { return false; }
public void GetSamples(short[] samples) public void EndAsyncSound() { }
{
resampler.Flush();
metaspu.GetSamples(samples);
}
public void DiscardSamples()
{
metaspu.DiscardSamples();
}
public int MaxVolume { get; set; }
#endregion audio stuff #endregion audio stuff
} }
} }

View File

@ -277,6 +277,9 @@ namespace BizHawk.Emulation.Consoles.TurboGrafx
{ {
get { return soundProvider; } 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 SystemId { get { return systemid; } }
public string Region { get; set; } public string Region { get; set; }

View File

@ -260,6 +260,9 @@ namespace BizHawk.Emulation.Consoles.Sega
{ {
get { return SoundMixer; } 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 Frame { get; set; }
public int LagCount { get { return _lagcount; } set { _lagcount = value; } } public int LagCount { get { return _lagcount; } set { _lagcount = value; } }

View File

@ -353,6 +353,9 @@ namespace BizHawk.Emulation.Consoles.Sega
ISoundProvider ActiveSoundProvider; ISoundProvider ActiveSoundProvider;
public ISoundProvider SoundProvider { get { return 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"; } } public string SystemId { get { return "SMS"; } }

View File

@ -15,6 +15,10 @@ namespace BizHawk
public CoreOutputComm CoreOutputComm { get; private set; } public CoreOutputComm CoreOutputComm { get; private set; }
public IVideoProvider VideoProvider { get { return this; } } public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { 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() public NullEmulator()
{ {
var domains = new List<MemoryDomain>(1); var domains = new List<MemoryDomain>(1);

View File

@ -8,6 +8,11 @@ namespace BizHawk
{ {
IVideoProvider VideoProvider { get; } IVideoProvider VideoProvider { get; }
ISoundProvider SoundProvider { 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; } ControllerDefinition ControllerDefinition { get; }
IController Controller { get; set; } 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> /// <summary>
/// implements a DC block filter on top of an ISoundProvider. rather simple. /// implements a DC block filter on top of an ISoundProvider. rather simple.
/// </summary> /// </summary>
public class DCFilter : ISoundProvider public class DCFilter : ISoundProvider, ISyncSoundProvider
{ {
/* /*
* A note about accuracy: * A note about accuracy:
@ -18,6 +18,7 @@ namespace BizHawk.Emulation.Sound.Utilities
*/ */
ISoundProvider input; ISoundProvider input;
ISyncSoundProvider syncinput;
int sumL = 0; int sumL = 0;
int sumR = 0; int sumR = 0;
@ -35,35 +36,41 @@ namespace BizHawk.Emulation.Sound.Utilities
if (filterwidth < 1 || filterwidth > 65536) if (filterwidth < 1 || filterwidth > 65536)
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
this.input = input; 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.depth = filterwidth;
this.buffer = new Queue<short>(depth * 2); this.buffer = new Queue<short>(depth * 2);
for (int i = 0; i < depth * 2; i++) for (int i = 0; i < depth * 2; i++)
buffer.Enqueue(0); 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> /// <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> /// </summary>
public void PushThroughSamples(short[] samples, int length) 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) for (int i = 0; i < length; i += 2)
{ {
sumL -= buffer.Dequeue(); sumL -= buffer.Dequeue();
sumR -= buffer.Dequeue(); sumR -= buffer.Dequeue();
short L = samples[i]; short L = samplesin[i];
short R = samples[i + 1]; short R = samplesin[i + 1];
sumL += L; sumL += L;
sumR += R; sumR += R;
buffer.Enqueue(L); buffer.Enqueue(L);
@ -72,19 +79,19 @@ namespace BizHawk.Emulation.Sound.Utilities
int bigR = R - sumR / depth; int bigR = R - sumR / depth;
// check for clipping // check for clipping
if (bigL > 32767) if (bigL > 32767)
samples[i] = 32767; samplesout[i] = 32767;
else if (bigL < -32768) else if (bigL < -32768)
samples[i] = -32768; samplesout[i] = -32768;
else else
samples[i] = (short)bigL; samplesout[i] = (short)bigL;
if (bigR > 32767) if (bigR > 32767)
samples[i + 1] = 32767; samplesout[i + 1] = 32767;
else if (bigR < -32768) else if (bigR < -32768)
samples[i + 1] = -32768; samplesout[i + 1] = -32768;
else else
samples[i + 1] = (short)bigR; samplesout[i + 1] = (short)bigR;
} }
} }
public void GetSamples(short[] samples) public void GetSamples(short[] samples)
@ -95,7 +102,10 @@ namespace BizHawk.Emulation.Sound.Utilities
public void DiscardSamples() public void DiscardSamples()
{ {
input.DiscardSamples(); if (input != null)
input.DiscardSamples();
if (syncinput != null)
syncinput.DiscardSamples();
} }
public int MaxVolume public int MaxVolume
@ -109,5 +119,16 @@ namespace BizHawk.Emulation.Sound.Utilities
input.MaxVolume = value; 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 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 class MetaspuSoundProvider : ISoundProvider
{ {
public ISynchronizingAudioBuffer buffer; public ISynchronizingAudioBuffer buffer;

View File

@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Sound.Utilities
/// <summary> /// <summary>
/// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced /// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced
/// </summary> /// </summary>
public class SpeexResampler : IDisposable public class SpeexResampler : IDisposable, ISyncSoundProvider
{ {
static class LibSpeexDSP static class LibSpeexDSP
{ {
@ -269,6 +269,13 @@ namespace BizHawk.Emulation.Sound.Utilities
short[] outbuf; short[] outbuf;
// for ISyncSoundProvider
short[] outbuf2 = new short[16];
int outbuf2pos = 0;
// to accept an ISyncSoundProvder input
ISyncSoundProvider input;
/// <summary> /// <summary>
/// in buffer position in samples (not sample pairs) /// in buffer position in samples (not sample pairs)
/// </summary> /// </summary>
@ -303,8 +310,9 @@ namespace BizHawk.Emulation.Sound.Utilities
/// <param name="ratioden">demonenator of srate change ratio (inrate / outrate)</param> /// <param name="ratioden">demonenator of srate change ratio (inrate / outrate)</param>
/// <param name="sratein">sampling rate in, rounded to nearest hz</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="srateout">sampling rate out, rounded to nearest hz</param>
/// <param name="drainer">function which accepts output as produced</param> /// <param name="drainer">function which accepts output as produced. if null, act as an ISyncSoundProvider</param>
public SpeexResampler(int quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action<short[], int> drainer) /// <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; LibSpeexDSP.RESAMPLER_ERR err = LibSpeexDSP.RESAMPLER_ERR.SUCCESS;
st = LibSpeexDSP.speex_resampler_init_frac(2, rationum, ratioden, sratein, srateout, quality, ref err); 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); 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))); if (drainer != null && input != null)
this.drainer = drainer; 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]; 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> /// <summary>
@ -372,62 +383,54 @@ namespace BizHawk.Emulation.Sound.Utilities
// reset inbuf // reset inbuf
Buffer.BlockCopy(inbuf, (int)inal * 2 * sizeof(short), inbuf, 0, inbufpos - (int)inal * 2); if (inal != inbufpos / 2)
inbufpos -= (int)inal * 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 // dispatch outbuf
drainer(outbuf, (int)outal); 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() public void Dispose()
{ {
LibSpeexDSP.speex_resampler_destroy(st); LibSpeexDSP.speex_resampler_destroy(st);
st = IntPtr.Zero; 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) private void soundToolStripMenuItem_Click(object sender, EventArgs e)
{ {
SoundConfig s = new SoundConfig(); 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) private void zoomMenuItem_Click(object sender, EventArgs e)

View File

@ -40,6 +40,7 @@ namespace BizHawk.MultiClient
public bool NeedsReboot = false; public bool NeedsReboot = false;
//avi/wav state //avi/wav state
IVideoWriter CurrAviWriter = null; IVideoWriter CurrAviWriter = null;
ISoundProvider AviSoundInput = null;
/// <summary> /// <summary>
/// an audio proxy used for dumping /// an audio proxy used for dumping
/// </summary> /// </summary>
@ -1645,10 +1646,41 @@ namespace BizHawk.MultiClient
Global.StickyXORAdapter.ClearStickies(); Global.StickyXORAdapter.ClearStickies();
Global.AutofireStickyXORAdapter.ClearStickies(); Global.AutofireStickyXORAdapter.ClearStickies();
RewireSound();
return true; 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() private void UpdateDumpIcon()
{ {
DumpStatus.Image = BizHawk.MultiClient.Properties.Resources.Blank; DumpStatus.Image = BizHawk.MultiClient.Properties.Resources.Blank;
@ -2259,10 +2291,8 @@ namespace BizHawk.MultiClient
SoundRemainder = nsampnum % Global.Emulator.CoreOutputComm.VsyncNum; SoundRemainder = nsampnum % Global.Emulator.CoreOutputComm.VsyncNum;
short[] temp = new short[nsamp * 2]; short[] temp = new short[nsamp * 2];
Global.Emulator.SoundProvider.GetSamples(temp); AviSoundInput.GetSamples(temp);
DumpProxy.buffer.enqueue_samples(temp, (int)nsamp); DumpProxy.buffer.enqueue_samples(temp, (int)nsamp);
//DumpProxy.GetSamples(temp);
//genSound = false;
if (Global.Config.AVI_CaptureOSD) if (Global.Config.AVI_CaptureOSD)
{ {
@ -2314,14 +2344,10 @@ namespace BizHawk.MultiClient
if (genSound) if (genSound)
{ {
// change audio path if dumping is occuring Global.Sound.UpdateSound();
if (DumpProxy != null)
Global.Sound.UpdateSound(DumpProxy);
else
Global.Sound.UpdateSound(Global.Emulator.SoundProvider);
} }
else else
Global.Sound.UpdateSound(NullSound.SilenceProvider); Global.Sound.UpdateSilence();
} }
@ -3377,6 +3403,7 @@ namespace BizHawk.MultiClient
AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.AVI; AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.AVI;
AVIStatusLabel.ToolTipText = "A/V capture in progress"; AVIStatusLabel.ToolTipText = "A/V capture in progress";
AVIStatusLabel.Visible = true; AVIStatusLabel.Visible = true;
} }
catch catch
{ {
@ -3385,9 +3412,14 @@ namespace BizHawk.MultiClient
throw; 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); DumpProxy = new Emulation.Sound.MetaspuSoundProvider(Emulation.Sound.ESynchMethod.ESynchMethod_V);
SoundRemainder = 0; SoundRemainder = 0;
RewireSound();
} }
public void StopAVI() public void StopAVI()
@ -3395,6 +3427,7 @@ namespace BizHawk.MultiClient
if (CurrAviWriter == null) if (CurrAviWriter == null)
{ {
DumpProxy = null; DumpProxy = null;
RewireSound();
return; return;
} }
CurrAviWriter.CloseFile(); CurrAviWriter.CloseFile();
@ -3404,8 +3437,10 @@ namespace BizHawk.MultiClient
AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.Blank; AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.Blank;
AVIStatusLabel.ToolTipText = ""; AVIStatusLabel.ToolTipText = "";
AVIStatusLabel.Visible = false; AVIStatusLabel.Visible = false;
AviSoundInput = null;
DumpProxy = null; // return to normal sound output DumpProxy = null; // return to normal sound output
SoundRemainder = 0; SoundRemainder = 0;
RewireSound();
} }
private void SwapBackupSavestate(string path) private void SwapBackupSavestate(string path)

View File

@ -12,7 +12,7 @@ namespace BizHawk.MultiClient
#if WINDOWS #if WINDOWS
public class Sound : IDisposable public class Sound : IDisposable
{ {
public bool Muted = false; private bool Muted = false;
private bool disposed = false; private bool disposed = false;
private SecondarySoundBuffer DSoundBuffer; private SecondarySoundBuffer DSoundBuffer;
@ -23,6 +23,9 @@ namespace BizHawk.MultiClient
private BufferedAsync semisync = new BufferedAsync(); private BufferedAsync semisync = new BufferedAsync();
private ISoundProvider asyncsoundProvider;
private ISyncSoundProvider syncsoundProvider;
public Sound(IntPtr handle, DirectSound device) public Sound(IntPtr handle, DirectSound device)
{ {
if (device != null) 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) static int circularDist(int from, int to, int size)
{ {
if (size == 0) if (size == 0)
@ -121,53 +138,68 @@ namespace BizHawk.MultiClient
return curToPlay / 4; 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) if (Global.Config.SoundEnabled == false || disposed)
{ {
soundProvider.DiscardSamples(); if (asyncsoundProvider != null) asyncsoundProvider.DiscardSamples();
if (syncsoundProvider != null) syncsoundProvider.DiscardSamples();
return; return;
} }
int samplesNeeded = SNDDXGetAudioSpace() * 2; int samplesNeeded = SNDDXGetAudioSpace() * 2;
short[] samples; short[] samples;
if (Global.Config.SoundThrottle && !Global.ForceNoThrottle) int samplesProvided = 0;
{
if (DSoundBuffer == null) return; // can cause SNDDXGetAudioSpace() = 0
int samplesWanted = 2 * (int) (0.8 + 44100.0 / Global.Emulator.CoreOutputComm.VsyncRate);
while (samplesNeeded < samplesWanted)
{ if (Muted)
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 (samplesNeeded == 0) if (samplesNeeded == 0)
return; return;
samples = new short[samplesNeeded]; samples = new short[samplesNeeded];
if (soundProvider != null && Muted == false) samplesProvided = samplesNeeded;
{
semisync.BaseSoundProvider = soundProvider;
semisync.GetSamples(samples);
}
else soundProvider.DiscardSamples();
} }
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; int cursor = soundoffset;
for (int i = 0; i < samples.Length; i++) for (int i = 0; i < samplesProvided; i++)
{ {
short s = samples[i]; short s = samples[i];
SoundBuffer[cursor++] = (byte)(s & 0xFF); SoundBuffer[cursor++] = (byte)(s & 0xFF);
@ -179,7 +211,7 @@ namespace BizHawk.MultiClient
DSoundBuffer.Write(SoundBuffer, 0, LockFlags.EntireBuffer); DSoundBuffer.Write(SoundBuffer, 0, LockFlags.EntireBuffer);
soundoffset += samplesNeeded * 2; soundoffset += samplesProvided * 2;
soundoffset %= BufferSize; soundoffset %= BufferSize;
} }

View File

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