2016-12-06 02:47:36 +00:00
|
|
|
|
using BizHawk.Common.NumberExtensions;
|
|
|
|
|
using System;
|
2016-12-04 14:25:17 +00:00
|
|
|
|
using BizHawk.Common;
|
2016-12-08 01:49:47 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2012-08-08 23:05:55 +00:00
|
|
|
|
|
2013-11-13 03:32:25 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Intellivision
|
2012-08-08 23:05:55 +00:00
|
|
|
|
{
|
2016-12-09 16:24:43 +00:00
|
|
|
|
public sealed class PSG : IAsyncSoundProvider
|
2012-08-08 23:05:55 +00:00
|
|
|
|
{
|
2016-11-11 21:47:55 +00:00
|
|
|
|
public ushort[] Register = new ushort[16];
|
2012-08-08 23:05:55 +00:00
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
public void Reset()
|
|
|
|
|
{
|
|
|
|
|
// reset audio if needed later
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-08 01:49:47 +00:00
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
2016-12-10 04:00:40 +00:00
|
|
|
|
sample_count = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 3733; i++)
|
|
|
|
|
{
|
|
|
|
|
audio_samples[i] = 0;
|
|
|
|
|
}
|
2016-12-08 01:49:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void GetSamples(short[] samples)
|
|
|
|
|
{
|
2016-12-10 04:00:40 +00:00
|
|
|
|
for (int i = 0; i < samples.Length / 2; i++)
|
|
|
|
|
{
|
|
|
|
|
samples[i * 2] = audio_samples[i];
|
|
|
|
|
samples[(i * 2) + 1] = samples[i * 2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-12-08 01:49:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
// There is one audio clock for every 4 cpu clocks, and ~15000 cycles per frame
|
|
|
|
|
public short[] audio_samples = new short[4000];
|
|
|
|
|
|
|
|
|
|
public static int[] volume_table = new int[16] {0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA,
|
|
|
|
|
0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA};
|
|
|
|
|
|
|
|
|
|
public int sample_count;
|
|
|
|
|
|
2012-09-06 04:51:17 +00:00
|
|
|
|
public int TotalExecutedCycles;
|
|
|
|
|
public int PendingCycles;
|
|
|
|
|
|
2016-12-06 02:47:36 +00:00
|
|
|
|
public int psg_clock;
|
|
|
|
|
public int psg_noise;
|
|
|
|
|
|
|
|
|
|
public int sq_per_A, sq_per_B, sq_per_C;
|
|
|
|
|
public int clock_A, clock_B, clock_C;
|
|
|
|
|
public int vol_A, vol_B, vol_C;
|
|
|
|
|
public bool A_on, B_on, C_on;
|
|
|
|
|
public bool A_up, B_up, C_up;
|
|
|
|
|
public bool A_noise, B_noise, C_noise;
|
|
|
|
|
|
|
|
|
|
public int env_per;
|
|
|
|
|
public int env_clock;
|
|
|
|
|
public int env_shape;
|
|
|
|
|
public int env_vol_A, env_vol_B, env_vol_C;
|
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
public int noise_clock;
|
2016-12-06 02:47:36 +00:00
|
|
|
|
public int noise_per;
|
|
|
|
|
public int noise=0x1FFF;
|
|
|
|
|
|
2012-09-06 04:51:17 +00:00
|
|
|
|
public Func<ushort, ushort> ReadMemory;
|
|
|
|
|
public Func<ushort, ushort, bool> WriteMemory;
|
|
|
|
|
|
2016-12-04 14:25:17 +00:00
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
ser.BeginSection("PSG");
|
|
|
|
|
|
2016-12-07 15:56:31 +00:00
|
|
|
|
ser.Sync("Register", ref Register, false);
|
|
|
|
|
ser.Sync("Toal_executed_cycles", ref TotalExecutedCycles);
|
|
|
|
|
ser.Sync("Pending Cycles", ref PendingCycles);
|
2016-12-04 14:25:17 +00:00
|
|
|
|
|
|
|
|
|
ser.EndSection();
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-08 23:05:55 +00:00
|
|
|
|
public ushort? ReadPSG(ushort addr)
|
|
|
|
|
{
|
|
|
|
|
if (addr >= 0x01F0 && addr <= 0x01FF)
|
2012-12-17 04:23:59 +00:00
|
|
|
|
{
|
2016-12-06 02:47:36 +00:00
|
|
|
|
return (ushort)(0xFF00 | Register[addr - 0x01F0]);
|
2012-12-17 04:23:59 +00:00
|
|
|
|
}
|
2012-08-08 23:05:55 +00:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool WritePSG(ushort addr, ushort value)
|
|
|
|
|
{
|
|
|
|
|
if (addr >= 0x01F0 && addr <= 0x01FF)
|
|
|
|
|
{
|
2012-08-10 20:40:34 +00:00
|
|
|
|
Register[addr - 0x01F0] = value;
|
2016-12-06 02:47:36 +00:00
|
|
|
|
var reg = addr - 0x01F0;
|
|
|
|
|
if (reg==0 || reg==4)
|
|
|
|
|
{
|
|
|
|
|
sq_per_A = (Register[0] & 0xFF) | (((Register[4] & 0xF) << 8));
|
|
|
|
|
if (sq_per_A == 0)
|
|
|
|
|
sq_per_A = 0x1000;
|
|
|
|
|
else
|
|
|
|
|
sq_per_A *= 2;
|
|
|
|
|
//clock_A = 0;
|
|
|
|
|
}
|
|
|
|
|
if (reg == 1 || reg == 5)
|
|
|
|
|
{
|
|
|
|
|
sq_per_B = (Register[1] & 0xFF) | (((Register[5] & 0xF) << 8));
|
|
|
|
|
if (sq_per_B == 0)
|
|
|
|
|
sq_per_B = 0x1000;
|
|
|
|
|
else
|
|
|
|
|
sq_per_B *= 2;
|
|
|
|
|
//clock_B = 0;
|
|
|
|
|
}
|
|
|
|
|
if (reg == 2 || reg == 6)
|
|
|
|
|
{
|
|
|
|
|
sq_per_C = (Register[2] & 0xFF) | (((Register[6] & 0xF) << 8));
|
|
|
|
|
if (sq_per_C == 0)
|
|
|
|
|
sq_per_C = 0x1000;
|
|
|
|
|
else
|
|
|
|
|
sq_per_C *= 2;
|
|
|
|
|
//clock_C = 0;
|
|
|
|
|
}
|
|
|
|
|
if (reg == 3 || reg == 7)
|
|
|
|
|
{
|
|
|
|
|
env_per = (Register[3] & 0xFF) | (((Register[7] & 0xFF) << 8));
|
|
|
|
|
if (env_per == 0)
|
|
|
|
|
env_per = 0x20000;
|
|
|
|
|
else
|
|
|
|
|
env_per *= 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reg==8)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reg==9)
|
|
|
|
|
{
|
|
|
|
|
noise_per = Register[9] & 0x1F;
|
2016-12-10 04:00:40 +00:00
|
|
|
|
if (noise_per==0)
|
|
|
|
|
{
|
|
|
|
|
noise_per = 64;
|
|
|
|
|
} else
|
|
|
|
|
{
|
|
|
|
|
noise_per *= 2;
|
|
|
|
|
}
|
2016-12-06 02:47:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reg==10)
|
|
|
|
|
{
|
|
|
|
|
//writing to register 10 resets the envelope
|
|
|
|
|
env_clock = 0;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reg==11)
|
|
|
|
|
{
|
|
|
|
|
vol_A = Register[11] & 0xF;
|
|
|
|
|
env_vol_A = (Register[11] >> 4) & 0x3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reg == 12)
|
|
|
|
|
{
|
|
|
|
|
vol_B = Register[12] & 0xF;
|
|
|
|
|
env_vol_B = (Register[12] >> 4) & 0x3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reg == 13)
|
|
|
|
|
{
|
|
|
|
|
vol_C = Register[13] & 0xF;
|
|
|
|
|
env_vol_C = (Register[13] >> 4) & 0x3;
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-08 23:05:55 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-12-06 02:47:36 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
clock_A++;
|
|
|
|
|
clock_B++;
|
|
|
|
|
clock_C++;
|
|
|
|
|
env_clock++;
|
2016-12-10 04:00:40 +00:00
|
|
|
|
noise_clock++;
|
2016-12-06 02:47:36 +00:00
|
|
|
|
|
|
|
|
|
//clock noise
|
2016-12-10 04:00:40 +00:00
|
|
|
|
if (noise_clock >= noise_per)
|
|
|
|
|
{
|
|
|
|
|
noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0);
|
|
|
|
|
noise_clock = 0;
|
|
|
|
|
}
|
2016-12-06 02:47:36 +00:00
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
if (clock_A >= sq_per_A/2)
|
2016-12-06 02:47:36 +00:00
|
|
|
|
{
|
|
|
|
|
A_up = !A_up;
|
|
|
|
|
clock_A = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
if (clock_B >= sq_per_B/2)
|
2016-12-06 02:47:36 +00:00
|
|
|
|
{
|
|
|
|
|
B_up = !B_up;
|
|
|
|
|
clock_B = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
if (clock_C >= sq_per_C/2)
|
2016-12-06 02:47:36 +00:00
|
|
|
|
{
|
|
|
|
|
C_up = !C_up;
|
|
|
|
|
clock_C = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-12-10 04:00:40 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
if (env_vol_A == 0)
|
|
|
|
|
{
|
|
|
|
|
audio_samples[sample_count] = (short)(sound_out_A ? volume_table[vol_A] : 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//Console.Write(env_vol_A); Console.Write("A"); Console.Write('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (env_vol_B == 0)
|
|
|
|
|
{
|
|
|
|
|
audio_samples[sample_count] += (short)(sound_out_B ? volume_table[vol_B] : 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//Console.Write(env_vol_B); Console.Write("B"); Console.Write('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (env_vol_C == 0)
|
|
|
|
|
{
|
|
|
|
|
audio_samples[sample_count] += (short)(sound_out_C ? volume_table[vol_C] : 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//Console.Write(env_vol_C); Console.Write("C"); Console.Write('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sample_count++;
|
2016-12-06 02:47:36 +00:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2012-08-08 23:05:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|