📄 apu_internal.cpp
字号:
//////////////////////////////////////////////////////////////////////////
// //
// APU Internal //
// Norix //
// written 2002/06/27 //
// last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#include "DebugOut.h"
#include "Pathlib.h"
#include "Config.h"
#include "APU_INTERNAL.h"
#include "state.h"
#include "rom.h"
// Dummy
#define APU_CLOCK 1789772.5f
// Volume shift
#define RECTANGLE_VOL_SHIFT 8
#define TRIANGLE_VOL_SHIFT 9
#define NOISE_VOL_SHIFT 8
#define DPCM_VOL_SHIFT 8
INT APU_INTERNAL::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_INTERNAL::freq_limit[8] = {
0x03FF, 0x0555, 0x0666, 0x071C, 0x0787, 0x07C1, 0x07E0, 0x07F0
};
INT APU_INTERNAL::duty_lut[4] = {
2, 4, 8, 12
};
INT APU_INTERNAL::noise_freq[16] = {
4, 8, 16, 32, 64, 96, 128, 160,
202, 254, 380, 508, 762, 1016, 2034, 4068
};
// DMC 揮憲僋儘僢僋悢僥乕僽儖
INT APU_INTERNAL::dpcm_cycles[16] = {
428, 380, 340, 320, 286, 254, 226, 214,
190, 160, 142, 128, 106, 85, 72, 54
};
//INT APU_INTERNAL::vol_effect[16] = {
// 100, 94, 88, 83, 78, 74, 71, 67,
// 64, 61, 59, 56, 54, 52, 50, 48
//};
APU_INTERNAL::APU_INTERNAL()
{
nes = NULL;
ZEROMEMORY( &ch0, sizeof(ch0) );
ZEROMEMORY( &ch1, sizeof(ch1) );
ZEROMEMORY( &ch2, sizeof(ch2) );
ZEROMEMORY( &ch3, sizeof(ch3) );
ZEROMEMORY( &ch4, sizeof(ch4) );
FrameIRQ = 0xC0;
FrameCycle = 0;
FrameIRQoccur = 0;
FrameCount = 0;
FrameType = 0;
reg4015 = sync_reg4015 = 0;
cpu_clock = APU_CLOCK;
sampling_rate = 22050;
// 壖愝掕
cycle_rate = (INT)(cpu_clock*65536.0f/22050.0f);
}
APU_INTERNAL::~APU_INTERNAL()
{
}
void APU_INTERNAL::Reset( FLOAT fClock, INT nRate )
{
ZEROMEMORY( &ch0, sizeof(ch0) );
ZEROMEMORY( &ch1, sizeof(ch1) );
ZEROMEMORY( &ch2, sizeof(ch2) );
ZEROMEMORY( &ch3, sizeof(ch3) );
// ZEROMEMORY( &ch4, sizeof(ch4) );
ZEROMEMORY( bToneTableEnable, sizeof(bToneTableEnable) );
ZEROMEMORY( ToneTable, sizeof(ToneTable) );
ZEROMEMORY( ChannelTone, sizeof(ChannelTone) );
reg4015 = sync_reg4015 = 0;
// Sweep complement
ch0.complement = 0x00;
ch1.complement = 0xFF;
// Noise shift register
ch3.shift_reg = 0x4000;
Setup( fClock, nRate );
// $4011偼弶婜壔偟側偄
WORD addr;
for( addr = 0x4000; addr <= 0x4010; addr++ ) {
Write( addr, 0x00 );
SyncWrite( addr, 0x00 );
}
// Write( 0x4001, 0x08 ); // Reset帪偼inc儌乕僪偵側傞?
// Write( 0x4005, 0x08 ); // Reset帪偼inc儌乕僪偵側傞?
Write( 0x4012, 0x00 );
Write( 0x4013, 0x00 );
Write( 0x4015, 0x00 );
SyncWrite( 0x4012, 0x00 );
SyncWrite( 0x4013, 0x00 );
SyncWrite( 0x4015, 0x00 );
// $4017偼彂偒崬傒偱弶婜壔偟側偄(弶婜儌乕僪偑0偱偁傞偺傪婜懸偟偨僜僼僩偑偁傞堊)
FrameIRQ = 0xC0;
FrameCycle = 0;
FrameIRQoccur = 0;
FrameCount = 0;
FrameType = 0;
// ToneLoad
ToneTableLoad();
}
void APU_INTERNAL::Setup( FLOAT fClock, INT nRate )
{
cpu_clock = fClock;
sampling_rate = nRate;
cycle_rate = (INT)(fClock*65536.0f/(float)nRate);
}
//
// Wavetable loader
//
void APU_INTERNAL::ToneTableLoad()
{
FILE* fp = NULL;
CHAR buf[512];
string tempstr;
tempstr = CPathlib::MakePathExt( nes->rom->GetRomPath(), nes->rom->GetRomName(), "vtd" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
if( !(fp = ::fopen( tempstr.c_str(), "r" )) ) {
// 僨僼僅儖僩僼傽僀儖柤偱撉傫偱尒傞
tempstr = CPathlib::MakePathExt( nes->rom->GetRomPath(), "Default", "vtd" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
if( !(fp = ::fopen( tempstr.c_str(), "r" )) ) {
DEBUGOUT( "File not found.\n" );
return;
}
}
DEBUGOUT( "Find.\n" );
// 掕媊僼傽僀儖傪撉傒崬傓
while( ::fgets( buf, 512, fp ) != NULL ) {
if( buf[0] == ';' || ::strlen(buf) <= 0 )
continue;
CHAR c = ::toupper( buf[0] );
if( c == '@' ) {
// 壒怓撉傒崬傒
CHAR* pbuf = &buf[1];
CHAR* p;
INT no, val;
// 壒怓僫儞僶乕庢摼
no = ::strtol( pbuf, &p, 10 );
if( pbuf == p )
continue;
if( no < 0 || no > TONEDATA_MAX-1 )
continue;
// '='傪尒偮偗傞
p = ::strchr( pbuf, '=' );
if( p == NULL )
continue;
pbuf = p+1; // 師
// 壒怓僨乕僞傪庢摼
for( INT i = 0; i < TONEDATA_LEN; i++ ) {
val = ::strtol( pbuf, &p, 10 );
if( pbuf == p ) // 庢摼幐攕丠
break;
if( *p == ',' ) // 僇儞儅傪旘偽偡乧
pbuf = p+1;
else
pbuf = p;
ToneTable[no][i] = val;
}
if( i >= TONEDATA_MAX )
bToneTableEnable[no] = TRUE;
} else
if( c == 'A' || c == 'B' ) {
// 奺僠儍儞僱儖壒怓掕媊
CHAR* pbuf = &buf[1];
CHAR* p;
INT no, val;
// 撪晹壒怓僫儞僶乕庢摼
no = ::strtol( pbuf, &p, 10 );
if( pbuf == p )
continue;
pbuf = p;
if( no < 0 || no > TONE_MAX-1 )
continue;
// '='傪尒偮偗傞
p = ::strchr( pbuf, '=' );
if( p == NULL )
continue;
pbuf = p+1; // 師
// 壒怓僫儞僶乕庢摼
val = ::strtol( pbuf, &p, 10 );
if( pbuf == p )
continue;
pbuf = p;
if( val > TONEDATA_MAX-1 )
continue;
if( val >= 0 && bToneTableEnable[val] ) {
if( c == 'A' ) {
ChannelTone[0][no] = val+1;
} else {
ChannelTone[1][no] = val+1;
}
} else {
if( c == 'A' ) {
ChannelTone[0][no] = 0;
} else {
ChannelTone[1][no] = 0;
}
}
} else
if( c == 'C' ) {
// 奺僠儍儞僱儖壒怓掕媊
CHAR* pbuf = &buf[1];
CHAR* p;
INT val;
// '='傪尒偮偗傞
p = ::strchr( pbuf, '=' );
if( p == NULL )
continue;
pbuf = p+1; // 師
// 壒怓僫儞僶乕庢摼
val = ::strtol( pbuf, &p, 10 );
if( pbuf == p )
continue;
pbuf = p;
if( val > TONEDATA_MAX-1 )
continue;
if( val >= 0 && bToneTableEnable[val] ) {
ChannelTone[2][0] = val+1;
} else {
ChannelTone[2][0] = 0;
}
}
}
FCLOSE( fp );
}
INT APU_INTERNAL::Process( INT channel )
{
switch( channel ) {
case 0:
return RenderRectangle( ch0 );
case 1:
return RenderRectangle( ch1 );
case 2:
return RenderTriangle();
case 3:
return RenderNoise();
case 4:
return RenderDPCM();
default:
return 0;
}
return 0;
}
void APU_INTERNAL::Write( WORD addr, BYTE data )
{
switch( addr ) {
// CH0,1 rectangle
case 0x4000: case 0x4001:
case 0x4002: case 0x4003:
case 0x4004: case 0x4005:
case 0x4006: case 0x4007:
WriteRectangle( (addr<0x4004)?0:1, addr, data );
break;
// CH2 triangle
case 0x4008: case 0x4009:
case 0x400A: case 0x400B:
WriteTriangle( addr, data );
break;
// CH3 noise
case 0x400C: case 0x400D:
case 0x400E: case 0x400F:
WriteNoise( addr, data );
break;
// CH4 DPCM
case 0x4010: case 0x4011:
case 0x4012: case 0x4013:
WriteDPCM( addr, data );
break;
case 0x4015:
reg4015 = data;
if( !(data&(1<<0)) ) {
ch0.enable = 0;
ch0.len_count = 0;
}
if( !(data&(1<<1)) ) {
ch1.enable = 0;
ch1.len_count = 0;
}
if( !(data&(1<<2)) ) {
ch2.enable = 0;
ch2.len_count = 0;
ch2.lin_count = 0;
}
if( !(data&(1<<3)) ) {
ch3.enable = 0;
ch3.len_count = 0;
}
if( !(data&(1<<4)) ) {
ch4.enable = 0;
ch4.dmalength = 0;
} else {
ch4.enable = 0xFF;
if( !ch4.dmalength ) {
ch4.address = ch4.cache_addr;
ch4.dmalength = ch4.cache_dmalength;
}
}
break;
case 0x4017:
break;
// VirtuaNES屌桳億乕僩
case 0x4018:
UpdateRectangle( ch0, (INT)data );
UpdateRectangle( ch1, (INT)data );
UpdateTriangle ( (INT)data );
UpdateNoise ( (INT)data );
break;
default:
break;
}
}
BYTE APU_INTERNAL::Read( WORD addr )
{
BYTE data = addr>>8;
if( addr == 0x4015 ) {
data = 0;
if( ch0.enable && ch0.len_count > 0 ) data |= (1<<0);
if( ch1.enable && ch1.len_count > 0 ) data |= (1<<1);
if( ch2.enable ) {
if( !ch2.holdnote ) {
if( ch2.len_count > 0 ) data |= (1<<2);
} else {
if( ch2.lin_count > 0 ) data |= (1<<2);
}
}
if( ch3.enable && ch3.len_count > 0 ) data |= (1<<3);
}
return data;
}
void APU_INTERNAL::SyncWrite( WORD addr, BYTE data )
{
//DEBUGOUT( "$%04X=$%02X\n", addr, data );
switch( addr ) {
// CH0,1 rectangle
case 0x4000: case 0x4001:
case 0x4002: case 0x4003:
case 0x4004: case 0x4005:
case 0x4006: case 0x4007:
SyncWriteRectangle( (addr<0x4004)?0:1, addr, data );
break;
// CH2 triangle
case 0x4008: case 0x4009:
case 0x400A: case 0x400B:
SyncWriteTriangle( addr, data );
break;
// CH3 noise
case 0x400C: case 0x400D:
case 0x400E: case 0x400F:
SyncWriteNoise( addr, data );
break;
// CH4 DPCM
case 0x4010: case 0x4011:
case 0x4012: case 0x4013:
SyncWriteDPCM( addr, data );
break;
case 0x4015:
sync_reg4015 = data;
if( !(data&(1<<0)) ) {
ch0.sync_enable = 0;
ch0.sync_len_count = 0;
}
if( !(data&(1<<1)) ) {
ch1.sync_enable = 0;
ch1.sync_len_count = 0;
}
if( !(data&(1<<2)) ) {
ch2.sync_enable = 0;
ch2.sync_len_count = 0;
ch2.sync_lin_count = 0;
}
if( !(data&(1<<3)) ) {
ch3.sync_enable = 0;
ch3.sync_len_count = 0;
}
if( !(data&(1<<4)) ) {
ch4.sync_enable = 0;
ch4.sync_dmalength = 0;
ch4.sync_irq_enable = 0;
nes->cpu->ClrIRQ( IRQ_DPCM );
} else {
ch4.sync_enable = 0xFF;
if( !ch4.sync_dmalength ) {
ch4.sync_cycles = ch4.sync_cache_cycles;
ch4.sync_dmalength = ch4.sync_cache_dmalength;
}
}
break;
case 0x4017:
SyncWrite4017( data );
break;
// VirtuaNES屌桳億乕僩
case 0x4018:
SyncUpdateRectangle( ch0, (INT)data );
SyncUpdateRectangle( ch1, (INT)data );
SyncUpdateTriangle ( (INT)data );
SyncUpdateNoise ( (INT)data );
break;
default:
break;
}
}
// $4017 Write
void APU_INTERNAL::SyncWrite4017( BYTE data )
{
FrameCycle = 0;
FrameIRQ = data;
FrameIRQoccur = 0;
nes->cpu->ClrIRQ( IRQ_FRAMEIRQ );
if( !(data&0x80) ) {
FrameType = 0;
FrameCount = 0;
} else {
FrameCount = 0;
FrameType = 1;
// Counters Update
nes->Write( 0x4018, 0 );
}
}
BYTE APU_INTERNAL::SyncRead( WORD addr )
{
BYTE data = addr>>8;
if( addr == 0x4015 ) {
data = 0;
if( ch0.sync_enable && ch0.sync_len_count > 0 ) data |= (1<<0);
if( ch1.sync_enable && ch1.sync_len_count > 0 ) data |= (1<<1);
if( ch2.sync_enable ) {
if( !ch2.sync_holdnote ) {
if( ch2.sync_len_count > 0 ) data |= (1<<2);
} else {
if( ch2.sync_lin_count > 0 ) data |= (1<<2);
}
}
if( ch3.sync_enable && ch3.sync_len_count > 0 ) data |= (1<<3);
if( ch4.sync_enable && ch4.sync_dmalength ) data |= (1<<4);
if( FrameIRQoccur ) data |= (1<<6);
if( ch4.sync_irq_enable ) data |= (1<<7);
FrameIRQoccur = 0;
nes->cpu->ClrIRQ( IRQ_FRAMEIRQ );
}
if( addr == 0x4017 ) {
if( FrameIRQoccur )
data = 0;
else
data = (1<<6);
}
return data;
}
BOOL APU_INTERNAL::Sync( INT cycles )
{
FrameCycle += cycles;
if( FrameCycle >= 7457 ) {
FrameCycle -= 7457;
if( FrameType == 0 ) {
// 0,1,2,3
if( FrameCount < 4 ) {
// Counters Update
nes->Write( 0x4018, (BYTE)FrameCount&3 );
}
if( ++FrameCount > 3 ) {
FrameCount = 0;
if( !(FrameIRQ&0xC0) && nes->GetFrameIRQmode() ) {
FrameIRQoccur = 0xFF;
nes->cpu->SetIRQ( IRQ_FRAMEIRQ );
}
}
} else {
// 0,1,2
if( FrameCount < 3 ) {
// Counters Update
nes->Write( 0x4018, (BYTE)(FrameCount+1)&3 );
}
if( ++FrameCount > 4 ) {
FrameCount = 0;
// Counters Update
nes->Write( 0x4018, 0 );
}
}
}
return FrameIRQoccur | SyncUpdateDPCM( cycles );
}
INT APU_INTERNAL::GetFreq( INT channel )
{
INT freq = 0;
// Rectangle
if( channel == 0 || channel == 1 ) {
RECTANGLE* ch;
if( channel == 0 ) ch = &ch0;
else ch = &ch1;
if( !ch->enable || ch->len_count <= 0 )
return 0;
if( (ch->freq < 8) || (!ch->swp_inc && ch->freq > ch->freqlimit) )
return 0;
if( !ch->volume )
return 0;
// freq = (((INT)ch->reg[3]&0x07)<<8)+(INT)ch->reg[2]+1;
freq = (INT)(16.0f*cpu_clock/(FLOAT)(ch->freq+1));
return freq;
}
// Triangle
if( channel == 2 ) {
if( !ch2.enable || ch2.len_count <= 0 )
return 0;
if( ch2.lin_count <= 0 || ch2.freq < INT2FIX(8) )
return 0;
freq = (((INT)ch2.reg[3]&0x07)<<8)+(INT)ch2.reg[2]+1;
freq = (INT)(8.0f*cpu_clock/(FLOAT)freq);
return freq;
}
// Noise
if( channel == 3 ) {
if( !ch3.enable || ch3.len_count <= 0 )
return 0;
if( ch3.env_fixed ) {
if( !ch3.volume )
return 0;
} else {
if( !ch3.env_vol )
return 0;
}
return 1;
}
// DPCM
if( channel == 4 ) {
if( ch4.enable && ch4.dmalength )
return 1;
}
return 0;
}
// Write Rectangle
void APU_INTERNAL::WriteRectangle( INT no, WORD addr, BYTE data )
{
RECTANGLE& ch = (no==0)?ch0:ch1;
ch.reg[addr&3] = data;
switch( addr&3 ) {
case 0:
ch.holdnote = data&0x20;
ch.volume = data&0x0F;
ch.env_fixed = data&0x10;
ch.env_decay = (data&0x0F)+1;
ch.duty = duty_lut[data>>6];
break;
case 1:
ch.swp_on = data&0x80;
ch.swp_inc = data&0x08;
ch.swp_shift = data&0x07;
ch.swp_decay = ((data>>4)&0x07)+1;
ch.freqlimit = freq_limit[data&0x07];
break;
case 2:
ch.freq = (ch.freq&(~0xFF))+data;
break;
case 3: // Master
ch.freq = ((data&0x07)<<8)+(ch.freq&0xFF);
ch.len_count = vbl_length[data>>3]*2;
ch.env_vol = 0x0F;
ch.env_count = ch.env_decay+1;
ch.adder = 0;
if( reg4015&(1<<no) )
ch.enable = 0xFF;
break;
}
}
// Update Rectangle
void APU_INTERNAL::UpdateRectangle( RECTANGLE& ch, INT type )
{
if( !ch.enable || ch.len_count <= 0 )
return;
// Update Length/Sweep
if( type&1 ) {
// Update Length
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -