ChannelFHawk: Audio subsystem now working correctly, Debugger exception fixed, SyncState updated

This commit is contained in:
Asnivor 2024-09-04 09:54:49 +01:00
parent 7061acbfcf
commit 323288872b
6 changed files with 258 additions and 143 deletions

View File

@ -320,7 +320,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8
set { }
}
public string PCRegisterName => "PC";
public string PCRegisterName => "PC0";
public IEnumerable<string> AvailableCpus { get; } = [ "F3850" ];

View File

@ -1,104 +0,0 @@
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
/// <summary>
/// Audio related functions
/// </summary>
public partial class ChannelF : ISoundProvider
{
private readonly double SampleRate = 44100;
private int SamplesPerFrame;
private double Period;
private double CyclesPerSample;
private int tone = 0;
private readonly double[] tone_freqs = { 0, 1000, 500, 120 };
#pragma warning disable CS0414
private double amplitude = 0;
private double decay = 0.998;
private double time = 0;
private double cycles = 0;
private int samplePos = 0;
#pragma warning restore CS0414
private int lastCycle = 0;
private BlipBuffer _blip;
private short[] SampleBuffer;
private void SetupAudio()
{
Period = 1.0 / SampleRate;
SamplesPerFrame = (int)(SampleRate / refreshRate);
CyclesPerSample = ClockPerFrame / (double)SamplesPerFrame;
SampleBuffer = new short[SamplesPerFrame];
_blip = new BlipBuffer(SamplesPerFrame);
_blip.SetRates(ClockPerFrame * refreshRate, SampleRate);
}
private void AudioChange()
{
if (tone == 0)
{
// silence
}
else
{
int SamplesPerWave = (int)(SampleRate / tone_freqs[tone]);
double RadPerSample = (Math.PI * 2) / SamplesPerWave;
double SinVal = 0;
int NumSamples = (int)((FrameClock - (double)lastCycle) / CyclesPerSample);
int startPos = lastCycle;
for (int i = 0; i < NumSamples; i++)
{
SinVal = Math.Sin(RadPerSample * (i * SamplesPerWave));
_blip.AddDelta((uint)startPos, (int) (Math.Floor(SinVal * 127) + 128) * 1024);
startPos += (int)CyclesPerSample;
}
}
}
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
public void SetSyncMode(SyncSoundMode mode)
{
if (mode != SyncSoundMode.Sync)
throw new InvalidOperationException("Only Sync mode is supported.");
}
public void GetSamplesAsync(short[] samples)
{
throw new NotSupportedException("Async is not available");
}
public void DiscardSamples()
{
SampleBuffer = new short[SamplesPerFrame];
samplePos = 0;
lastCycle = 0;
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
AudioChange();
_blip.EndFrame((uint)ClockPerFrame);
nsamp = _blip.SamplesAvailable();
samples = new short[nsamp * 2];
_blip.ReadSamples(samples, nsamp, true);
for (int i = 0; i < nsamp * 2; i += 2)
{
samples[i + 1] = samples[i];
}
}
}
}

View File

@ -0,0 +1,234 @@
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
/// <summary>
/// Sound related functions
/// </summary>
public partial class ChannelF : ISoundProvider
{
private int tone = 0;
private short[] sampleBuffer;
private int[] toneBuffer;
private readonly double sampleRate = 44100;
private readonly double decay = 0.998;
private readonly int rampUpTime = 10;
private int samplesPerFrame;
private double cyclesPerSample;
private double amplitude = 0;
private int rampCounter = 0;
private void SetupAudio()
{
samplesPerFrame = (int)(sampleRate / refreshRate);
cyclesPerSample = ClockPerFrame / (double)samplesPerFrame;
sampleBuffer = new short[samplesPerFrame];
toneBuffer = new int[samplesPerFrame];
}
private void AudioChange()
{
var currSample = (int)(FrameClock / cyclesPerSample);
while (currSample < samplesPerFrame)
{
toneBuffer[currSample++] = tone;
}
}
private int GetWaveSample(double time, int tone)
{
int t = 0;
switch (tone)
{
case 0:
// silence
t = 0;
break;
case 1:
// 1000Hz tone
t = GetSquareWaveSample(time, 1000);
break;
case 2:
// 500 Hz tone
t = GetSquareWaveSample(time, 500);
break;
case 3:
// 120 Hz tone
t = GetSquareWaveSample(time, 120);
t += GetSquareWaveSample(time, 240);
break;
}
return t;
}
private int GetSineWaveSample(double time, double freq)
{
double sTime = time / sampleRate;
double sAngle = 2.0 * Math.PI * sTime * freq;
return (int)(Math.Sin(sAngle) * 0x8000);
}
private int GetSquareWaveSample(double time, double freq)
{
double sTime = time / sampleRate;
double period = 1.0 / freq;
double halfPeriod = period / 2.0;
double currentTimeInPeriod = sTime % period;
return currentTimeInPeriod < halfPeriod ? 32766 : -32766;
}
private void ApplyLowPassFilter(short[] samples, double cutoffFrequency)
{
if (samples == null || samples.Length == 0)
return;
double rc = 1.0 / (cutoffFrequency * 2 * Math.PI);
double dt = 1.0 / sampleRate;
double alpha = dt / (rc + dt);
short previousSample = samples[0];
for (int i = 1; i < samples.Length; i++)
{
samples[i] = (short)(previousSample + alpha * (samples[i] - previousSample));
previousSample = samples[i];
}
}
private void ApplyBassBoostFilter(short[] samples, double boostAmount, double cutoffFrequency)
{
if (samples == null || samples.Length == 0)
return;
double A = Math.Pow(10, boostAmount / 40);
double omega = 2 * Math.PI * cutoffFrequency / sampleRate;
double sn = Math.Sin(omega);
double cs = Math.Cos(omega);
double alpha = sn / 2 * Math.Sqrt((A + 1 / A) * (1 / 0.707 - 1) + 2);
double beta = 2 * Math.Sqrt(A) * alpha;
double b0 = A * ((A + 1) - (A - 1) * cs + beta);
double b1 = 2 * A * ((A - 1) - (A + 1) * cs);
double b2 = A * ((A + 1) - (A - 1) * cs - beta);
double a0 = (A + 1) + (A - 1) * cs + beta;
double a1 = -2 * ((A - 1) + (A + 1) * cs);
double a2 = (A + 1) + (A - 1) * cs - beta;
double[] filteredSamples = new double[samples.Length];
filteredSamples[0] = samples[0];
filteredSamples[1] = samples[1];
for (int i = 2; i < samples.Length; i++)
{
filteredSamples[i] = (b0 / a0) * samples[i] + (b1 / a0) * samples[i - 1] + (b2 / a0) * samples[i - 2]
- (a1 / a0) * filteredSamples[i - 1] - (a2 / a0) * filteredSamples[i - 2];
}
for (int i = 0; i < samples.Length; i++)
{
samples[i] = Clamp((short)filteredSamples[i], short.MinValue, short.MaxValue);
}
short Clamp(short value, short min, short max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
}
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
public void SetSyncMode(SyncSoundMode mode)
{
if (mode != SyncSoundMode.Sync)
throw new InvalidOperationException("Only Sync mode is supported.");
}
public void GetSamplesAsync(short[] samples)
{
throw new NotSupportedException("Async is not available");
}
public void DiscardSamples()
{
sampleBuffer = new short[samplesPerFrame];
// pre-populate the tonebuffer with the last active tone from the frame (will be overwritten if and when a tone change happens)
int lastTone = toneBuffer[^1];
for (int i = 0; i < toneBuffer.Length; i++)
toneBuffer[i] = lastTone;
}
int currTone = 0;
int samplePosition = 0;
public void GetSamplesSync(out short[] samples, out int nsamp)
{
// process tone buffer
for (int t = 0; t < toneBuffer.Length; t++)
{
var tValue = toneBuffer[t];
if (currTone != tValue)
{
// tone is changing
if (tValue == 0)
{
// immediate silence
currTone = tValue;
sampleBuffer[t] = 0;
}
else
{
// tone change
amplitude = 1;
samplePosition = 0;
rampCounter = rampUpTime;
currTone = tValue;
if (rampCounter <= 0)
sampleBuffer[t] = (short)((GetWaveSample(samplePosition++, currTone) * amplitude) / 30);
}
}
else if (currTone > 0)
{
// tone is continuing
if (rampCounter <= 0)
{
amplitude *= decay;
samplePosition %= samplesPerFrame;
sampleBuffer[t] = (short)((GetWaveSample(samplePosition++, currTone) * amplitude) / 30);
}
else
{
rampCounter--;
}
}
else
{
// explicit silence
sampleBuffer[t] = 0;
}
}
ApplyBassBoostFilter(sampleBuffer, 4.0, 600);
ApplyLowPassFilter(sampleBuffer, 10000);
nsamp = sampleBuffer.Length;
samples = new short[nsamp * 2];
for (int i = 0, s = 0; i < nsamp; i++, s += 2)
{
samples[s] = (short)(sampleBuffer[i] * 10);
samples[s + 1] = (short)(sampleBuffer[i] * 10);
}
DiscardSamples();
}
}
}

View File

@ -11,6 +11,27 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
ser.Sync(nameof(latch_colour), ref latch_colour);
ser.Sync(nameof(latch_x), ref latch_x);
ser.Sync(nameof(latch_y), ref latch_y);
ser.Sync(nameof(FrameClock), ref FrameClock);
ser.Sync(nameof(_frame), ref _frame);
ser.Sync(nameof(ClockPerFrame), ref ClockPerFrame);
ser.Sync(nameof(_isLag), ref _isLag);
ser.Sync(nameof(_lagCount), ref _lagCount);
ser.Sync(nameof(tone), ref tone);
ser.Sync(nameof(sampleBuffer), ref sampleBuffer, false);
ser.Sync(nameof(toneBuffer), ref toneBuffer, false);
ser.Sync(nameof(samplesPerFrame), ref samplesPerFrame);
ser.Sync(nameof(cyclesPerSample), ref cyclesPerSample);
ser.Sync(nameof(amplitude), ref amplitude);
ser.Sync(nameof(rampCounter), ref rampCounter);
ser.Sync(nameof(currTone), ref currTone);
ser.Sync(nameof(samplePosition), ref samplePosition);
ser.Sync(nameof(StateConsole), ref StateConsole, false);
ser.Sync(nameof(StateRight), ref StateRight, false);
ser.Sync(nameof(StateLeft), ref StateLeft, false);
//ser.Sync(nameof(ControllersEnabled), ref ControllersEnabled);
CPU.SyncState(ser);
Cartridge.SyncState(ser);

View File

@ -101,7 +101,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
// WRT pulse
// pulse clocks the 74195 parallel access shift register which feeds inputs of 2 NAND gates
// writing data to both sets of even and odd VRAM chips (based on the row and column addresses latched into the 7493 ICs
// writing data to both sets of even and odd VRAM chips (based on the row and column addresses latched into the 7493 ICs)
VRAM[((latch_y) * 0x80) + latch_x] = (byte)latch_colour;
}
break;
@ -119,12 +119,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
case 5:
OutputLatch[addr] = value;
latch_y = (value | 0xC0) ^ 0xFF;
var audio = ((value ^ 0xFF) >> 6) & 0x03;
var audio = ((value) >> 6) & 0x03;
if (audio != tone)
{
tone = audio;
time = 0;
amplitude = 1;
AudioChange();
}
break;

View File

@ -1,34 +0,0 @@
namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
/// <summary>
/// Video related functions
/// </summary>
public partial class ChannelF
{
private void BuildFrame1()
{
// rows
int counter = 0;
for (int row = 0; row < 64; row++)
{
// columns 125 and 126 hold the palette index modifier for the entire row
var rIndex = 128 * row;
var c125 = (VRAM[rIndex + 125] & 0x03);
var c126 = (VRAM[rIndex + 126] & 0x03);
var pModifier = (((c126 & 0x02) | c125 >> 1) << 2);
pModifier = ((VRAM[(row << 7) + 125] & 2) >> 1) | (VRAM[(row << 7) + 126] & 3);
pModifier = (pModifier << 2) & 0xc;
// columns
for (int col = 0; col < 128; col++, counter++)
{
int cl = (VRAM[(row << 7) + col]) & 0x3;
frameBuffer[(row << 7) + col] = CMap[pModifier | cl] & 0x7;
//var nCol = pModifier + (VRAM[col | (row << 7)] & 0x03);
//frameBuffer[counter] = FPalette[CMap[nCol]];
}
}
}
}
}