ym2612 mothaaaaafukkkkaaaaaaa
This commit is contained in:
parent
5770931669
commit
32bc79be06
|
@ -354,9 +354,6 @@
|
||||||
<Compile Include="Sound\Utilities\DualSound.cs" />
|
<Compile Include="Sound\Utilities\DualSound.cs" />
|
||||||
<Compile Include="Sound\Utilities\Equalizer.cs" />
|
<Compile Include="Sound\Utilities\Equalizer.cs" />
|
||||||
<Compile Include="Sound\VRC6.cs" />
|
<Compile Include="Sound\VRC6.cs" />
|
||||||
<Compile Include="Sound\YM2612.IO.cs" />
|
|
||||||
<Compile Include="Sound\YM2612.Channel.cs" />
|
|
||||||
<Compile Include="Sound\YM2612.Operator.cs" />
|
|
||||||
<Compile Include="Sound\Utilities\BufferedAsync.cs" />
|
<Compile Include="Sound\Utilities\BufferedAsync.cs" />
|
||||||
<Compile Include="Sound\Utilities\Metaspu.cs" />
|
<Compile Include="Sound\Utilities\Metaspu.cs" />
|
||||||
<Compile Include="Interfaces\IController.cs" />
|
<Compile Include="Interfaces\IController.cs" />
|
||||||
|
@ -390,7 +387,6 @@
|
||||||
<Compile Include="Consoles\Sega\SMS\SMS.cs" />
|
<Compile Include="Consoles\Sega\SMS\SMS.cs" />
|
||||||
<Compile Include="Consoles\Sega\SMS\VDP.cs" />
|
<Compile Include="Consoles\Sega\SMS\VDP.cs" />
|
||||||
<Compile Include="Sound\YM2413.cs" />
|
<Compile Include="Sound\YM2413.cs" />
|
||||||
<Compile Include="Sound\YM2612.Timers.cs" />
|
|
||||||
<Compile Include="Util.cs" />
|
<Compile Include="Util.cs" />
|
||||||
<Compile Include="Sound\Utilities\Waves.cs" />
|
<Compile Include="Sound\Utilities\Waves.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Sound
|
|
||||||
{
|
|
||||||
public partial class YM2612
|
|
||||||
{
|
|
||||||
public sealed class Channel
|
|
||||||
{
|
|
||||||
public readonly Operator[] Operators;
|
|
||||||
|
|
||||||
public int FrequencyNumber;
|
|
||||||
public int Block;
|
|
||||||
public int Feedback;
|
|
||||||
public int Algorithm;
|
|
||||||
|
|
||||||
public bool SpecialMode; // Enables separate frequency for each operator, available on CH3 and CH6 only
|
|
||||||
// TODO. CSM. Pg6 details CSM mode.
|
|
||||||
public bool LeftOutput;
|
|
||||||
public bool RightOutput;
|
|
||||||
|
|
||||||
public int AMS_AmplitudeModulationSensitivity;
|
|
||||||
public int FMS_FrequencyModulationSensitivity;
|
|
||||||
|
|
||||||
public Channel()
|
|
||||||
{
|
|
||||||
Operators = new Operator[4];
|
|
||||||
Operators[0] = new Operator();
|
|
||||||
Operators[1] = new Operator();
|
|
||||||
Operators[2] = new Operator();
|
|
||||||
Operators[3] = new Operator();
|
|
||||||
|
|
||||||
LeftOutput = true; // Revenge of Shinobi does not output DAC if these arent initialized ??
|
|
||||||
RightOutput = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFrequencyLow(byte value)
|
|
||||||
{
|
|
||||||
FrequencyNumber &= 0x700;
|
|
||||||
FrequencyNumber |= value;
|
|
||||||
|
|
||||||
// TODO maybe its 4-frequency mode
|
|
||||||
// TODO is this right, only reflect change when writing LSB?
|
|
||||||
Operators[0].FrequencyNumber = FrequencyNumber;
|
|
||||||
Operators[1].FrequencyNumber = FrequencyNumber;
|
|
||||||
Operators[2].FrequencyNumber = FrequencyNumber;
|
|
||||||
Operators[3].FrequencyNumber = FrequencyNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFrequencyHigh(byte value)
|
|
||||||
{
|
|
||||||
FrequencyNumber &= 0x0FF;
|
|
||||||
FrequencyNumber |= (value & 15) << 8;
|
|
||||||
Block = (value >> 3) & 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_Feedback_Algorithm(byte value)
|
|
||||||
{
|
|
||||||
Algorithm = value & 7;
|
|
||||||
Feedback = (value >> 3) & 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_Stereo_LfoSensitivy(byte value)
|
|
||||||
{
|
|
||||||
FMS_FrequencyModulationSensitivity = value & 3;
|
|
||||||
AMS_AmplitudeModulationSensitivity = (value >> 3) & 7;
|
|
||||||
RightOutput = (value & 0x40) != 0;
|
|
||||||
LeftOutput = (value & 0x80) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------
|
|
||||||
//|Mode| Behaviour |
|
|
||||||
//|----|---------------|
|
|
||||||
//| 00 | Normal |
|
|
||||||
//| 01 | Special |
|
|
||||||
//| 10 | Special + CSM |
|
|
||||||
//| 11 | Special |
|
|
||||||
//----------------------
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,231 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Sound
|
|
||||||
{
|
|
||||||
// We process TIMER writes immediately when writes come in.
|
|
||||||
// All other writes are queued up with a timestamp, so that we
|
|
||||||
// can sift through them when we're rendering audio for the frame.
|
|
||||||
|
|
||||||
public partial class YM2612
|
|
||||||
{
|
|
||||||
byte PartSelect;
|
|
||||||
byte RegisterSelect;
|
|
||||||
bool DacEnable;
|
|
||||||
byte DacValue;
|
|
||||||
|
|
||||||
Queue<QueuedCommand> commands = new Queue<QueuedCommand>();
|
|
||||||
|
|
||||||
public byte ReadStatus(int clock)
|
|
||||||
{
|
|
||||||
UpdateTimers(clock);
|
|
||||||
|
|
||||||
byte retval = 0;
|
|
||||||
if (TimerATripped) retval |= 1;
|
|
||||||
if (TimerBTripped) retval |= 2;
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(int addr, byte value, int clock)
|
|
||||||
{
|
|
||||||
UpdateTimers(clock);
|
|
||||||
|
|
||||||
if (addr == 0)
|
|
||||||
{
|
|
||||||
PartSelect = 1;
|
|
||||||
RegisterSelect = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (addr == 2)
|
|
||||||
{
|
|
||||||
PartSelect = 2;
|
|
||||||
RegisterSelect = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PartSelect == 1)
|
|
||||||
{
|
|
||||||
if (RegisterSelect == 0x24) { WriteTimerA_MSB_24(value, clock); return; }
|
|
||||||
if (RegisterSelect == 0x25) { WriteTimerA_LSB_25(value, clock); return; }
|
|
||||||
if (RegisterSelect == 0x26) { WriteTimerB_26(value, clock); return; }
|
|
||||||
if (RegisterSelect == 0x27) { WriteTimerControl_27(value, clock); } // don't return on this one; we process immediately AND enqueue command for port $27.
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd = new QueuedCommand { Part = PartSelect, Register = RegisterSelect, Data = value, Clock = clock-frameStartClock };
|
|
||||||
commands.Enqueue(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteCommand(QueuedCommand cmd)
|
|
||||||
{
|
|
||||||
if (cmd.Part == 1)
|
|
||||||
Part1_WriteRegister(cmd.Register, cmd.Data);
|
|
||||||
else
|
|
||||||
Part2_WriteRegister(cmd.Register, cmd.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GetChanOpP1(byte value, out int channel, out int oper)
|
|
||||||
{
|
|
||||||
value &= 15;
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case 0: channel = 0; oper = 0; return;
|
|
||||||
case 4: channel = 0; oper = 2; return;
|
|
||||||
case 8: channel = 0; oper = 1; return;
|
|
||||||
case 12: channel = 0; oper = 3; return;
|
|
||||||
|
|
||||||
case 1: channel = 1; oper = 0; return;
|
|
||||||
case 5: channel = 1; oper = 2; return;
|
|
||||||
case 9: channel = 1; oper = 1; return;
|
|
||||||
case 13: channel = 1; oper = 3; return;
|
|
||||||
|
|
||||||
case 2: channel = 2; oper = 0; return;
|
|
||||||
case 6: channel = 2; oper = 2; return;
|
|
||||||
case 10: channel = 2; oper = 1; return;
|
|
||||||
case 14: channel = 2; oper = 3; return;
|
|
||||||
|
|
||||||
default: channel = -1; oper = -1; return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GetChanOpP2(byte value, out int channel, out int oper)
|
|
||||||
{
|
|
||||||
value &= 15;
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case 0: channel = 3; oper = 0; return;
|
|
||||||
case 4: channel = 3; oper = 2; return;
|
|
||||||
case 8: channel = 3; oper = 1; return;
|
|
||||||
case 12: channel = 3; oper = 3; return;
|
|
||||||
|
|
||||||
case 1: channel = 4; oper = 0; return;
|
|
||||||
case 5: channel = 4; oper = 2; return;
|
|
||||||
case 9: channel = 4; oper = 1; return;
|
|
||||||
case 13: channel = 4; oper = 3; return;
|
|
||||||
|
|
||||||
case 2: channel = 5; oper = 0; return;
|
|
||||||
case 6: channel = 5; oper = 2; return;
|
|
||||||
case 10: channel = 5; oper = 1; return;
|
|
||||||
case 14: channel = 5; oper = 3; return;
|
|
||||||
|
|
||||||
default: channel = -1; oper = -1; return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Part1_WriteRegister(byte register, byte value)
|
|
||||||
{
|
|
||||||
switch (register)
|
|
||||||
{
|
|
||||||
//case 0x22: Console.WriteLine("LFO Control {0:X2}", value); break;
|
|
||||||
case 0x24: break; // Timer A MSB, handled immediately
|
|
||||||
case 0x25: break; // Timer A LSB, handled immediately
|
|
||||||
case 0x26: break; // Timer B, handled immediately
|
|
||||||
//case 0x27: Console.WriteLine("$27: Ch3 Mode / Timer Control {0:X2}", value); break; // determines if CH3 has 1 frequency or 4 frequencies.
|
|
||||||
//case 0x28: Console.WriteLine("Operator Key On/Off Ctrl {0:X2}", value); break;
|
|
||||||
case 0x2A: DacValue = value; break;
|
|
||||||
case 0x2B: DacEnable = (value & 0x80) != 0; break;
|
|
||||||
case 0x2C: throw new Exception("something wrote to ym2612 port $2C!"); //http://forums.sonicretro.org/index.php?showtopic=28589
|
|
||||||
|
|
||||||
default:
|
|
||||||
int chan, oper;
|
|
||||||
GetChanOpP1(register, out chan, out oper);
|
|
||||||
if (chan < 0) break; // abort if invalid port number
|
|
||||||
switch (register & 0xF0)
|
|
||||||
{
|
|
||||||
case 0x30: Channels[chan].Operators[oper].Write_MUL_DT1(value); break;
|
|
||||||
case 0x40: Channels[chan].Operators[oper].Write_TL(value); break;
|
|
||||||
case 0x50: Channels[chan].Operators[oper].Write_AR_KS(value); break;
|
|
||||||
case 0x60: Channels[chan].Operators[oper].Write_DR_AM(value); break;
|
|
||||||
case 0x70: Channels[chan].Operators[oper].Write_SR(value); break;
|
|
||||||
case 0x80: Channels[chan].Operators[oper].Write_RR_SL(value); break;
|
|
||||||
case 0x90: Channels[chan].Operators[oper].Write_SSGEG(value); break;
|
|
||||||
case 0xA0:
|
|
||||||
case 0xB0: WriteHighBlockP1(register, value); break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// "In MAME OPN emulation code register pairs for multi-frequency mode are A6A2, ACA8, AEAA, ADA9".
|
|
||||||
|
|
||||||
//D7 - operator, which frequency defined by A6A2
|
|
||||||
//D6 - .. ACA8
|
|
||||||
//D5 - .. AEAA
|
|
||||||
//D4 - .. ADA9
|
|
||||||
//Where D7=op4, D6=op3, D5=op2, and D4=op1. That matches the YM2608 document. At least that's confirmed then.
|
|
||||||
|
|
||||||
// PG4 has some info on frquency calculations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Part2_WriteRegister(byte register, byte value)
|
|
||||||
{
|
|
||||||
// NOTE. Only first bank has multi-frequency CSM/Special mode. This mode can't work on CH6.
|
|
||||||
|
|
||||||
int chan, oper;
|
|
||||||
GetChanOpP2(register, out chan, out oper);
|
|
||||||
if (chan < 0) return; // abort if invalid port number
|
|
||||||
switch (register & 0xF0)
|
|
||||||
{
|
|
||||||
case 0x30: Channels[chan].Operators[oper].Write_MUL_DT1(value); break;
|
|
||||||
case 0x40: Channels[chan].Operators[oper].Write_TL(value); break;
|
|
||||||
case 0x50: Channels[chan].Operators[oper].Write_AR_KS(value); break;
|
|
||||||
case 0x60: Channels[chan].Operators[oper].Write_DR_AM(value); break;
|
|
||||||
case 0x70: Channels[chan].Operators[oper].Write_SR(value); break;
|
|
||||||
case 0x80: Channels[chan].Operators[oper].Write_RR_SL(value); break;
|
|
||||||
case 0x90: Channels[chan].Operators[oper].Write_SSGEG(value); break;
|
|
||||||
case 0xA0:
|
|
||||||
case 0xB0: WriteHighBlockP2(register, value); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteHighBlockP1(byte register, byte value)
|
|
||||||
{
|
|
||||||
switch (register)
|
|
||||||
{
|
|
||||||
case 0xA0: Channels[0].WriteFrequencyLow(value); break;
|
|
||||||
case 0xA1: Channels[1].WriteFrequencyLow(value); break;
|
|
||||||
case 0xA2: Channels[2].WriteFrequencyLow(value); break;
|
|
||||||
|
|
||||||
case 0xA4: Channels[0].WriteFrequencyHigh(value); break;
|
|
||||||
case 0xA5: Channels[1].WriteFrequencyHigh(value); break;
|
|
||||||
case 0xA6: Channels[2].WriteFrequencyHigh(value); break;
|
|
||||||
|
|
||||||
case 0xB0: Channels[0].Write_Feedback_Algorithm(value); break;
|
|
||||||
case 0xB1: Channels[1].Write_Feedback_Algorithm(value); break;
|
|
||||||
case 0xB2: Channels[2].Write_Feedback_Algorithm(value); break;
|
|
||||||
|
|
||||||
case 0xB4: Channels[0].Write_Stereo_LfoSensitivy(value); break;
|
|
||||||
case 0xB5: Channels[1].Write_Stereo_LfoSensitivy(value); break;
|
|
||||||
case 0xB6: Channels[2].Write_Stereo_LfoSensitivy(value); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteHighBlockP2(byte register, byte value)
|
|
||||||
{
|
|
||||||
switch (register)
|
|
||||||
{
|
|
||||||
case 0xA0: Channels[3].WriteFrequencyLow(value); break;
|
|
||||||
case 0xA1: Channels[4].WriteFrequencyLow(value); break;
|
|
||||||
case 0xA2: Channels[5].WriteFrequencyLow(value); break;
|
|
||||||
|
|
||||||
case 0xA4: Channels[3].WriteFrequencyHigh(value); break;
|
|
||||||
case 0xA5: Channels[4].WriteFrequencyHigh(value); break;
|
|
||||||
case 0xA6: Channels[5].WriteFrequencyHigh(value); break;
|
|
||||||
|
|
||||||
case 0xB0: Channels[3].Write_Feedback_Algorithm(value); break;
|
|
||||||
case 0xB1: Channels[4].Write_Feedback_Algorithm(value); break;
|
|
||||||
case 0xB2: Channels[5].Write_Feedback_Algorithm(value); break;
|
|
||||||
|
|
||||||
case 0xB4: Channels[3].Write_Stereo_LfoSensitivy(value); break;
|
|
||||||
case 0xB5: Channels[4].Write_Stereo_LfoSensitivy(value); break;
|
|
||||||
case 0xB6: Channels[5].Write_Stereo_LfoSensitivy(value); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QueuedCommand
|
|
||||||
{
|
|
||||||
public byte Part;
|
|
||||||
public byte Register;
|
|
||||||
public byte Data;
|
|
||||||
public int Clock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Sound
|
|
||||||
{
|
|
||||||
public partial class YM2612
|
|
||||||
{
|
|
||||||
public sealed class Operator
|
|
||||||
{
|
|
||||||
// External Settings
|
|
||||||
public int TL_TotalLevel;
|
|
||||||
public int SL_SustainLevel;
|
|
||||||
public int AR_AttackRate;
|
|
||||||
public int DR_DecayRate;
|
|
||||||
public int SR_SustainRate;
|
|
||||||
public int RR_ReleaseRate;
|
|
||||||
public int KS_KeyScale;
|
|
||||||
public int SSG_EG;
|
|
||||||
|
|
||||||
public int DT1_Detune;
|
|
||||||
public int MUL_Multiple;
|
|
||||||
|
|
||||||
public bool AM_AmplitudeModulation;
|
|
||||||
|
|
||||||
public int FrequencyNumber;
|
|
||||||
public int Block;
|
|
||||||
|
|
||||||
// Internal State
|
|
||||||
public int PhaseCounter;
|
|
||||||
|
|
||||||
// I/O
|
|
||||||
public void Write_MUL_DT1(byte value)
|
|
||||||
{
|
|
||||||
MUL_Multiple = value & 15;
|
|
||||||
DT1_Detune = (value >> 4) & 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_TL(byte value)
|
|
||||||
{
|
|
||||||
TL_TotalLevel = value & 127;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_AR_KS(byte value)
|
|
||||||
{
|
|
||||||
AR_AttackRate = value & 31;
|
|
||||||
KS_KeyScale = value >> 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_DR_AM(byte value)
|
|
||||||
{
|
|
||||||
DR_DecayRate = value & 31;
|
|
||||||
AM_AmplitudeModulation = (value & 128) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_SR(byte value)
|
|
||||||
{
|
|
||||||
SR_SustainRate = value & 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_RR_SL(byte value)
|
|
||||||
{
|
|
||||||
RR_ReleaseRate = value & 15;
|
|
||||||
SL_SustainLevel = value >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write_SSGEG(byte value)
|
|
||||||
{
|
|
||||||
SSG_EG = value & 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateFrequency(int frequencyNumber, int block)
|
|
||||||
{
|
|
||||||
FrequencyNumber = frequencyNumber;
|
|
||||||
Block = block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//TODO "the shape of the waves of the envelope changes in a exponential when attacking it, and it changes in the straight line at other rates."
|
|
||||||
|
|
||||||
// pg 8, read it
|
|
||||||
// pg 11, detailed overview of how operator works.
|
|
||||||
// pg 12, detailed description of phase generator.
|
|
||||||
|
|
||||||
//TL Total Level 7 bits
|
|
||||||
//SL Sustain Level 4 bits
|
|
||||||
//AR Attack Rate 5 bits
|
|
||||||
//DR Decay Rate 5 bits
|
|
||||||
//SR Sustain Rate 5 bits
|
|
||||||
//RR Release Rate 4 bits
|
|
||||||
//SSG-EG SSG-EG Mode 4 bits
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Sound
|
|
||||||
{
|
|
||||||
// The master clock on the genesis is 53,693,175 MCLK / sec (NTSC)
|
|
||||||
// 53,203,424 MCLK / sec (PAL)
|
|
||||||
// 7,670,454 68K cycles / sec (7 MCLK divisor)
|
|
||||||
// 3,579,545 Z80 cycles / sec (15 MCLK divisor)
|
|
||||||
|
|
||||||
// YM2612 is fed by EXT CLOCK: 7,670,454 ECLK / sec (NTSC)
|
|
||||||
// (Same clock on 68000) 7,600,489 ECLK / sec (PAL)
|
|
||||||
|
|
||||||
// YM2612 has /6 divisor on the EXT CLOCK.
|
|
||||||
// YM2612 takes 24 cycles to generate a sample. 6*24 = 144. This is where the /144 divisor comes from.
|
|
||||||
// YM2612 native output rate is 7670454 / 144 = 53267 hz (NTSC), 52781 hz (PAL)
|
|
||||||
|
|
||||||
// Timer A ticks at the native output rate (53267 times per second for NTSC).
|
|
||||||
// Timer B ticks down with a /16 divisor. (3329 times per second for NTSC).
|
|
||||||
|
|
||||||
// Ergo, Timer A ticks every 67.2 Z80 cycles. Timer B ticks every 1075.2 Z80 cycles.
|
|
||||||
|
|
||||||
public partial class YM2612
|
|
||||||
{
|
|
||||||
const float timerAZ80Factor = 67.2f;
|
|
||||||
const float timerBZ80Factor = 1075.2f;
|
|
||||||
|
|
||||||
int TimerAPeriod, TimerBPeriod;
|
|
||||||
bool TimerATripped, TimerBTripped;
|
|
||||||
int TimerAResetClock, TimerBResetClock;
|
|
||||||
int TimerALastReset, TimerBLastReset;
|
|
||||||
|
|
||||||
byte TimerControl27;
|
|
||||||
bool TimerALoad { get { return (TimerControl27 & 1) != 0; } }
|
|
||||||
bool TimerBLoad { get { return (TimerControl27 & 2) != 0; } }
|
|
||||||
bool TimerAEnable { get { return (TimerControl27 & 4) != 0; } }
|
|
||||||
bool TimerBEnable { get { return (TimerControl27 & 8) != 0; } }
|
|
||||||
bool TimerAReset { get { return (TimerControl27 & 16) != 0; } }
|
|
||||||
bool TimerBReset { get { return (TimerControl27 & 32) != 0; } }
|
|
||||||
|
|
||||||
void InitTimers()
|
|
||||||
{
|
|
||||||
TimerAResetClock = 68812;
|
|
||||||
TimerBResetClock = 275200;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateTimers(int clock)
|
|
||||||
{
|
|
||||||
int elapsedCyclesSinceLastTimerAReset = clock - TimerALastReset;
|
|
||||||
if (elapsedCyclesSinceLastTimerAReset > TimerAResetClock)
|
|
||||||
{
|
|
||||||
if (TimerAEnable)
|
|
||||||
TimerATripped = true;
|
|
||||||
|
|
||||||
int numTimesTripped = elapsedCyclesSinceLastTimerAReset / TimerAResetClock;
|
|
||||||
TimerALastReset += (TimerAResetClock * numTimesTripped);
|
|
||||||
}
|
|
||||||
|
|
||||||
int elapsedCyclesSinceLastTimerBReset = clock - TimerBLastReset;
|
|
||||||
if (elapsedCyclesSinceLastTimerBReset > TimerBResetClock)
|
|
||||||
{
|
|
||||||
if (TimerBEnable)
|
|
||||||
TimerBTripped = true;
|
|
||||||
|
|
||||||
int numTimesTripped = elapsedCyclesSinceLastTimerBReset / TimerBResetClock;
|
|
||||||
TimerBLastReset += (TimerBResetClock * numTimesTripped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerA_MSB_24(byte value, int clock)
|
|
||||||
{
|
|
||||||
TimerAPeriod = (value << 2) | (TimerAPeriod & 3);
|
|
||||||
TimerAResetClock = (int)((1024 - TimerAPeriod) * timerAZ80Factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerA_LSB_25(byte value, int clock)
|
|
||||||
{
|
|
||||||
TimerAPeriod = (TimerAPeriod & 0x3FC) | (value & 3);
|
|
||||||
TimerAResetClock = (int)((1024 - TimerAPeriod) * timerAZ80Factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerB_26(byte value, int clock)
|
|
||||||
{
|
|
||||||
TimerBPeriod = value;
|
|
||||||
TimerBResetClock = (int)((256 - TimerBPeriod) * timerBZ80Factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerControl_27(byte value, int clock)
|
|
||||||
{
|
|
||||||
bool lagALoad = TimerALoad;
|
|
||||||
bool lagBLoad = TimerBLoad;
|
|
||||||
|
|
||||||
TimerControl27 = value;
|
|
||||||
|
|
||||||
if (!lagALoad && TimerALoad)
|
|
||||||
TimerALastReset = clock;
|
|
||||||
|
|
||||||
if (!lagBLoad && TimerBLoad)
|
|
||||||
TimerBLastReset = clock;
|
|
||||||
|
|
||||||
if (TimerAReset) TimerATripped = false;
|
|
||||||
if (TimerBReset) TimerBTripped = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue