Custom SoundProviderMixer implementation
This commit is contained in:
parent
85d38a3379
commit
f82b1b8336
|
@ -256,6 +256,7 @@
|
||||||
<Compile Include="Computers\Commodore64\MOS\Vic.VideoProvider.cs" />
|
<Compile Include="Computers\Commodore64\MOS\Vic.VideoProvider.cs" />
|
||||||
<Compile Include="Computers\Commodore64\SaveState.cs" />
|
<Compile Include="Computers\Commodore64\SaveState.cs" />
|
||||||
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
|
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Hardware\AYSound.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Buzzer.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Hardware\Buzzer.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Hardware\DefaultTapeProvider.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Hardware\DefaultTapeProvider.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Interfaces\IKeyboard.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Hardware\Interfaces\IKeyboard.cs" />
|
||||||
|
@ -270,6 +271,7 @@
|
||||||
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeBlockSetPlayer.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeBlockSetPlayer.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeDataBlockPlayer.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeDataBlockPlayer.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeFilePlayer.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeFilePlayer.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\SoundProviderMixer.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Machine\MachineType.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Machine\MachineType.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Input.cs" />
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Input.cs" />
|
||||||
|
@ -307,6 +309,7 @@
|
||||||
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IInputPollable.cs" />
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IInputPollable.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IMemoryDomains.cs" />
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IMemoryDomains.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.ISettable.cs" />
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.ISettable.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.ISoundProvider.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IStatable.cs" />
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IStatable.cs" />
|
||||||
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.Util.cs" />
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.Util.cs" />
|
||||||
<Compile Include="Consoles\Atari\2600\Atari2600.cs" />
|
<Compile Include="Consoles\Atari\2600\Atari2600.cs" />
|
||||||
|
|
|
@ -0,0 +1,407 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Common.NumberExtensions;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Components;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public class AYSound : ISoundProvider
|
||||||
|
{
|
||||||
|
private readonly BlipBuffer _blip = new BlipBuffer(4096);
|
||||||
|
private short[] _sampleBuffer = new short[0];
|
||||||
|
|
||||||
|
public AYSound()
|
||||||
|
{
|
||||||
|
_blip.SetRates(894866 / 4.0, 44100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort[] Register = new ushort[16];
|
||||||
|
|
||||||
|
public int total_clock; // TODO: what is this used for?
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
clock_A = clock_B = clock_C = 0x1000;
|
||||||
|
noise_clock = 0x20;
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
Register[i] = 0x0000;
|
||||||
|
}
|
||||||
|
sync_psg_state();
|
||||||
|
DiscardSamples();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DiscardSamples()
|
||||||
|
{
|
||||||
|
_blip.Clear();
|
||||||
|
_sampleClock = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesAsync(short[] samples)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Async is not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
_blip.EndFrame((uint)_sampleClock);
|
||||||
|
_sampleClock = 0;
|
||||||
|
|
||||||
|
nsamp = _blip.SamplesAvailable();
|
||||||
|
int targetLength = nsamp * 2;
|
||||||
|
if (_sampleBuffer.Length != targetLength)
|
||||||
|
{
|
||||||
|
_sampleBuffer = new short[targetLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
_blip.ReadSamplesLeft(_sampleBuffer, nsamp);
|
||||||
|
for (int i = 0; i < _sampleBuffer.Length; i += 2)
|
||||||
|
{
|
||||||
|
_sampleBuffer[i + 1] = _sampleBuffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
samples = _sampleBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamples(short[] samples)
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly int[] VolumeTable =
|
||||||
|
{
|
||||||
|
0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA,
|
||||||
|
0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA
|
||||||
|
};
|
||||||
|
|
||||||
|
private int _sampleClock;
|
||||||
|
private int _latchedSample;
|
||||||
|
|
||||||
|
private int TotalExecutedCycles;
|
||||||
|
private int PendingCycles;
|
||||||
|
private int psg_clock;
|
||||||
|
private int sq_per_A, sq_per_B, sq_per_C;
|
||||||
|
private int clock_A, clock_B, clock_C;
|
||||||
|
private int vol_A, vol_B, vol_C;
|
||||||
|
private bool A_on, B_on, C_on;
|
||||||
|
private bool A_up, B_up, C_up;
|
||||||
|
private bool A_noise, B_noise, C_noise;
|
||||||
|
|
||||||
|
private int env_per;
|
||||||
|
private int env_clock;
|
||||||
|
private int env_shape;
|
||||||
|
private int env_E;
|
||||||
|
private int E_up_down;
|
||||||
|
private int env_vol_A, env_vol_B, env_vol_C;
|
||||||
|
|
||||||
|
private int noise_clock;
|
||||||
|
private int noise_per;
|
||||||
|
private int noise = 0x1;
|
||||||
|
|
||||||
|
public Func<ushort, bool, ushort> ReadMemory;
|
||||||
|
public Func<ushort, ushort, bool, bool> WriteMemory;
|
||||||
|
|
||||||
|
public void SyncState(Serializer ser)
|
||||||
|
{
|
||||||
|
ser.BeginSection("PSG");
|
||||||
|
|
||||||
|
ser.Sync("Register", ref Register, false);
|
||||||
|
ser.Sync("Toal_executed_cycles", ref TotalExecutedCycles);
|
||||||
|
ser.Sync("Pending_Cycles", ref PendingCycles);
|
||||||
|
|
||||||
|
ser.Sync("psg_clock", ref psg_clock);
|
||||||
|
ser.Sync("clock_A", ref clock_A);
|
||||||
|
ser.Sync("clock_B", ref clock_B);
|
||||||
|
ser.Sync("clock_C", ref clock_C);
|
||||||
|
ser.Sync("noise_clock", ref noise_clock);
|
||||||
|
ser.Sync("env_clock", ref env_clock);
|
||||||
|
ser.Sync("A_up", ref A_up);
|
||||||
|
ser.Sync("B_up", ref B_up);
|
||||||
|
ser.Sync("C_up", ref C_up);
|
||||||
|
ser.Sync("noise", ref noise);
|
||||||
|
ser.Sync("env_E", ref env_E);
|
||||||
|
ser.Sync("E_up_down", ref E_up_down);
|
||||||
|
|
||||||
|
sync_psg_state();
|
||||||
|
|
||||||
|
ser.EndSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort? ReadPSG(ushort addr, bool peek)
|
||||||
|
{
|
||||||
|
if (addr >= 0x01F0 && addr <= 0x01FF)
|
||||||
|
{
|
||||||
|
return (ushort)(Register[addr - 0x01F0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sync_psg_state()
|
||||||
|
{
|
||||||
|
sq_per_A = (Register[0] & 0xFF) | (((Register[4] & 0xF) << 8));
|
||||||
|
if (sq_per_A == 0)
|
||||||
|
{
|
||||||
|
sq_per_A = 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
sq_per_B = (Register[1] & 0xFF) | (((Register[5] & 0xF) << 8));
|
||||||
|
if (sq_per_B == 0)
|
||||||
|
{
|
||||||
|
sq_per_B = 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
sq_per_C = (Register[2] & 0xFF) | (((Register[6] & 0xF) << 8));
|
||||||
|
if (sq_per_C == 0)
|
||||||
|
{
|
||||||
|
sq_per_C = 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
env_per = (Register[3] & 0xFF) | (((Register[7] & 0xFF) << 8));
|
||||||
|
if (env_per == 0)
|
||||||
|
{
|
||||||
|
env_per = 0x10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
env_per *= 2;
|
||||||
|
|
||||||
|
A_on = Register[8].Bit(0);
|
||||||
|
B_on = Register[8].Bit(1);
|
||||||
|
C_on = Register[8].Bit(2);
|
||||||
|
A_noise = Register[8].Bit(3);
|
||||||
|
B_noise = Register[8].Bit(4);
|
||||||
|
C_noise = Register[8].Bit(5);
|
||||||
|
|
||||||
|
noise_per = Register[9] & 0x1F;
|
||||||
|
if (noise_per == 0)
|
||||||
|
{
|
||||||
|
noise_per = 0x20;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shape_select = Register[10] & 0xF;
|
||||||
|
|
||||||
|
if (shape_select < 4)
|
||||||
|
env_shape = 0;
|
||||||
|
else if (shape_select < 8)
|
||||||
|
env_shape = 1;
|
||||||
|
else
|
||||||
|
env_shape = 2 + (shape_select - 8);
|
||||||
|
|
||||||
|
vol_A = Register[11] & 0xF;
|
||||||
|
env_vol_A = (Register[11] >> 4) & 0x3;
|
||||||
|
|
||||||
|
vol_B = Register[12] & 0xF;
|
||||||
|
env_vol_B = (Register[12] >> 4) & 0x3;
|
||||||
|
|
||||||
|
vol_C = Register[13] & 0xF;
|
||||||
|
env_vol_C = (Register[13] >> 4) & 0x3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WritePSG(ushort addr, ushort value, bool poke)
|
||||||
|
{
|
||||||
|
if (addr >= 0x01F0 && addr <= 0x01FF)
|
||||||
|
{
|
||||||
|
var reg = addr - 0x01F0;
|
||||||
|
|
||||||
|
value &= 0xFF;
|
||||||
|
|
||||||
|
if (reg == 4 || reg == 5 || reg == 6 || reg == 10)
|
||||||
|
value &= 0xF;
|
||||||
|
|
||||||
|
if (reg == 9)
|
||||||
|
value &= 0x1F;
|
||||||
|
|
||||||
|
if (reg == 11 || reg == 12 || reg == 13)
|
||||||
|
value &= 0x3F;
|
||||||
|
|
||||||
|
Register[addr - 0x01F0] = value;
|
||||||
|
|
||||||
|
sync_psg_state();
|
||||||
|
|
||||||
|
if (reg == 10)
|
||||||
|
{
|
||||||
|
env_clock = env_per;
|
||||||
|
|
||||||
|
if (env_shape == 0 || env_shape == 2 || env_shape == 3 || env_shape == 4 || env_shape == 5)
|
||||||
|
{
|
||||||
|
env_E = 15;
|
||||||
|
E_up_down = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env_E = 0;
|
||||||
|
E_up_down = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generate_sound(int cycles_to_do)
|
||||||
|
{
|
||||||
|
// there are 4 cpu cycles for every psg cycle
|
||||||
|
bool sound_out_A;
|
||||||
|
bool sound_out_B;
|
||||||
|
bool sound_out_C;
|
||||||
|
|
||||||
|
for (int i = 0; i < cycles_to_do; i++)
|
||||||
|
{
|
||||||
|
psg_clock++;
|
||||||
|
|
||||||
|
if (psg_clock == 4)
|
||||||
|
{
|
||||||
|
psg_clock = 0;
|
||||||
|
|
||||||
|
total_clock++;
|
||||||
|
|
||||||
|
clock_A--;
|
||||||
|
clock_B--;
|
||||||
|
clock_C--;
|
||||||
|
|
||||||
|
noise_clock--;
|
||||||
|
env_clock--;
|
||||||
|
|
||||||
|
// clock noise
|
||||||
|
if (noise_clock == 0)
|
||||||
|
{
|
||||||
|
noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0);
|
||||||
|
noise_clock = noise_per;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env_clock == 0)
|
||||||
|
{
|
||||||
|
env_clock = env_per;
|
||||||
|
|
||||||
|
env_E += E_up_down;
|
||||||
|
|
||||||
|
if (env_E == 16 || env_E == -1)
|
||||||
|
{
|
||||||
|
|
||||||
|
// we just completed a period of the envelope, determine what to do now based on the envelope shape
|
||||||
|
if (env_shape == 0 || env_shape == 1 || env_shape == 3 || env_shape == 9)
|
||||||
|
{
|
||||||
|
E_up_down = 0;
|
||||||
|
env_E = 0;
|
||||||
|
}
|
||||||
|
else if (env_shape == 5 || env_shape == 7)
|
||||||
|
{
|
||||||
|
E_up_down = 0;
|
||||||
|
env_E = 15;
|
||||||
|
}
|
||||||
|
else if (env_shape == 4 || env_shape == 8)
|
||||||
|
{
|
||||||
|
if (env_E == 16)
|
||||||
|
{
|
||||||
|
env_E = 15;
|
||||||
|
E_up_down = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env_E = 0;
|
||||||
|
E_up_down = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (env_shape == 2)
|
||||||
|
{
|
||||||
|
env_E = 15;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env_E = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock_A == 0)
|
||||||
|
{
|
||||||
|
A_up = !A_up;
|
||||||
|
clock_A = sq_per_A;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock_B == 0)
|
||||||
|
{
|
||||||
|
B_up = !B_up;
|
||||||
|
clock_B = sq_per_B;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock_C == 0)
|
||||||
|
{
|
||||||
|
C_up = !C_up;
|
||||||
|
clock_C = sq_per_C;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sound_out_A = (noise.Bit(0) | A_noise) & (A_on | A_up);
|
||||||
|
sound_out_B = (noise.Bit(0) | B_noise) & (B_on | B_up);
|
||||||
|
sound_out_C = (noise.Bit(0) | C_noise) & (C_on | C_up);
|
||||||
|
|
||||||
|
// now calculate the volume of each channel and add them together
|
||||||
|
int v;
|
||||||
|
|
||||||
|
if (env_vol_A == 0)
|
||||||
|
{
|
||||||
|
v = (short)(sound_out_A ? VolumeTable[vol_A] : 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int shift_A = 3 - env_vol_A;
|
||||||
|
if (shift_A < 0)
|
||||||
|
shift_A = 0;
|
||||||
|
v = (short)(sound_out_A ? (VolumeTable[env_E] >> shift_A) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env_vol_B == 0)
|
||||||
|
{
|
||||||
|
v += (short)(sound_out_B ? VolumeTable[vol_B] : 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int shift_B = 3 - env_vol_B;
|
||||||
|
if (shift_B < 0)
|
||||||
|
shift_B = 0;
|
||||||
|
v += (short)(sound_out_B ? (VolumeTable[env_E] >> shift_B) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env_vol_C == 0)
|
||||||
|
{
|
||||||
|
v += (short)(sound_out_C ? VolumeTable[vol_C] : 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int shift_C = 3 - env_vol_C;
|
||||||
|
if (shift_C < 0)
|
||||||
|
shift_C = 0;
|
||||||
|
v += (short)(sound_out_C ? (VolumeTable[env_E] >> shift_C) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v != _latchedSample)
|
||||||
|
{
|
||||||
|
_blip.AddDelta((uint)_sampleClock, v - _latchedSample);
|
||||||
|
_latchedSample = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sampleClock++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Buzzer BuzzerDevice { get; set; }
|
public Buzzer BuzzerDevice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Device representing the AY-3-8912 chip found in the 128k and up spectrums
|
||||||
|
/// </summary>
|
||||||
|
public AYSound AYDevice { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The spectrum keyboard
|
/// The spectrum keyboard
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -231,9 +236,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
BuzzerDevice.SyncState(ser);
|
BuzzerDevice.SyncState(ser);
|
||||||
TapeDevice.SyncState(ser);
|
TapeDevice.SyncState(ser);
|
||||||
|
|
||||||
|
if (AYDevice != null)
|
||||||
|
AYDevice.SyncState(ser);
|
||||||
|
|
||||||
ser.EndSection();
|
ser.EndSection();
|
||||||
|
|
||||||
ReInitMemory();
|
//ReInitMemory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
BuzzerDevice = new Buzzer(this);
|
BuzzerDevice = new Buzzer(this);
|
||||||
BuzzerDevice.Init(44100, UlaFrameCycleCount);
|
BuzzerDevice.Init(44100, UlaFrameCycleCount);
|
||||||
|
|
||||||
|
AYDevice = new AYSound();
|
||||||
|
|
||||||
KeyboardDevice = new Keyboard48(this);
|
KeyboardDevice = new Keyboard48(this);
|
||||||
KempstonDevice = new KempstonJoystick(this);
|
KempstonDevice = new KempstonJoystick(this);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// My attempt at mixing multiple ISoundProvider sources together and outputting another ISoundProvider
|
||||||
|
/// Currently only supports SyncSoundMode.Sync
|
||||||
|
/// Attached ISoundProvider sources must already be stereo 44.1khz
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SoundProviderMixer : ISoundProvider
|
||||||
|
{
|
||||||
|
private class Provider
|
||||||
|
{
|
||||||
|
public ISoundProvider SoundProvider { get; set; }
|
||||||
|
public int MaxVolume { get; set; }
|
||||||
|
public short[] Buffer { get; set; }
|
||||||
|
public int NSamp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Provider> SoundProviders;
|
||||||
|
|
||||||
|
private short[] _buffer;
|
||||||
|
private int _nSamp;
|
||||||
|
|
||||||
|
public SoundProviderMixer(params ISoundProvider[] soundProviders)
|
||||||
|
{
|
||||||
|
SoundProviders = new List<Provider>();
|
||||||
|
|
||||||
|
foreach (var s in soundProviders)
|
||||||
|
{
|
||||||
|
SoundProviders.Add(new Provider
|
||||||
|
{
|
||||||
|
SoundProvider = s,
|
||||||
|
MaxVolume = short.MaxValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EqualizeVolumes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSource(ISoundProvider source)
|
||||||
|
{
|
||||||
|
SoundProviders.Add(new Provider
|
||||||
|
{
|
||||||
|
SoundProvider = source,
|
||||||
|
MaxVolume = short.MaxValue
|
||||||
|
});
|
||||||
|
|
||||||
|
EqualizeVolumes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableSource(ISoundProvider source)
|
||||||
|
{
|
||||||
|
var sp = SoundProviders.Where(a => a.SoundProvider == source);
|
||||||
|
if (sp.Count() == 1)
|
||||||
|
SoundProviders.Remove(sp.First());
|
||||||
|
else if (sp.Count() > 1)
|
||||||
|
foreach (var s in sp)
|
||||||
|
SoundProviders.Remove(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EqualizeVolumes()
|
||||||
|
{
|
||||||
|
if (SoundProviders.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int eachVolume = short.MaxValue / SoundProviders.Count;
|
||||||
|
foreach (var source in SoundProviders)
|
||||||
|
{
|
||||||
|
source.MaxVolume = eachVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ISoundProvider
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
foreach (var soundSource in SoundProviders)
|
||||||
|
{
|
||||||
|
soundSource.SoundProvider.DiscardSamples();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
samples = null;
|
||||||
|
nsamp = 0;
|
||||||
|
|
||||||
|
// get samples from all the providers
|
||||||
|
foreach (var sp in SoundProviders)
|
||||||
|
{
|
||||||
|
int sampCount;
|
||||||
|
short[] samp;
|
||||||
|
sp.SoundProvider.GetSamplesSync(out samp, out sampCount);
|
||||||
|
sp.NSamp = sampCount;
|
||||||
|
sp.Buffer = samp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// are all the sample lengths the same?
|
||||||
|
var firstEntry = SoundProviders.First();
|
||||||
|
bool sameCount = SoundProviders.All(s => s.NSamp == firstEntry.NSamp);
|
||||||
|
|
||||||
|
if (!sameCount)
|
||||||
|
{
|
||||||
|
int divisor = 1;
|
||||||
|
int highestCount = 0;
|
||||||
|
|
||||||
|
// get the lowest divisor of all the soundprovider nsamps
|
||||||
|
for (int d = 2; d < 999; d++)
|
||||||
|
{
|
||||||
|
bool divFound = false;
|
||||||
|
foreach (var sp in SoundProviders)
|
||||||
|
{
|
||||||
|
if (sp.NSamp > highestCount)
|
||||||
|
highestCount = sp.NSamp;
|
||||||
|
|
||||||
|
if (sp.NSamp % d == 0)
|
||||||
|
divFound = true;
|
||||||
|
else
|
||||||
|
divFound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (divFound)
|
||||||
|
{
|
||||||
|
divisor = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have the largest current number of samples among the providers
|
||||||
|
// along with a common divisor for all of them
|
||||||
|
nsamp = highestCount * divisor;
|
||||||
|
samples = new short[nsamp * 2];
|
||||||
|
|
||||||
|
// take a pass at populating the samples array for each provider
|
||||||
|
foreach (var sp in SoundProviders)
|
||||||
|
{
|
||||||
|
short sectorVal = 0;
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 0; i < sp.Buffer.Length; i++)
|
||||||
|
{
|
||||||
|
if (sp.Buffer[i] > sp.MaxVolume)
|
||||||
|
sectorVal = (short)sp.MaxVolume;
|
||||||
|
else
|
||||||
|
sectorVal = sp.Buffer[i];
|
||||||
|
|
||||||
|
for (int s = 0; s < divisor; s++)
|
||||||
|
{
|
||||||
|
samples[pos++] += sectorVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nsamp = firstEntry.NSamp;
|
||||||
|
samples = new short[nsamp * 2];
|
||||||
|
|
||||||
|
for (int i = 0; i < samples.Length; i++)
|
||||||
|
{
|
||||||
|
short sectorVal = 0;
|
||||||
|
foreach (var sp in SoundProviders)
|
||||||
|
{
|
||||||
|
if (sp.Buffer[i] > sp.MaxVolume)
|
||||||
|
sectorVal += (short)sp.MaxVolume;
|
||||||
|
else
|
||||||
|
sectorVal += sp.Buffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
samples[i] = sectorVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using BizHawk.Emulation.Cores.Components;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum
|
||||||
|
{
|
||||||
|
private FakeSyncSound _fakeSyncSound;
|
||||||
|
private IAsyncSoundProvider ActiveSoundProvider;
|
||||||
|
private SoundProviderMixer SoundMixer;
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
ser.Register<ITraceable>(_tracer);
|
ser.Register<ITraceable>(_tracer);
|
||||||
ser.Register<IDisassemblable>(_cpu);
|
ser.Register<IDisassemblable>(_cpu);
|
||||||
ser.Register<IVideoProvider>(_machine);
|
ser.Register<IVideoProvider>(_machine);
|
||||||
ser.Register<ISoundProvider>(_machine.BuzzerDevice);
|
|
||||||
|
SoundMixer = new SoundProviderMixer(_machine.BuzzerDevice);
|
||||||
|
if (_machine.AYDevice != null)
|
||||||
|
SoundMixer.AddSource(_machine.AYDevice);
|
||||||
|
|
||||||
|
ser.Register<ISoundProvider>(SoundMixer);
|
||||||
|
|
||||||
HardReset();
|
HardReset();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue