ChannelFHawk: Audio subsystem now working correctly, Debugger exception fixed, SyncState updated
This commit is contained in:
parent
7061acbfcf
commit
323288872b
|
@ -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" ];
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue