AxibugEmuOnline/virtuanessrc097-master/NES/ApuEX/APU_MMC5.cpp

381 lines
16 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//////////////////////////////////////////////////////////////////////////
// //
// 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) );
}