using System.IO; namespace MAME.Core { public unsafe class AY8910 { public struct _ay_ym_param { public double r_up; public double r_down; public int res_count; public double[] res; } public struct ay8910_context { public int streams; public int ready; public int register_latch; public byte[] regs; public int last_enable; public int[] count; public byte[] output; public byte output_noise; public int count_noise; public int count_env; public sbyte env_step; public int env_volume; public byte hold, alternate, attack, holding; public int rng; public byte env_step_mask; public int step; public int zero_is_off; public byte[] vol_enabled; public int[][] vol_table; public int[][] env_table; public int[] vol3d_table; } public static _ay_ym_param ay8910_param; public static _ay_ym_param ym2149_param, ym2149_param_env; public struct ay8910_interface { public int flags; public int[] res_load; public read8handler portAread; public read8handler portBread; public write8handler portAwrite; public write8handler portBwrite; } public delegate byte read8handler(int offset); public delegate void write8handler(int offset, byte value); public static _ay_ym_param ay_ym_param, ay_ym_param_env; public ay8910_context ay8910info; public static AY8910[] AA8910 = new AY8910[3]; public static ay8910_interface ay8910_intf; public sound_stream stream; private int NOISE_ENABLEQ(int chan) { return (ay8910info.regs[7] >> (3 + chan)) & 1; } private int TONE_ENABLEQ(int chan) { return (ay8910info.regs[7] >> chan) & 1; } private int TONE_PERIOD(int chan) { return ay8910info.regs[chan << 1] | ((ay8910info.regs[(chan << 1) | 1] & 0x0f) << 8); } private int NOISE_PERIOD() { return ay8910info.regs[6] & 0x1f; } //private int TONE_VOLUME(int chan) //{ // return ay8910info.regs[8 + chan] & 0x0f; //} //private int TONE_ENVELOPE(int chan) //{ // return (ay8910info.regs[8 + chan] >> 4) & 1; //} //用常量优化海量访问 private const int TONE_VOLUME_REG_OFFSET = 8; private const int TONE_VOLUME_VOLUME_MASK = 0x0f; private int TONE_VOLUME(int chan) { return ay8910info.regs[TONE_VOLUME_REG_OFFSET + chan] & TONE_VOLUME_VOLUME_MASK; } private const int TONE_ENVELOPE_REG_OFFSET = 8; private const int TONE_ENVELOPE_MOVE = 4; private const int TONE_ENVELOPE_VOLUME_MASK = 0x01; private int TONE_ENVELOPE(int chan) { return (ay8910info.regs[TONE_ENVELOPE_REG_OFFSET + chan] >> TONE_ENVELOPE_MOVE) & TONE_ENVELOPE_VOLUME_MASK; } private int ENVELOPE_PERIOD() { return ay8910info.regs[11] | (ay8910info.regs[12] << 8); } public static void ay8910_start_ym(int chip_type, int sndindex, int clock, ay8910_interface intf) { int i; AA8910[sndindex] = new AY8910(); ym2149_param.r_up = 630; ym2149_param.r_down = 801; ym2149_param.res_count = 16; ym2149_param.res = new double[] { 73770, 37586, 27458, 21451, 15864, 12371, 8922, 6796, 4763, 3521, 2403, 1737, 1123, 762, 438, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; ym2149_param_env.r_up = 630; ym2149_param_env.r_down = 801; ym2149_param_env.res_count = 32; ym2149_param_env.res = new double[] { 103350, 73770, 52657, 37586, 32125, 27458, 24269, 21451, 18447, 15864, 14009, 12371, 10506, 8922, 7787, 6796, 5689, 4763, 4095, 3521, 2909, 2403, 2043, 1737, 1397, 1123, 925, 762, 578, 438, 332, 251 }; ay8910_param.r_up = 5806; ay8910_param.r_down = 300; ay8910_param.res_count = 16; ay8910_param.res = new double[] { 118996, 42698, 33105, 24770, 17925, 12678, 9331, 5807, 4936, 3038, 2129, 1658, 1271, 969, 781, 623, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; AA8910[sndindex].ay8910info = new ay8910_context(); AA8910[sndindex].ay8910info.regs = new byte[16]; AA8910[sndindex].ay8910info.count = new int[3]; AA8910[sndindex].ay8910info.output = new byte[3]; AA8910[sndindex].ay8910info.vol_enabled = new byte[3]; AA8910[sndindex].ay8910info.vol3d_table = new int[8 * 32 * 32 * 32]; AA8910[sndindex].ay8910info.vol_table = new int[3][]; for (i = 0; i < 3; i++) { AA8910[sndindex].ay8910info.vol_table[i] = new int[16]; } AA8910[sndindex].ay8910info.env_table = new int[3][]; for (i = 0; i < 3; i++) { AA8910[sndindex].ay8910info.env_table[i] = new int[32]; } ay8910_intf = intf; if ((ay8910_intf.flags & 2) != 0) { AA8910[sndindex].ay8910info.streams = 1; } else { AA8910[sndindex].ay8910info.streams = 3; } switch (chip_type) { case 6: case 9: AA8910[sndindex].ay8910info.step = 2; ay_ym_param = ay8910_param; ay_ym_param_env = ay8910_param; AA8910[sndindex].ay8910info.zero_is_off = 1; AA8910[sndindex].ay8910info.env_step_mask = 0x0f; break; case 10: case 14: case 17: case 18: case 16: case 12: case 13: case 11: default: AA8910[sndindex].ay8910info.step = 1; ay_ym_param = ym2149_param; ay_ym_param_env = ym2149_param_env; AA8910[sndindex].ay8910info.zero_is_off = 0; AA8910[sndindex].ay8910info.env_step_mask = 0x1f; break; } AA8910[sndindex].build_mixer_table(); AA8910[sndindex].stream = new sound_stream(clock / 8, 0, AA8910[sndindex].ay8910info.streams, AA8910[sndindex].ay8910_update); AA8910[sndindex].ay8910_set_clock_ym(clock); } private void build_3D_table(double rl, int normalize, double factor, int zero_is_off) { int j, j1, j2, j3, e, indx; double rt, rw, n; double min = 10.0, max = 0.0; double[] temp = new double[8 * 32 * 32 * 32]; for (e = 0; e < 8; e++) { for (j1 = 0; j1 < 32; j1++) { for (j2 = 0; j2 < 32; j2++) { for (j3 = 0; j3 < 32; j3++) { if (zero_is_off != 0) { n = (j1 != 0 || (e & 0x01) != 0) ? 1 : 0; n += (j2 != 0 || (e & 0x02) != 0) ? 1 : 0; n += (j3 != 0 || (e & 0x04) != 0) ? 1 : 0; } else { n = 3.0; } rt = n / ay_ym_param.r_up + 3.0 / ay_ym_param.r_down + 1.0 / rl; rw = n / ay_ym_param.r_up; rw += 1.0 / (((e & 0x01) != 0) ? ay_ym_param_env.res[j1] : ay_ym_param.res[j1]); rt += 1.0 / (((e & 0x01) != 0) ? ay_ym_param_env.res[j1] : ay_ym_param.res[j1]); rw += 1.0 / (((e & 0x02) != 0) ? ay_ym_param_env.res[j2] : ay_ym_param.res[j2]); rt += 1.0 / (((e & 0x02) != 0) ? ay_ym_param_env.res[j2] : ay_ym_param.res[j2]); rw += 1.0 / (((e & 0x04) != 0) ? ay_ym_param_env.res[j3] : ay_ym_param.res[j3]); rt += 1.0 / (((e & 0x04) != 0) ? ay_ym_param_env.res[j3] : ay_ym_param.res[j3]); indx = (e << 15) | (j3 << 10) | (j2 << 5) | j1; temp[indx] = rw / rt; if (temp[indx] < min) { min = temp[indx]; } if (temp[indx] > max) { max = temp[indx]; } } } } } if (normalize != 0) { for (j = 0; j < 32 * 32 * 32 * 8; j++) { ay8910info.vol3d_table[j] = (int)(0x7fff * (((temp[j] - min) / (max - min))) * factor); } } else { for (j = 0; j < 32 * 32 * 32 * 8; j++) { ay8910info.vol3d_table[j] = (int)(0x7fff * temp[j]); } } } public static void build_single_table(double rl, _ay_ym_param par, int normalize, int[] ii, int zero_is_off) { int j; double rt, rw = 0; double[] temp = new double[32]; double min = 10.0, max = 0.0; for (j = 0; j < par.res_count; j++) { rt = 1.0 / par.r_down + 1.0 / rl; rw = 1.0 / par.res[j]; rt += 1.0 / par.res[j]; if (!((zero_is_off != 0) && (j == 0))) { rw += 1.0 / par.r_up; rt += 1.0 / par.r_up; } temp[j] = rw / rt; if (temp[j] < min) { min = temp[j]; } if (temp[j] > max) { max = temp[j]; } } if (normalize != 0) { for (j = 0; j < par.res_count; j++) { ii[j] = (int)(0x7fff * (((temp[j] - min) / (max - min)) - 0.25) * 0.5); } } else { for (j = 0; j < par.res_count; j++) { ii[j] = (int)(0x7fff * temp[j]); } } } private int mix_3D() { int indx = 0, chan; for (chan = 0; chan < 3; chan++) { if (TONE_ENVELOPE(chan) != 0) { indx |= ((1 << (chan + 15)) | ((ay8910info.vol_enabled[chan] != 0) ? ay8910info.env_volume << (chan * 5) : 0)); } else { indx |= ((ay8910info.vol_enabled[chan] != 0) ? TONE_VOLUME(chan) << (chan * 5) : 0); } } return ay8910info.vol3d_table[indx]; } private void ay8910_write_reg(int r, byte v) { ay8910info.regs[r] = v; switch (r) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 8: case 9: case 10: case 11: case 12: break; case 7: if ((ay8910info.last_enable == -1) || ((ay8910info.last_enable & 0x40) != (ay8910info.regs[7] & 0x40))) { if (ay8910_intf.portAwrite != null) { ay8910_intf.portAwrite(0, (ay8910info.regs[7] & 0x40) != 0 ? ay8910info.regs[14] : (byte)0xff); } } if ((ay8910info.last_enable == -1) || ((ay8910info.last_enable & 0x80) != (ay8910info.regs[7] & 0x80))) { if (ay8910_intf.portBwrite != null) { ay8910_intf.portBwrite(0, (ay8910info.regs[7] & 0x80) != 0 ? ay8910info.regs[15] : (byte)0xff); } } ay8910info.last_enable = ay8910info.regs[7]; break; case 13: ay8910info.attack = ((ay8910info.regs[13] & 0x04) != 0) ? ay8910info.env_step_mask : (byte)0x00; if ((ay8910info.regs[13] & 0x08) == 0) { ay8910info.hold = 1; ay8910info.alternate = ay8910info.attack; } else { ay8910info.hold = (byte)(ay8910info.regs[13] & 0x01); ay8910info.alternate = (byte)(ay8910info.regs[13] & 0x02); } ay8910info.env_step = (sbyte)ay8910info.env_step_mask; ay8910info.holding = 0; ay8910info.env_volume = (ay8910info.env_step ^ ay8910info.attack); break; case 14: if ((ay8910info.regs[7] & 0x40) != 0) { if (ay8910_intf.portAwrite != null) { ay8910_intf.portAwrite(0, ay8910info.regs[14]); } } break; case 15: if ((ay8910info.regs[7] & 0x80) != 0) { if (ay8910_intf.portBwrite != null) { ay8910_intf.portBwrite(0, ay8910info.regs[15]); } } break; } } public void ay8910_update(int offset, int length) { int chan, i, j; if (ay8910info.ready == 0) { for (chan = 0; chan < ay8910info.streams; chan++) { for (j = 0; j < length; j++) { stream.streamoutput_Ptrs[chan][offset + j] = 0; } } } for (i = 0; i < length; i++) { for (chan = 0; chan < 3; chan++) { ay8910info.count[chan]++; if (ay8910info.count[chan] >= TONE_PERIOD(chan)) { ay8910info.output[chan] ^= 1; ay8910info.count[chan] = 0; ; } } ay8910info.count_noise++; if (ay8910info.count_noise >= NOISE_PERIOD()) { if (((ay8910info.rng + 1) & 2) != 0) { ay8910info.output_noise ^= 1; } if ((ay8910info.rng & 1) != 0) { ay8910info.rng ^= 0x24000; } ay8910info.rng >>= 1; ay8910info.count_noise = 0; } for (chan = 0; chan < 3; chan++) { ay8910info.vol_enabled[chan] = (byte)((ay8910info.output[chan] | TONE_ENABLEQ(chan)) & (ay8910info.output_noise | NOISE_ENABLEQ(chan))); } if (ay8910info.holding == 0) { ay8910info.count_env++; if (ay8910info.count_env >= ENVELOPE_PERIOD() * ay8910info.step) { ay8910info.count_env = 0; ay8910info.env_step--; if (ay8910info.env_step < 0) { if (ay8910info.hold != 0) { if (ay8910info.alternate != 0) { ay8910info.attack ^= ay8910info.env_step_mask; } ay8910info.holding = 1; ay8910info.env_step = 0; } else { if (ay8910info.alternate != 0 && (ay8910info.env_step & (ay8910info.env_step_mask + 1)) != 0) { ay8910info.attack ^= ay8910info.env_step_mask; } ay8910info.env_step &= (sbyte)ay8910info.env_step_mask; } } } } ay8910info.env_volume = (ay8910info.env_step ^ ay8910info.attack); if (ay8910info.streams == 3) { for (chan = 0; chan < 3; chan++) { if (TONE_ENVELOPE(chan) != 0) { int i1 = ay8910info.env_table[chan][ay8910info.vol_enabled[chan] != 0 ? ay8910info.env_volume : 0]; stream.streamoutput_Ptrs[chan][offset] = ay8910info.env_table[chan][ay8910info.vol_enabled[chan] != 0 ? ay8910info.env_volume : 0]; } else { int i1 = ay8910info.vol_table[chan][ay8910info.vol_enabled[chan] != 0 ? TONE_VOLUME(chan) : 0]; stream.streamoutput_Ptrs[chan][offset] = ay8910info.vol_table[chan][ay8910info.vol_enabled[chan] != 0 ? TONE_VOLUME(chan) : 0]; } } } else { stream.streamoutput_Ptrs[0][offset] = mix_3D(); } offset++; } } public void build_mixer_table() { int normalize = 0; int chan; if ((ay8910_intf.flags & 1) != 0) { normalize = 1; } for (chan = 0; chan < 3; chan++) { build_single_table(ay8910_intf.res_load[chan], ay_ym_param, normalize, ay8910info.vol_table[chan], ay8910info.zero_is_off); build_single_table(ay8910_intf.res_load[chan], ay_ym_param_env, normalize, ay8910info.env_table[chan], 0); } build_3D_table(ay8910_intf.res_load[0], normalize, 3, ay8910info.zero_is_off); } public void ay8910_reset_ym() { int i; ay8910info.register_latch = 0; ay8910info.rng = 1; ay8910info.output[0] = 0; ay8910info.output[1] = 0; ay8910info.output[2] = 0; ay8910info.count[0] = 0; ay8910info.count[1] = 0; ay8910info.count[2] = 0; ay8910info.count_noise = 0; ay8910info.count_env = 0; ay8910info.output_noise = 0x01; for (i = 0; i < 14; i++) { ay8910_write_reg(i, 0); } ay8910info.ready = 1; } public void ay8910_set_clock_ym(int clock) { int rate1, rate2; rate1 = (stream.new_sample_rate != 0) ? stream.new_sample_rate : stream.sample_rate; rate2 = clock / 8; if (rate2 != rate1) { stream.new_sample_rate = rate2; } } public void ay8910_write_ym(int addr, byte data) { if ((addr & 1) != 0) { int r = ay8910info.register_latch; if (r > 15) { return; } if (r == 13 || ay8910info.regs[r] != data) { stream.stream_update(); } ay8910_write_reg(r, data); } else { ay8910info.register_latch = data & 0x0f; } } public byte ay8910_read_ym() { int r = ay8910info.register_latch; if (r > 15) { return 0; } switch (r) { case 14: if (ay8910_intf.portAread != null) { ay8910info.regs[14] = ay8910_intf.portAread(0); } break; case 15: if (ay8910_intf.portBread != null) { ay8910info.regs[15] = ay8910_intf.portBread(0); } break; } return ay8910info.regs[r]; } public void SaveStateBinary(BinaryWriter writer) { int i; writer.Write(ay8910info.register_latch); writer.Write(ay8910info.regs, 0, 16); for (i = 0; i < 3; i++) { writer.Write(ay8910info.count[i]); } writer.Write(ay8910info.output, 0, 3); writer.Write(ay8910info.output_noise); writer.Write(ay8910info.count_noise); writer.Write(ay8910info.count_env); writer.Write(ay8910info.env_step); writer.Write(ay8910info.env_volume); writer.Write(ay8910info.hold); writer.Write(ay8910info.alternate); writer.Write(ay8910info.attack); writer.Write(ay8910info.holding); writer.Write(ay8910info.rng); writer.Write(ay8910info.vol_enabled, 0, 3); } public void LoadStateBinary(BinaryReader reader) { int i; ay8910info.register_latch = reader.ReadInt32(); ay8910info.regs = reader.ReadBytes(16); for (i = 0; i < 3; i++) { ay8910info.count[i] = reader.ReadInt32(); } ay8910info.output = reader.ReadBytes(3); ay8910info.output_noise = reader.ReadByte(); ay8910info.count_noise = reader.ReadInt32(); ay8910info.count_env = reader.ReadInt32(); ay8910info.env_step = reader.ReadSByte(); ay8910info.env_volume = reader.ReadInt32(); ay8910info.hold = reader.ReadByte(); ay8910info.alternate = reader.ReadByte(); ay8910info.attack = reader.ReadByte(); ay8910info.holding = reader.ReadByte(); ay8910info.rng = reader.ReadInt32(); ay8910info.vol_enabled = reader.ReadBytes(3); } } }