nes-big improvements to sound, hopefully. fix regressions from last night which killed a lot of music.
This commit is contained in:
parent
0e09a071cf
commit
0b03e6edc8
|
@ -1,4 +1,7 @@
|
|||
//TODO - so many integers in the square wave output keep us from exactly unbiasing the waveform. also other waves probably
|
||||
//TODO - so many integers in the square wave output keep us from exactly unbiasing the waveform. also other waves probably. consider improving the unbiasing.
|
||||
//ALSO - consider whether we should even be doing it: the nonlinear-mixing behaviour probably depends on those biases being there.
|
||||
//if we have a better high-pass filter somewhere then we might could cope with the weird biases
|
||||
//(mix higher integer precision with the non-linear mixer and then highpass filter befoure outputting s16s)
|
||||
//TODO - DMC cpu suspending - http://forums.nesdev.com/viewtopic.php?p=62690#p62690
|
||||
|
||||
using System;
|
||||
|
@ -20,9 +23,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
partial class NES
|
||||
{
|
||||
public class APU : ISoundProvider
|
||||
public class APU
|
||||
{
|
||||
public static bool CFG_USE_METASPU = true;
|
||||
public static bool CFG_DECLICK = true;
|
||||
|
||||
public bool EnableSquare1 = false;
|
||||
|
@ -31,7 +33,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
public bool EnableNoise = false;
|
||||
public bool EnableDMC = true;
|
||||
|
||||
|
||||
NES nes;
|
||||
public APU(NES nes)
|
||||
{
|
||||
|
@ -545,6 +546,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
out_silence = true;
|
||||
timer_reload = DMC_RATE_NTSC[0];
|
||||
sample_buffer_filled = false;
|
||||
out_deltacounter = 64;
|
||||
out_bits_remaining = 0;
|
||||
}
|
||||
|
||||
bool irq_enabled;
|
||||
|
@ -559,7 +562,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
int out_shift, out_bits_remaining, out_deltacounter;
|
||||
bool out_silence;
|
||||
|
||||
public int sample;
|
||||
public int sample { get { return out_deltacounter - 64; } }
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
|
@ -581,6 +584,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
ser.Sync("out_deltacounter", ref out_deltacounter);
|
||||
ser.Sync("out_silence", ref out_silence);
|
||||
|
||||
int sample = 0; //junk
|
||||
ser.Sync("sample", ref sample);
|
||||
}
|
||||
|
||||
|
@ -592,23 +596,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
timer = timer_reload;
|
||||
Clock();
|
||||
}
|
||||
|
||||
SyncSample();
|
||||
}
|
||||
|
||||
void SyncSample()
|
||||
{
|
||||
if (out_silence)
|
||||
sample = 0;
|
||||
else
|
||||
{
|
||||
sample = out_deltacounter;
|
||||
sample -= 64; //unbias;
|
||||
}
|
||||
}
|
||||
|
||||
void Clock()
|
||||
{
|
||||
//If the silence flag is clear, bit 0 of the shift register is applied to the counter as follows:
|
||||
//if bit 0 is clear and the delta-counter is greater than 1, the counter is decremented by 2;
|
||||
//otherwise, if bit 0 is set and the delta-counter is less than 126, the counter is incremented by 2
|
||||
if (!out_silence)
|
||||
{
|
||||
//apply current sample bit to delta counter
|
||||
|
@ -622,51 +617,63 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
if (out_deltacounter > 1)
|
||||
out_deltacounter -= 2;
|
||||
}
|
||||
//SyncSample();
|
||||
out_shift >>= 1;
|
||||
//apu.nes.LogLine("dmc out sample: {0}", out_deltacounter);
|
||||
}
|
||||
|
||||
//The right shift register is clocked.
|
||||
out_shift >>= 1;
|
||||
|
||||
//The bits-remaining counter is decremented. If it becomes zero, a new cycle is started.
|
||||
if (out_bits_remaining == 0)
|
||||
{
|
||||
//The bits-remaining counter is loaded with 8.
|
||||
out_bits_remaining = 7;
|
||||
if (sample_length > 0)
|
||||
//If the sample buffer is empty then the silence flag is set
|
||||
if (!sample_buffer_filled)
|
||||
{
|
||||
if (sample_buffer_filled)
|
||||
{
|
||||
out_silence = false;
|
||||
out_shift = sample_buffer;
|
||||
sample_buffer_filled = false;
|
||||
//TODO - cpu/apu DMC reads need to be emulated better!
|
||||
}
|
||||
Fetch();
|
||||
out_silence = true;
|
||||
//out_deltacounter = 64; //gonna go out on a limb here and guess this gets reset. could make some things pop, though, if they dont end at 0.
|
||||
}
|
||||
else
|
||||
//otherwise, the silence flag is cleared and the sample buffer is emptied into the shift register.
|
||||
{
|
||||
out_silence = false;
|
||||
out_shift = sample_buffer;
|
||||
sample_buffer_filled = false;
|
||||
}
|
||||
else out_silence = true;
|
||||
}
|
||||
else
|
||||
out_bits_remaining--;
|
||||
else out_bits_remaining--;
|
||||
|
||||
|
||||
//Any time the sample buffer is in an empty state and bytes remaining is not zero, the following occur:
|
||||
if (!sample_buffer_filled && sample_length > 0)
|
||||
Fetch();
|
||||
}
|
||||
|
||||
public void set_lenctr_en(bool en)
|
||||
{
|
||||
if (!en)
|
||||
//disable playback
|
||||
{
|
||||
//If the DMC bit is clear, the DMC bytes remaining will be set to 0
|
||||
sample_length = 0;
|
||||
//and the DMC will silence when it empties.
|
||||
// (what does this mean? does out_deltacounter get reset to 0? maybe just that the out_silence flag gets set, but this is natural)
|
||||
}
|
||||
else
|
||||
{
|
||||
//only start playback if sample length is 0 (playback is stopped
|
||||
//only start playback if playback is stopped
|
||||
if (sample_length == 0)
|
||||
{
|
||||
sample_address = user_address;
|
||||
sample_length = user_length;
|
||||
out_deltacounter = 64;
|
||||
if (out_silence) // if the DMC buffer was empty...
|
||||
if (out_silence)
|
||||
{
|
||||
timer = 0; // reset frequency counter so next Run() will cause output
|
||||
out_bits_remaining = 0; // reset buffercount so next Run() will cause fetch
|
||||
timer = 0;
|
||||
out_bits_remaining = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//irq is acknowledged or sure to be clear, in either case
|
||||
apu.dmc_irq = false;
|
||||
apu.SyncIRQ();
|
||||
|
@ -691,9 +698,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
break;
|
||||
case 1:
|
||||
out_deltacounter = val & 0x7F;
|
||||
out_silence = false;
|
||||
//apu.nes.LogLine("~~ out_deltacounter set to {0}", out_deltacounter);
|
||||
SyncSample();
|
||||
break;
|
||||
case 2:
|
||||
user_address = 0xC000 | (val << 6);
|
||||
|
@ -706,6 +711,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
public void Fetch()
|
||||
{
|
||||
//TODO - cpu/apu DMC reads need to be emulated better!
|
||||
sample_buffer = apu.nes.ReadMemory((ushort)sample_address);
|
||||
sample_buffer_filled = true;
|
||||
sample_address = (ushort)(sample_address + 1);
|
||||
|
@ -719,6 +725,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
}
|
||||
else if (irq_enabled) apu.dmc_irq = true;
|
||||
}
|
||||
//Console.WriteLine("fetching dmc byte: {0:X2}", sample_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -944,10 +951,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
}
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
metaspu.buffer.clear();
|
||||
}
|
||||
//public void DiscardSamples()
|
||||
//{
|
||||
// metaspu.buffer.clear();
|
||||
//}
|
||||
|
||||
int toggle = 0;
|
||||
public void RunOne()
|
||||
|
@ -958,33 +965,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
noise.Run();
|
||||
dmc.Run();
|
||||
|
||||
int s_pulse0 = pulse[0].sample;
|
||||
int s_pulse1 = pulse[1].sample;
|
||||
int s_tri = triangle.sample;
|
||||
int s_noise = noise.sample;
|
||||
int s_dmc = dmc.sample;
|
||||
//int s_ext = 0; //gamepak
|
||||
|
||||
if (!EnableSquare1) s_pulse0 = 0;
|
||||
if (!EnableSquare2) s_pulse1 = 0;
|
||||
if (!EnableTriangle) s_tri = 0;
|
||||
if (!EnableNoise) s_noise = 0;
|
||||
if (!EnableDMC) s_dmc = 0;
|
||||
|
||||
//s_pulse0 = 0;
|
||||
//s_pulse1 = 0;
|
||||
//s_tri = 0;
|
||||
//s_noise = 0;
|
||||
//s_dmc = 0;
|
||||
|
||||
const float NOISEADJUST = 0.5f;
|
||||
float pulse_out = 0.00752f * (s_pulse0 + s_pulse1);
|
||||
float tnd_out = 0.00851f * s_tri + 0.00494f * NOISEADJUST * s_noise + 0.00335f * s_dmc;
|
||||
float output = pulse_out + tnd_out;
|
||||
|
||||
int mix = (int)(50000 * output);
|
||||
|
||||
EmitSample(mix);
|
||||
EmitSample();
|
||||
|
||||
//this (and the similar line below) is a crude hack
|
||||
//we should be generating logic to suppress the $4015 clear when the assert signal is set instead
|
||||
|
@ -1022,81 +1003,60 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
//we want the changes to affect it on the *next* cycle.
|
||||
}
|
||||
|
||||
double accumulate;
|
||||
double timer;
|
||||
Queue<int> squeue = new Queue<int>();
|
||||
int last_hwsamp;
|
||||
int panic_sample, panic_count;
|
||||
void EmitSample(int samp)
|
||||
int loopy = 0;
|
||||
public const int DECIMATIONFACTOR = 20;
|
||||
const int QUEUESIZE = 1789773 / DECIMATIONFACTOR; //1 second, should be enough
|
||||
public QuickQueue<short> squeue = new QuickQueue<short>(QUEUESIZE);
|
||||
void EmitSample()
|
||||
{
|
||||
//kill the annoying hum that is a consequence of the shitty code below
|
||||
if (samp == panic_sample)
|
||||
panic_count++;
|
||||
else panic_count = 0;
|
||||
if (panic_count > 178977)
|
||||
samp = 0;
|
||||
else
|
||||
panic_sample = samp;
|
||||
|
||||
int this_samp = samp;
|
||||
const double kMixRate = 44100.0 / 1789772.0;
|
||||
const double kInvMixRate = (1 / kMixRate);
|
||||
timer += kMixRate;
|
||||
accumulate += samp;
|
||||
if (timer <= 1)
|
||||
return;
|
||||
|
||||
accumulate -= samp;
|
||||
timer -= 1;
|
||||
double ratio = (timer / kMixRate);
|
||||
double fractional = (this_samp - last_hwsamp) * ratio;
|
||||
double factional_remainder = (this_samp - last_hwsamp) * (1 - ratio);
|
||||
accumulate += fractional;
|
||||
|
||||
//accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels
|
||||
int outsamp = (int)(accumulate / kInvMixRate);
|
||||
if (CFG_USE_METASPU)
|
||||
metaspu.buffer.enqueue_sample((short)outsamp, (short)outsamp);
|
||||
else squeue.Enqueue(outsamp);
|
||||
accumulate = factional_remainder;
|
||||
|
||||
last_hwsamp = this_samp;
|
||||
}
|
||||
|
||||
MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
|
||||
public int MaxVolume { get; set; } // not supported
|
||||
|
||||
void ISoundProvider.GetSamples(short[] samples)
|
||||
{
|
||||
if (CFG_USE_METASPU)
|
||||
//here we throw out 19/20 of the samples, for an easy speedup... blech.
|
||||
loopy++;
|
||||
if (loopy == DECIMATIONFACTOR)
|
||||
{
|
||||
metaspu.GetSamples(samples);
|
||||
//foreach(short sample in samples) bw.Write((short)sample);
|
||||
}
|
||||
else
|
||||
MyGetSamples(samples);
|
||||
loopy = 0;
|
||||
|
||||
//mix in the cart's extra sound circuit
|
||||
nes.board.ApplyCustomAudio(samples);
|
||||
}
|
||||
int s_pulse0 = pulse[0].sample;
|
||||
int s_pulse1 = pulse[1].sample;
|
||||
int s_tri = triangle.sample;
|
||||
int s_noise = noise.sample;
|
||||
int s_dmc = dmc.sample;
|
||||
//int s_ext = 0; //gamepak
|
||||
|
||||
//static BinaryWriter bw = new BinaryWriter(new FileStream("d:\\out.raw",FileMode.Create,FileAccess.Write,FileShare.Read));
|
||||
void MyGetSamples(short[] samples)
|
||||
{
|
||||
//Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2);
|
||||
if (!EnableSquare1) s_pulse0 = 0;
|
||||
if (!EnableSquare2) s_pulse1 = 0;
|
||||
if (!EnableTriangle) s_tri = 0;
|
||||
if (!EnableNoise) s_noise = 0;
|
||||
if (!EnableDMC) s_dmc = 0;
|
||||
|
||||
for (int i = 0; i < (samples.Length >> 1); i++)
|
||||
{
|
||||
int samp = 0;
|
||||
if (squeue.Count != 0)
|
||||
samp = squeue.Dequeue();
|
||||
const float NOISEADJUST = 0.5f;
|
||||
|
||||
samples[(i << 1) + 0] = (short)(samp);
|
||||
samples[(i << 1) + 1] = (short)(samp);
|
||||
//bw.Write((short)samp);
|
||||
//linear approximation
|
||||
float pulse_out = 0.00752f * (s_pulse0 + s_pulse1);
|
||||
float tnd_out = 0.00851f * s_tri + 0.00494f * NOISEADJUST * s_noise + 0.00335f * s_dmc;
|
||||
float output = pulse_out + tnd_out;
|
||||
//this needs to leave enough headroom for straying DC bias due to the DMC unit getting stuck outputs. smb3 is bad about that.
|
||||
int mix = (int)(50000 * output);
|
||||
|
||||
//more properly correct
|
||||
//float pulse_out, tnd_out;
|
||||
//if (s_pulse0 == 0 && s_pulse1 == 0)
|
||||
// pulse_out = 0;
|
||||
//else pulse_out = 95.88f / ((8128.0f / (s_pulse0 + s_pulse1)) + 100.0f);
|
||||
//if (s_tri == 0 && s_noise == 0 && s_dmc == 0)
|
||||
// tnd_out = 0;
|
||||
//else tnd_out = 159.79f / (1 / ((s_tri / 8227.0f) + (s_noise / 12241.0f * NOISEADJUST) + (s_dmc / 22638.0f)) + 100);
|
||||
//float output = pulse_out + tnd_out;
|
||||
//output = output * 2 - 1;
|
||||
//this needs to leave enough headroom for straying DC bias due to the DMC unit getting stuck outputs. smb3 is bad about that.
|
||||
//int mix = (int)(20000 * output);
|
||||
|
||||
|
||||
|
||||
if (squeue.Count < QUEUESIZE)
|
||||
squeue.Enqueue((short)mix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} //class APU
|
||||
|
||||
|
||||
|
|
|
@ -128,7 +128,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
int len = samples.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
samples[i] = (short)((samples[i] >> 1) + (fmsamples[i] >> 1));
|
||||
short fmsamp = fmsamples[i];
|
||||
samples[i] = (short)(samples[i] + fmsamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,65 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
public void SetNoise(bool enabled) { apu.EnableNoise = enabled; }
|
||||
public void SetDMC(bool enabled) { apu.EnableDMC = enabled; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (magicSoundProvider != null) magicSoundProvider.Dispose();
|
||||
magicSoundProvider = null;
|
||||
}
|
||||
|
||||
class MagicSoundProvider : ISoundProvider, IDisposable
|
||||
{
|
||||
Sound.Utilities.SpeexResampler resampler;
|
||||
ISoundProvider output;
|
||||
NES nes;
|
||||
|
||||
public MagicSoundProvider(NES nes)
|
||||
{
|
||||
this.nes = nes;
|
||||
var actualMetaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
|
||||
//1.789773mhz NTSC
|
||||
resampler = new Sound.Utilities.SpeexResampler(2, 1789773, 44100*APU.DECIMATIONFACTOR, 1789773, 44100, actualMetaspu.buffer.enqueue_samples);
|
||||
output = new Sound.Utilities.DCFilter(actualMetaspu);
|
||||
}
|
||||
|
||||
Random r = new Random();
|
||||
public void GetSamples(short[] samples)
|
||||
{
|
||||
var monosampbuf = nes.apu.squeue.ToArray(2);
|
||||
nes.apu.squeue.Clear();
|
||||
if (monosampbuf.Length > 0)
|
||||
{
|
||||
var stereosampbuf = new short[monosampbuf.Length * 2];
|
||||
for (int i = 0; i < monosampbuf.Length; i++)
|
||||
{
|
||||
stereosampbuf[i * 2 + 0] = monosampbuf[i];
|
||||
stereosampbuf[i * 2 + 1] = monosampbuf[i];
|
||||
}
|
||||
resampler.EnqueueSamples(stereosampbuf, monosampbuf.Length);
|
||||
resampler.Flush();
|
||||
output.GetSamples(samples);
|
||||
}
|
||||
|
||||
//mix in the cart's extra sound circuit
|
||||
nes.board.ApplyCustomAudio(samples);
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
output.DiscardSamples();
|
||||
}
|
||||
|
||||
public int MaxVolume { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
resampler.Dispose();
|
||||
resampler = null;
|
||||
}
|
||||
}
|
||||
MagicSoundProvider magicSoundProvider;
|
||||
|
||||
|
||||
public void HardReset()
|
||||
{
|
||||
cpu = new MOS6502X();
|
||||
|
@ -48,13 +107,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
cpu.WriteMemory = WriteMemory;
|
||||
cpu.BCD_Enabled = false;
|
||||
ppu = new PPU(this);
|
||||
apu = new APU(this);
|
||||
ram = new byte[0x800];
|
||||
CIRAM = new byte[0x800];
|
||||
ports = new IPortDevice[2];
|
||||
ports[0] = new JoypadPortDevice(this,0);
|
||||
ports[1] = new JoypadPortDevice(this,1);
|
||||
|
||||
apu = new APU(this);
|
||||
if (magicSoundProvider != null) magicSoundProvider.Dispose();
|
||||
magicSoundProvider = new MagicSoundProvider(this);
|
||||
|
||||
//fceux uses this technique, which presumably tricks some games into thinking the memory is randomized
|
||||
for (int i = 0; i < 0x800; i++)
|
||||
{
|
||||
|
|
|
@ -203,7 +203,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
MyVideoProvider videoProvider;
|
||||
public IVideoProvider VideoProvider { get { return videoProvider; } }
|
||||
public ISoundProvider SoundProvider { get { return apu; } }
|
||||
public ISoundProvider SoundProvider { get { return magicSoundProvider; } }
|
||||
|
||||
public static readonly ControllerDefinition NESController =
|
||||
new ControllerDefinition
|
||||
|
@ -641,6 +641,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
throw new InvalidOperationException("the current NES mapper didnt call base.SyncState");
|
||||
ppu.SyncState(ser);
|
||||
apu.SyncState(ser);
|
||||
|
||||
if (version >= 2)
|
||||
ser.Sync("DB", ref DB);
|
||||
|
||||
|
@ -660,8 +661,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
bw.Flush();
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,20 @@ namespace BizHawk
|
|||
size++;
|
||||
}
|
||||
|
||||
public T[] ToArray(int elemSize)
|
||||
{
|
||||
T[] ret = new T[size];
|
||||
int todo;
|
||||
if (tail > head) todo = tail - head;
|
||||
else todo = buffer.Length - head;
|
||||
Buffer.BlockCopy(buffer, head, ret, 0, elemSize * todo);
|
||||
int todo2;
|
||||
if (tail < head) todo2 = tail;
|
||||
else todo2 = 0;
|
||||
if (todo2 != 0) Buffer.BlockCopy(buffer, 0, ret, todo, elemSize * todo2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
if (size == 0)
|
||||
|
|
Loading…
Reference in New Issue