381 lines
16 KiB
C++
381 lines
16 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Nintendo MMC5 //
|
||
// Norix //
|
||
// written 2001/09/18 //
|
||
// last modify ----/--/-- //
|
||
//////////////////////////////////////////////////////////////////////////
|
||
#include "DebugOut.h"
|
||
|
||
#include "APU_MMC5.h"
|
||
#include "state.h"
|
||
|
||
#define RECTANGLE_VOL_SHIFT 8
|
||
#define DAOUT_VOL_SHIFT 6
|
||
|
||
INT APU_MMC5::vbl_length[32] = {
|
||
5, 127, 10, 1, 19, 2, 40, 3,
|
||
80, 4, 30, 5, 7, 6, 13, 7,
|
||
6, 8, 12, 9, 24, 10, 48, 11,
|
||
96, 12, 36, 13, 8, 14, 16, 15
|
||
};
|
||
|
||
INT APU_MMC5::duty_lut[4] = {
|
||
2, 4, 8, 12
|
||
};
|
||
|
||
INT APU_MMC5::decay_lut[16];
|
||
INT APU_MMC5::vbl_lut[32];
|
||
|
||
APU_MMC5::APU_MMC5()
|
||
{
|
||
// 仮設定
|
||
Reset( APU_CLOCK, 22050 );
|
||
}
|
||
|
||
APU_MMC5::~APU_MMC5()
|
||
{
|
||
}
|
||
|
||
void APU_MMC5::Reset( FLOAT fClock, INT nRate )
|
||
{
|
||
ZeroMemory( &ch0, sizeof(ch0) );
|
||
ZeroMemory( &ch1, sizeof(ch1) );
|
||
|
||
reg5010 = reg5011 = reg5015 = 0;
|
||
|
||
sync_reg5015 = 0;
|
||
FrameCycle = 0;
|
||
|
||
Setup( fClock, nRate );
|
||
|
||
for( WORD addr = 0x5000; addr <= 0x5015; addr++ ) {
|
||
Write( addr, 0 );
|
||
}
|
||
}
|
||
|
||
void APU_MMC5::Setup( FLOAT fClock, INT nRate )
|
||
{
|
||
cpu_clock = fClock;
|
||
cycle_rate = (INT)(fClock*65536.0f/(float)nRate);
|
||
|
||
// Create Tables
|
||
INT i;
|
||
INT samples = (INT)((float)nRate/60.0f);
|
||
for( i = 0; i < 16; i++ )
|
||
decay_lut[i] = (i+1)*samples*5;
|
||
for( i = 0; i < 32; i++ )
|
||
vbl_lut[i] = vbl_length[i]*samples*5;
|
||
}
|
||
|
||
void APU_MMC5::Write( WORD addr, BYTE data )
|
||
{
|
||
//DEBUGOUT( "$%04X:%02X\n", addr, data );
|
||
switch( addr ) {
|
||
// MMC5 CH0 rectangle
|
||
case 0x5000:
|
||
ch0.reg[0] = data;
|
||
ch0.volume = data&0x0F;
|
||
ch0.holdnote = data&0x20;
|
||
ch0.fixed_envelope = data&0x10;
|
||
ch0.env_decay = decay_lut[data&0x0F];
|
||
ch0.duty_flip = duty_lut[data>>6];
|
||
break;
|
||
case 0x5001:
|
||
ch0.reg[1] = data;
|
||
break;
|
||
case 0x5002:
|
||
ch0.reg[2] = data;
|
||
ch0.freq = INT2FIX( ((ch0.reg[3]&0x07)<<8)+data+1 );
|
||
break;
|
||
case 0x5003:
|
||
ch0.reg[3] = data;
|
||
ch0.vbl_length = vbl_lut[data>>3];
|
||
ch0.env_vol = 0;
|
||
ch0.freq = INT2FIX( ((data&0x07)<<8)+ch0.reg[2]+1 );
|
||
if( reg5015 & 0x01 )
|
||
ch0.enable = 0xFF;
|
||
break;
|
||
// MMC5 CH1 rectangle
|
||
case 0x5004:
|
||
ch1.reg[0] = data;
|
||
ch1.volume = data&0x0F;
|
||
ch1.holdnote = data&0x20;
|
||
ch1.fixed_envelope = data&0x10;
|
||
ch1.env_decay = decay_lut[data&0x0F];
|
||
ch1.duty_flip = duty_lut[data>>6];
|
||
break;
|
||
case 0x5005:
|
||
ch1.reg[1] = data;
|
||
break;
|
||
case 0x5006:
|
||
ch1.reg[2] = data;
|
||
ch1.freq = INT2FIX( ((ch1.reg[3]&0x07)<<8)+data+1 );
|
||
break;
|
||
case 0x5007:
|
||
ch1.reg[3] = data;
|
||
ch1.vbl_length = vbl_lut[data>>3];
|
||
ch1.env_vol = 0;
|
||
ch1.freq = INT2FIX( ((data&0x07)<<8)+ch1.reg[2]+1 );
|
||
if( reg5015 & 0x02 )
|
||
ch1.enable = 0xFF;
|
||
break;
|
||
case 0x5010:
|
||
reg5010 = data;
|
||
break;
|
||
case 0x5011:
|
||
reg5011 = data;
|
||
break;
|
||
case 0x5012:
|
||
case 0x5013:
|
||
case 0x5014:
|
||
break;
|
||
case 0x5015:
|
||
reg5015 = data;
|
||
if( reg5015 & 0x01 ) {
|
||
ch0.enable = 0xFF;
|
||
} else {
|
||
ch0.enable = 0;
|
||
ch0.vbl_length = 0;
|
||
}
|
||
if( reg5015 & 0x02 ) {
|
||
ch1.enable = 0xFF;
|
||
} else {
|
||
ch1.enable = 0;
|
||
ch1.vbl_length = 0;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void APU_MMC5::SyncWrite( WORD addr, BYTE data )
|
||
{
|
||
switch( addr ) {
|
||
// MMC5 CH0 rectangle
|
||
case 0x5000:
|
||
sch0.reg[0] = data;
|
||
sch0.holdnote = data&0x20;
|
||
break;
|
||
case 0x5001:
|
||
case 0x5002:
|
||
sch0.reg[addr&3] = data;
|
||
break;
|
||
case 0x5003:
|
||
sch0.reg[3] = data;
|
||
sch0.vbl_length = vbl_length[data>>3];
|
||
if( sync_reg5015 & 0x01 )
|
||
sch0.enable = 0xFF;
|
||
break;
|
||
// MMC5 CH1 rectangle
|
||
case 0x5004:
|
||
sch1.reg[0] = data;
|
||
sch1.holdnote = data&0x20;
|
||
break;
|
||
case 0x5005:
|
||
case 0x5006:
|
||
sch1.reg[addr&3] = data;
|
||
break;
|
||
case 0x5007:
|
||
sch1.reg[3] = data;
|
||
sch1.vbl_length = vbl_length[data>>3];
|
||
if( sync_reg5015 & 0x02 )
|
||
sch1.enable = 0xFF;
|
||
break;
|
||
case 0x5010:
|
||
case 0x5011:
|
||
case 0x5012:
|
||
case 0x5013:
|
||
case 0x5014:
|
||
break;
|
||
case 0x5015:
|
||
sync_reg5015 = data;
|
||
if( sync_reg5015 & 0x01 ) {
|
||
sch0.enable = 0xFF;
|
||
} else {
|
||
sch0.enable = 0;
|
||
sch0.vbl_length = 0;
|
||
}
|
||
if( sync_reg5015 & 0x02 ) {
|
||
sch1.enable = 0xFF;
|
||
} else {
|
||
sch1.enable = 0;
|
||
sch1.vbl_length = 0;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
BYTE APU_MMC5::SyncRead( WORD addr )
|
||
{
|
||
BYTE data = 0;
|
||
|
||
if( addr == 0x5015 ) {
|
||
if( sch0.enable && sch0.vbl_length > 0 ) data |= (1<<0);
|
||
if( sch1.enable && sch1.vbl_length > 0 ) data |= (1<<1);
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
BOOL APU_MMC5::Sync( INT cycles )
|
||
{
|
||
FrameCycle += cycles;
|
||
if( FrameCycle >= 7457*5/2 ) {
|
||
FrameCycle -= 7457*5/2;
|
||
|
||
if( sch0.enable && !sch0.holdnote ) {
|
||
if( sch0.vbl_length ) {
|
||
sch0.vbl_length--;
|
||
}
|
||
}
|
||
if( sch1.enable && !sch1.holdnote ) {
|
||
if( sch1.vbl_length ) {
|
||
sch1.vbl_length--;
|
||
}
|
||
}
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
INT APU_MMC5::Process( INT channel )
|
||
{
|
||
switch( channel ) {
|
||
case 0:
|
||
return RectangleRender( ch0 );
|
||
break;
|
||
case 1:
|
||
return RectangleRender( ch1 );
|
||
break;
|
||
case 2:
|
||
return (INT)reg5011 << DAOUT_VOL_SHIFT;
|
||
break;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
INT APU_MMC5::GetFreq( INT channel )
|
||
{
|
||
if( channel == 0 || channel == 1 ) {
|
||
RECTANGLE* ch;
|
||
if( channel == 0 ) ch = &ch0;
|
||
else ch = &ch1;
|
||
|
||
if( !ch->enable || ch->vbl_length <= 0 )
|
||
return 0;
|
||
if( ch->freq < INT2FIX( 8 ) )
|
||
return 0;
|
||
if( ch->fixed_envelope ) {
|
||
if( !ch->volume )
|
||
return 0;
|
||
} else {
|
||
if( !(0x0F-ch->env_vol) )
|
||
return 0;
|
||
}
|
||
|
||
return (INT)(256.0f*cpu_clock/((FLOAT)FIX2INT(ch->freq)*16.0f));
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
INT APU_MMC5::RectangleRender( RECTANGLE& ch )
|
||
{
|
||
if( !ch.enable || ch.vbl_length <= 0 )
|
||
return 0;
|
||
|
||
// vbl length counter
|
||
if( !ch.holdnote )
|
||
ch.vbl_length -= 5;
|
||
|
||
// envelope unit
|
||
ch.env_phase -= 5*4;
|
||
while( ch.env_phase < 0 ) {
|
||
ch.env_phase += ch.env_decay;
|
||
if( ch.holdnote )
|
||
ch.env_vol = (ch.env_vol+1)&0x0F;
|
||
else if( ch.env_vol < 0x0F )
|
||
ch.env_vol++;
|
||
}
|
||
|
||
if( ch.freq < INT2FIX( 8 ) )
|
||
return 0;
|
||
|
||
INT volume;
|
||
if( ch.fixed_envelope )
|
||
volume = (INT)ch.volume;
|
||
else
|
||
volume = (INT)(0x0F-ch.env_vol);
|
||
|
||
INT output = volume<<RECTANGLE_VOL_SHIFT;
|
||
|
||
ch.phaseacc -= cycle_rate;
|
||
if( ch.phaseacc >= 0 ) {
|
||
if( ch.adder < ch.duty_flip )
|
||
ch.output_vol = output;
|
||
else
|
||
ch.output_vol = -output;
|
||
return ch.output_vol;
|
||
}
|
||
|
||
if( ch.freq > cycle_rate ) {
|
||
ch.phaseacc += ch.freq;
|
||
ch.adder = (ch.adder+1)&0x0F;
|
||
if( ch.adder < ch.duty_flip )
|
||
ch.output_vol = output;
|
||
else
|
||
ch.output_vol = -output;
|
||
} else {
|
||
// 加重平均
|
||
INT num_times, total;
|
||
num_times = total = 0;
|
||
while( ch.phaseacc < 0 ) {
|
||
ch.phaseacc += ch.freq;
|
||
ch.adder = (ch.adder+1)&0x0F;
|
||
if( ch.adder < ch.duty_flip )
|
||
total += output;
|
||
else
|
||
total -= output;
|
||
num_times++;
|
||
}
|
||
ch.output_vol = total/num_times;
|
||
}
|
||
|
||
return ch.output_vol;
|
||
}
|
||
|
||
INT APU_MMC5::GetStateSize()
|
||
{
|
||
return 3*sizeof(BYTE) + sizeof(ch0) + sizeof(ch1) + sizeof(sch0) + sizeof(sch1);
|
||
}
|
||
|
||
void APU_MMC5::SaveState( LPBYTE p )
|
||
{
|
||
SETBYTE( p, reg5010 );
|
||
SETBYTE( p, reg5011 );
|
||
SETBYTE( p, reg5015 );
|
||
|
||
SETBLOCK( p, &ch0, sizeof(ch0) );
|
||
SETBLOCK( p, &ch1, sizeof(ch1) );
|
||
|
||
SETBYTE( p, sync_reg5015 );
|
||
SETBLOCK( p, &sch0, sizeof(sch0) );
|
||
SETBLOCK( p, &sch1, sizeof(sch1) );
|
||
}
|
||
|
||
void APU_MMC5::LoadState( LPBYTE p )
|
||
{
|
||
GETBYTE( p, reg5010 );
|
||
GETBYTE( p, reg5011 );
|
||
GETBYTE( p, reg5015 );
|
||
|
||
GETBLOCK( p, &ch0, sizeof(ch0) );
|
||
GETBLOCK( p, &ch1, sizeof(ch1) );
|
||
|
||
GETBYTE( p, sync_reg5015 );
|
||
GETBLOCK( p, &sch0, sizeof(sch0) );
|
||
GETBLOCK( p, &sch1, sizeof(sch1) );
|
||
}
|
||
|