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:
parent
f234e15df6
commit
b40897bb77
|
@ -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" />
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; } }
|
||||
|
|
|
@ -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"; } }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue