////////////////////////////////////////////////////////////////////////// // // // 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<> ch.swp_shift); // CH 0 else ch.freq -= (ch.freq >> ch.swp_shift); // CH 1 } else { // Sweep decrement(to lower frequency) ch.freq += (ch.freq >> ch.swp_shift); } } } } // Update Envelope if( ch.env_count ) { ch.env_count--; } if( ch.env_count == 0 ) { ch.env_count = ch.env_decay; // Holdnote if( ch.holdnote ) { ch.env_vol = (ch.env_vol-1)&0x0F; } else if( ch.env_vol ) { ch.env_vol--; } } if( !ch.env_fixed ) { ch.nowvolume = ch.env_vol<>3]*2; if( sync_reg4015&(1< ch.freqlimit) ) { return 0; } if( ch.env_fixed ) { ch.nowvolume = ch.volume<>6]) ) { // 補間処理 double total; double sample_weight = ch.phaseacc; if( sample_weight > cycle_rate ) { sample_weight = cycle_rate; } total = (ch.adder < ch.duty)?sample_weight:-sample_weight; INT freq = INT2FIX( ch.freq+1 ); ch.phaseacc -= cycle_rate; while( ch.phaseacc < 0 ) { ch.phaseacc += freq; ch.adder = (ch.adder+1)&0x0F; sample_weight = freq; if( ch.phaseacc > 0 ) { sample_weight -= ch.phaseacc; } total += (ch.adder < ch.duty)?sample_weight:-sample_weight; } return (INT)floor( volume*total/cycle_rate + 0.5 ); } else { INT* pTone = ToneTable[ChannelTone[(!ch.complement)?0:1][ch.reg[0]>>6]-1]; // 更新無し ch.phaseacc -= cycle_rate*2; if( ch.phaseacc >= 0 ) { return pTone[ch.adder&0x1F]*volume/((1< cycle_rate*2 ) { ch.phaseacc += freq; ch.adder = (ch.adder+1)&0x1F; return pTone[ch.adder&0x1F]*volume/((1<>3]*2; ch2.lin_count = ch2.reg[0]&0x7F; ch2.holdnote = ch2.reg[0]&0x80; ch2.counter_start = 0x80; if( reg4015&(1<<2) ) ch2.enable = 0xFF; break; } } // Update Triangle void APU_INTERNAL::UpdateTriangle( INT type ) { if( !ch2.enable ) return; // Update Length/Linear if( !ch2.holdnote ) { if( (type&1) && ch2.len_count ) { ch2.len_count--; } ch2.counter_start = 0; if( ch2.lin_count ) { ch2.lin_count--; } if( (ch2.len_count <= 0) || (ch2.lin_count <= 0) ) { ch2.lin_count = 0; } } } // Sync Write Triangle void APU_INTERNAL::SyncWriteTriangle( WORD addr, BYTE data ) { ch2.sync_reg[addr&3] = data; switch( addr&3 ) { case 0: if( ch2.sync_counter_start & 0x80 ) { ch2.sync_lin_count = data&0x7F; ch2.sync_holdnote = data&0x80; } if( !(data&0x7F) ) { ch2.sync_lin_count = 0; } ch2.sync_counter_start = data&0x80; break; case 1: break; case 2: break; case 3: // Master ch2.sync_len_count = vbl_length[ch2.sync_reg[3]>>3]*2; ch2.sync_lin_count = ch2.sync_reg[0]&0x7F; ch2.sync_holdnote = ch2.sync_reg[0]&0x80; ch2.sync_counter_start = 0x80; if( sync_reg4015&(1<<2) ) ch2.sync_enable = 0xFF; break; } } // Sync Update Triangle void APU_INTERNAL::SyncUpdateTriangle( INT type ) { if( !ch2.sync_enable ) return; // Update Length/Linear if( !ch2.sync_holdnote ) { if( (type&1) && ch2.sync_len_count ) { ch2.sync_len_count--; } ch2.sync_counter_start = 0; if( ch2.sync_lin_count ) { ch2.sync_lin_count--; } } } // Render Triangle INT APU_INTERNAL::RenderTriangle() { INT vol; if( Config.sound.bDisableVolumeEffect ) { vol = 256; } else { vol = 256-(INT)((ch4.reg[1]&0x01)+ch4.dpcm_value*2); } if( !ch2.enable || (ch2.len_count <= 0) || (ch2.lin_count <= 0) ) { return ch2.nowvolume*vol/256; } if( ch2.freq < INT2FIX(8) ) { return ch2.nowvolume*vol/256; } // if( !ch2.holdnote ) { // ch2.counter_start = 0xFF; // } if( !(Config.sound.bChangeTone && ChannelTone[2][0]) ) { ch2.phaseacc -= cycle_rate; if( ch2.phaseacc >= 0 ) { return ch2.nowvolume*vol/256; } if( ch2.freq > cycle_rate ) { ch2.phaseacc += ch2.freq; ch2.adder = (ch2.adder+1)&0x1F; if( ch2.adder < 0x10 ) { ch2.nowvolume = (ch2.adder&0x0F)<= 0 ) { return ch2.nowvolume*vol/256; } if( ch2.freq > cycle_rate ) { ch2.phaseacc += ch2.freq; ch2.adder = (ch2.adder+1)&0x1F; ch2.nowvolume = pTone[ch2.adder&0x1F]*0x0F; return ch2.nowvolume*vol/256; } // 加重平均 INT num_times, total; num_times = total = 0; while( ch2.phaseacc < 0 ) { ch2.phaseacc += ch2.freq; ch2.adder = (ch2.adder+1)&0x1F; total += pTone[ch2.adder&0x1F]*0x0F; num_times++; } return (total/num_times)*vol/256; } } ////////////// // Write Noise void APU_INTERNAL::WriteNoise( WORD addr, BYTE data ) { ch3.reg[addr&3] = data; switch( addr&3 ) { case 0: ch3.holdnote = data&0x20; ch3.volume = data&0x0F; ch3.env_fixed = data&0x10; ch3.env_decay = (data&0x0F)+1; break; case 1: // Unused break; case 2: ch3.freq = INT2FIX(noise_freq[data&0x0F]); ch3.xor_tap = (data&0x80)?0x40:0x02; break; case 3: // Master ch3.len_count = vbl_length[data>>3]*2; ch3.env_vol = 0x0F; ch3.env_count = ch3.env_decay+1; if( reg4015&(1<<3) ) ch3.enable = 0xFF; break; } } // Update Noise void APU_INTERNAL::UpdateNoise( INT type ) { if( !ch3.enable || ch3.len_count <= 0 ) return; // Update Length if( !ch3.holdnote ) { // Holdnote if( (type&1) && ch3.len_count ) { ch3.len_count--; } } // Update Envelope if( ch3.env_count ) { ch3.env_count--; } if( ch3.env_count == 0 ) { ch3.env_count = ch3.env_decay; // Holdnote if( ch3.holdnote ) { ch3.env_vol = (ch3.env_vol-1)&0x0F; } else if( ch3.env_vol ) { ch3.env_vol--; } } if( !ch3.env_fixed ) { ch3.nowvolume = ch3.env_vol<>3]*2; if( sync_reg4015&(1<<3) ) ch3.sync_enable = 0xFF; break; } } // Sync Update Noise void APU_INTERNAL::SyncUpdateNoise( INT type ) { if( !ch3.sync_enable || ch3.sync_len_count <= 0 ) return; // Update Length if( ch3.sync_len_count && !ch3.sync_holdnote ) { if( (type&1) && ch3.sync_len_count ) { ch3.sync_len_count--; } } } // Noise ShiftRegister BYTE APU_INTERNAL::NoiseShiftreg( BYTE xor_tap ) { int bit0, bit14; bit0 = ch3.shift_reg & 1; if( ch3.shift_reg & xor_tap ) bit14 = bit0^1; else bit14 = bit0^0; ch3.shift_reg >>= 1; ch3.shift_reg |= (bit14<<14); return (bit0^1); } // Render Noise INT APU_INTERNAL::RenderNoise() { if( !ch3.enable || ch3.len_count <= 0 ) return 0; if( ch3.env_fixed ) { ch3.nowvolume = ch3.volume<= 0 ) return ch3.output*vol/256; if( ch3.freq > cycle_rate ) { ch3.phaseacc += ch3.freq; if( NoiseShiftreg(ch3.xor_tap) ) ch3.output = ch3.nowvolume; else ch3.output = -ch3.nowvolume; return ch3.output*vol/256; } INT num_times, total; num_times = total = 0; while( ch3.phaseacc < 0 ) { ch3.phaseacc += ch3.freq; if( NoiseShiftreg(ch3.xor_tap) ) ch3.output = ch3.nowvolume; else ch3.output = -ch3.nowvolume; total += ch3.output; num_times++; } return (total/num_times)*vol/256; } ////////// void APU_INTERNAL::WriteDPCM( WORD addr, BYTE data ) { ch4.reg[addr&3] = data; switch( addr&3 ) { case 0: ch4.freq = INT2FIX( dpcm_cycles[data&0x0F] ); ch4.looping = data&0x40; break; case 1: ch4.dpcm_value = (data&0x7F)>>1; break; case 2: ch4.cache_addr = 0xC000+(WORD)(data<<6); break; case 3: ch4.cache_dmalength = ((data<<4)+1)<<3; break; } } INT APU_INTERNAL::RenderDPCM() { if( ch4.dmalength ) { ch4.phaseacc -= cycle_rate; while( ch4.phaseacc < 0 ) { ch4.phaseacc += ch4.freq; if( !(ch4.dmalength&7) ) { ch4.cur_byte = nes->Read( ch4.address ); if( 0xFFFF == ch4.address ) ch4.address = 0x8000; else ch4.address++; } if( !(--ch4.dmalength) ) { if( ch4.looping ) { ch4.address = ch4.cache_addr; ch4.dmalength = ch4.cache_dmalength; } else { ch4.enable = 0; break; } } // positive delta if( ch4.cur_byte&(1<<((ch4.dmalength&7)^7)) ) { if( ch4.dpcm_value < 0x3F ) ch4.dpcm_value += 1; } else { // negative delta if( ch4.dpcm_value > 1 ) ch4.dpcm_value -= 1; } } } #if 1 // インチキ臭いプチノイズカット(TEST) ch4.dpcm_output_real = (INT)((ch4.reg[1]&0x01)+ch4.dpcm_value*2)-0x40; if( abs(ch4.dpcm_output_real-ch4.dpcm_output_fake) <= 8 ) { ch4.dpcm_output_fake = ch4.dpcm_output_real; ch4.output = (INT)ch4.dpcm_output_real< ch4.dpcm_output_fake ) ch4.dpcm_output_fake += 8; else ch4.dpcm_output_fake -= 8; ch4.output = (INT)ch4.dpcm_output_fake<cpu->ClrIRQ( IRQ_DPCM ); } break; case 1: break; case 2: break; case 3: ch4.sync_cache_dmalength = ((data<<4)+1)<<3; break; } } BOOL APU_INTERNAL::SyncUpdateDPCM( INT cycles ) { BOOL bIRQ = FALSE; if( ch4.sync_enable ) { ch4.sync_cycles -= cycles; while( ch4.sync_cycles < 0 ) { ch4.sync_cycles += ch4.sync_cache_cycles; if( ch4.sync_dmalength ) { if( !(--ch4.sync_dmalength) ) { if( ch4.sync_looping ) { ch4.sync_dmalength = ch4.sync_cache_dmalength; } else if( ch4.sync_irq_gen ) { ch4.sync_irq_enable = 0xFF; nes->cpu->SetIRQ( IRQ_DPCM ); } } } } } if( ch4.sync_irq_enable ) { bIRQ = TRUE; } return bIRQ; } INT APU_INTERNAL::GetStateSize() { return 4*sizeof(BYTE) + 3*sizeof(INT) + sizeof(ch0) + sizeof(ch1) + sizeof(ch2) + sizeof(ch3) + sizeof(ch4); } void APU_INTERNAL::SaveState( LPBYTE p ) { SETBYTE( p, reg4015 ); SETBYTE( p, sync_reg4015 ); SETINT( p, FrameCycle ); SETINT( p, FrameCount ); SETINT( p, FrameType ); SETBYTE( p, FrameIRQ ); SETBYTE( p, FrameIRQoccur ); SETBLOCK( p, &ch0, sizeof(ch0) ); SETBLOCK( p, &ch1, sizeof(ch1) ); SETBLOCK( p, &ch2, sizeof(ch2) ); SETBLOCK( p, &ch3, sizeof(ch3) ); SETBLOCK( p, &ch4, sizeof(ch4) ); } void APU_INTERNAL::LoadState( LPBYTE p ) { GETBYTE( p, reg4015 ); GETBYTE( p, sync_reg4015 ); GETINT( p, FrameCycle ); GETINT( p, FrameCount ); GETINT( p, FrameType ); GETBYTE( p, FrameIRQ ); GETBYTE( p, FrameIRQoccur ); GETBLOCK( p, &ch0, sizeof(ch0) ); GETBLOCK( p, &ch1, sizeof(ch1) ); GETBLOCK( p, &ch2, sizeof(ch2) ); GETBLOCK( p, &ch3, sizeof(ch3) ); // p += sizeof(ch3); GETBLOCK( p, &ch4, sizeof(ch4) ); }