diff --git a/src/BizHawk.Emulation.Cores/CPUs/FairchildF8/F3850.Disassembler.cs b/src/BizHawk.Emulation.Cores/CPUs/FairchildF8/F3850.Disassembler.cs index 331930d808..f03b8a709f 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/FairchildF8/F3850.Disassembler.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/FairchildF8/F3850.Disassembler.cs @@ -320,7 +320,7 @@ namespace BizHawk.Emulation.Cores.Components.FairchildF8 set { } } - public string PCRegisterName => "PC"; + public string PCRegisterName => "PC0"; public IEnumerable AvailableCpus { get; } = [ "F3850" ]; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Audio.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Audio.cs deleted file mode 100644 index be37035910..0000000000 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Audio.cs +++ /dev/null @@ -1,104 +0,0 @@ -using BizHawk.Emulation.Common; - -namespace BizHawk.Emulation.Cores.Consoles.ChannelF -{ - /// - /// Audio related functions - /// - 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]; - } - } - } -} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs new file mode 100644 index 0000000000..1c4a2637a1 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs @@ -0,0 +1,234 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.ChannelF +{ + /// + /// Sound related functions + /// + 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(); + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs index 73a462b99b..dd61bb7977 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs @@ -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); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs index de6c30f28d..1c06bf5bae 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs @@ -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; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Video.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Video.cs deleted file mode 100644 index 33bbce1a1f..0000000000 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Video.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace BizHawk.Emulation.Cores.Consoles.ChannelF -{ - /// - /// Video related functions - /// - 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]]; - } - } - } - } -}