ym2612: support TimerA and TimerB
This commit is contained in:
parent
d9134ac6ce
commit
59eb4b4abe
|
@ -328,6 +328,7 @@
|
||||||
<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>
|
||||||
|
|
|
@ -110,6 +110,8 @@ namespace BizHawk.Emulation.Consoles.Sega
|
||||||
//Console.WriteLine("running z80");
|
//Console.WriteLine("running z80");
|
||||||
SoundCPU.ExecuteCycles(228);
|
SoundCPU.ExecuteCycles(228);
|
||||||
SoundCPU.Interrupt = false;
|
SoundCPU.Interrupt = false;
|
||||||
|
} else {
|
||||||
|
SoundCPU.TotalExecutedCycles += 228; // I emulate the YM2612 synced to Z80 clock, for better or worse. Keep the timer going even if Z80 isn't running.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VDP.ScanLine == 224)
|
if (VDP.ScanLine == 224)
|
||||||
|
|
|
@ -422,6 +422,9 @@ namespace BizHawk.Emulation.Sound
|
||||||
public void GetSamples(short[] samples)
|
public void GetSamples(short[] samples)
|
||||||
{
|
{
|
||||||
int elapsedCycles = frameStopTime - frameStartTime;
|
int elapsedCycles = frameStopTime - frameStartTime;
|
||||||
|
if (elapsedCycles == 0)
|
||||||
|
elapsedCycles = 1; // hey it's better than diving by zero
|
||||||
|
|
||||||
int start = 0;
|
int start = 0;
|
||||||
while (commands.Count > 0)
|
while (commands.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,12 +31,18 @@ this correctly right now.
|
||||||
*/
|
*/
|
||||||
public byte ReadStatus(int clock)
|
public byte ReadStatus(int clock)
|
||||||
{
|
{
|
||||||
// default status: not BUSY, both timers tripped
|
UpdateTimers(clock);
|
||||||
return 3;
|
|
||||||
|
byte retval = 0;
|
||||||
|
if (TimerATripped) retval |= 1;
|
||||||
|
if (TimerBTripped) retval |= 2;
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(int addr, byte value, int clock)
|
public void Write(int addr, byte value, int clock)
|
||||||
{
|
{
|
||||||
|
UpdateTimers(clock);
|
||||||
|
|
||||||
if (addr == 0)
|
if (addr == 0)
|
||||||
{
|
{
|
||||||
PartSelect = 1;
|
PartSelect = 1;
|
||||||
|
@ -65,42 +71,22 @@ this correctly right now.
|
||||||
void WriteCommand(QueuedCommand cmd)
|
void WriteCommand(QueuedCommand cmd)
|
||||||
{
|
{
|
||||||
if (cmd.Part == 1)
|
if (cmd.Part == 1)
|
||||||
Part1_WriteRegister(cmd.Register, cmd.Data, 0); // TODO remove clock 0 ?
|
Part1_WriteRegister(cmd.Register, cmd.Data);
|
||||||
else
|
else
|
||||||
Part2_WriteRegister(cmd.Register, cmd.Data);
|
Part2_WriteRegister(cmd.Register, cmd.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteTimerA_MSB_24(byte value, int clock)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Timer A (msb) {0:X2}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerA_LSB_25(byte value, int clock)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Timer A (lsb) {0:X2}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerB_26(byte value, int clock)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Timer B {0:X2}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteTimerControl_27(byte value, int clock)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Timer control {0:X2}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// information on TIMER is on pg 6
|
// information on TIMER is on pg 6
|
||||||
void Part1_WriteRegister(byte register, byte value, int clock) // TODO remove clock?
|
void Part1_WriteRegister(byte register, byte value)
|
||||||
{
|
{
|
||||||
switch (register)
|
switch (register)
|
||||||
{
|
{
|
||||||
case 0x22: Console.WriteLine("LFO Control {0:X2}", value); break;
|
//case 0x22: Console.WriteLine("LFO Control {0:X2}", value); break;
|
||||||
case 0x24: break; // Timer A MSB, handled immediately
|
case 0x24: break; // Timer A MSB, handled immediately
|
||||||
case 0x25: break; // Timer A LSB, handled immediately
|
case 0x25: break; // Timer A LSB, handled immediately
|
||||||
case 0x26: break; // Timer B, 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 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 0x28: Console.WriteLine("Operator Key On/Off Ctrl {0:X2}", value); break;
|
||||||
case 0x2A: DacValue = value; break;
|
case 0x2A: DacValue = value; break;
|
||||||
case 0x2B: DacEnable = (value & 0x80) != 0; 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
|
case 0x2C: throw new Exception("something wrote to ym2612 port $2C!"); //http://forums.sonicretro.org/index.php?showtopic=28589
|
||||||
|
@ -117,10 +103,11 @@ this correctly right now.
|
||||||
// PG4 has some info on frquency calculations
|
// PG4 has some info on frquency calculations
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (register >= 0x30 && register < 0xA0)
|
/* if (register >= 0x30 && register < 0xA0)
|
||||||
Console.WriteLine("P1 FM Channel data write");
|
Console.WriteLine("P1 FM Channel data write");
|
||||||
else
|
else
|
||||||
Console.WriteLine("P1 REG {0:X2} WRITE {1:X2}", register, value); break;
|
Console.WriteLine("P1 REG {0:X2} WRITE {1:X2}", register, value); */
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +115,10 @@ this correctly right now.
|
||||||
{
|
{
|
||||||
// NOTE. Only first bank has multi-frequency CSM/Special mode. This mode can't work on CH6.
|
// NOTE. Only first bank has multi-frequency CSM/Special mode. This mode can't work on CH6.
|
||||||
|
|
||||||
if (register >= 0x30 && register < 0xA0)
|
/*if (register >= 0x30 && register < 0xA0)
|
||||||
Console.WriteLine("P2 FM Channel data write");
|
Console.WriteLine("P2 FM Channel data write");
|
||||||
else
|
else
|
||||||
Console.WriteLine("P2 REG {0:X2} WRITE {1:X2}", register, value);
|
Console.WriteLine("P2 REG {0:X2} WRITE {1:X2}", register, value);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QueuedCommand
|
public class QueuedCommand
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,10 +19,13 @@ namespace BizHawk.Emulation.Sound
|
||||||
Channels[3] = new Channel();
|
Channels[3] = new Channel();
|
||||||
Channels[4] = new Channel();
|
Channels[4] = new Channel();
|
||||||
Channels[5] = new Channel();
|
Channels[5] = new Channel();
|
||||||
|
|
||||||
|
InitTimers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
|
throw new Exception("something is resetting the ym2612");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginFrame(int clock)
|
public void BeginFrame(int clock)
|
||||||
|
|
Loading…
Reference in New Issue