mirror of
https://github.com/daniel5151/ANESE.git
synced 2025-04-02 10:32:00 -04:00
I started looking into how to make the APU, and boy, let me tell ya, it's going to be a massive undertaking. Undoubtedly a fun undertaking, but still... Since it's a personal goal to get Super Mario Bros 2 running before new years (after all, that game has won game-of-the-year i don't even know _how_ many years in a row), i've decided to just use Blargg's venerable `nes_snd_emu` library for now. It took some wrestling, it it's in, and it works! Almost. I still don't know why enabling the Frame IRQ kills most games, but i'll look into that! Welp, on to bigger and better things! Namely: MMC1, which will give me Zelda and... Super Mario Bros 2!
234 lines
4.6 KiB
Text
Executable file
234 lines
4.6 KiB
Text
Executable file
|
|
// Using the APU in an emulator
|
|
// ----------------------------
|
|
//
|
|
// Two code skeletons are shown below. The first shows how to add basic APU support to an
|
|
// emulator that doesn't keep track of overall CPU time. The second shows how to add full
|
|
// APU support (including IRQs) to a framework which keeps track of overall CPU time.
|
|
|
|
// Basic APU support
|
|
// -----------------
|
|
|
|
#include "Nes_Apu.h"
|
|
|
|
Blip_Buffer buf;
|
|
Nes_Apu apu;
|
|
|
|
void output_samples( const blip_sample_t*, size_t count );
|
|
const size_t out_size = 4096;
|
|
blip_sample_t out_buf [out_size];
|
|
|
|
int total_cycles;
|
|
int cycles_remain;
|
|
|
|
int elapsed()
|
|
{
|
|
return total_cycles - cycles_remain;
|
|
}
|
|
|
|
const int apu_addr = 0x4000;
|
|
|
|
void cpu_write_memory( cpu_addr_t addr, int data )
|
|
{
|
|
// ...
|
|
if ( addr >= apu.start_addr && addr <= apu.end_addr )
|
|
apu.write_register( elapsed(), addr, data );
|
|
}
|
|
|
|
int cpu_read_memory( cpu_addr_t addr )
|
|
{
|
|
// ...
|
|
if ( addr == apu.status_addr )
|
|
return apu.read_status( elapsed() );
|
|
}
|
|
|
|
int dmc_read( void*, cpu_addr_t addr )
|
|
{
|
|
return cpu_read_memory( addr );
|
|
}
|
|
|
|
void emulate_cpu( int cycle_count )
|
|
{
|
|
total_cycles += cycle_count;
|
|
cycles_remain += cycle_count;
|
|
|
|
while ( cycles_remain > 0 )
|
|
{
|
|
// emulate opcode
|
|
// ...
|
|
cycles_remain -= cycle_table [opcode];
|
|
}
|
|
}
|
|
|
|
void end_time_frame( int length )
|
|
{
|
|
apu.end_frame( length );
|
|
buf.end_frame( length );
|
|
total_cycles -= length;
|
|
|
|
// Read some samples out of Blip_Buffer if there are enough to
|
|
// fill our output buffer
|
|
if ( buf.samples_avail() >= out_size )
|
|
{
|
|
size_t count = buf.read_samples( out_buf, out_size );
|
|
output_samples( out_buf, count );
|
|
}
|
|
}
|
|
|
|
void render_frame()
|
|
{
|
|
// ...
|
|
end_time_frame( elapsed() );
|
|
}
|
|
|
|
void init()
|
|
{
|
|
blargg_err_t error = buf.sample_rate( 44100 );
|
|
if ( error )
|
|
report_error( error );
|
|
buf.clock_rate( 1789773 );
|
|
apu.output( &buf );
|
|
|
|
apu.dmc_reader( dmc_read );
|
|
}
|
|
|
|
|
|
// Full APU support
|
|
// ----------------
|
|
|
|
#include "Nes_Apu.h"
|
|
|
|
Blip_Buffer buf;
|
|
Nes_Apu apu;
|
|
|
|
void output_samples( const blip_sample_t*, size_t count );
|
|
const size_t out_size = 4096;
|
|
blip_sample_t out_buf [out_size];
|
|
|
|
cpu_time_t cpu_end_time; // Time for CPU to stop at
|
|
cpu_time_t cpu_time; // Current CPU time relative to current time frame
|
|
|
|
unsigned apu_addr = 0x4000;
|
|
|
|
void cpu_write_memory( cpu_addr_t addr, int data )
|
|
{
|
|
// ...
|
|
if ( addr >= apu.start_addr && addr <= apu.end_addr )
|
|
apu.write_register( cpu_time, addr, data );
|
|
}
|
|
|
|
int cpu_read_memory( cpu_addr_t addr )
|
|
{
|
|
// ...
|
|
if ( addr == apu.status_addr )
|
|
return apu.read_status( cpu_time );
|
|
}
|
|
|
|
int dmc_read( void*, cpu_addr_t addr )
|
|
{
|
|
return cpu_read_memory( addr );
|
|
}
|
|
|
|
void emulate_cpu()
|
|
{
|
|
while ( cpu_time < cpu_end_time )
|
|
{
|
|
// Decode instruction
|
|
// ...
|
|
cpu_time += cycle_table [opcode];
|
|
switch ( opcode )
|
|
{
|
|
// ...
|
|
case 0x58: // CLI
|
|
if ( cpu_status & i_flag )
|
|
{
|
|
cpu_status &= ~i_flag;
|
|
return; // I flag cleared; stop CPU immediately
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Time of next IRQ if before end_time, otherwise end_time
|
|
cpu_time_t earliest_irq_before( cpu_time_t end_time )
|
|
{
|
|
if ( !(cpu_status & i_flag) )
|
|
{
|
|
cpu_time_t irq_time = apu.earliest_irq();
|
|
if ( irq_time < end_time )
|
|
end_time = irq_time;
|
|
}
|
|
return end_time;
|
|
}
|
|
|
|
// IRQ time may have changed, so update CPU end time
|
|
void irq_changed( void* )
|
|
{
|
|
cpu_end_time = earliest_irq_before( cpu_end_time );
|
|
}
|
|
|
|
// Run CPU to 'end_time' (possibly a few cycles over depending on instruction)
|
|
void run_cpu_until( cpu_time_t end_time )
|
|
{
|
|
while ( cpu_time < end_time )
|
|
{
|
|
cpu_end_time = earliest_irq_before( end_time );
|
|
if ( cpu_end_time <= cpu_time )
|
|
{
|
|
// Save PC and status, load IRQ vector, set I flag, etc.
|
|
cpu_trigger_irq();
|
|
|
|
// I flag is now set, so CPU can be run for full time
|
|
cpu_end_time = end_time;
|
|
}
|
|
|
|
emulate_cpu();
|
|
}
|
|
}
|
|
|
|
// Run CPU for at least 'cycle_count'
|
|
void run_cpu( int cycle_count )
|
|
{
|
|
run_cpu_until( cpu_time + cycle_count );
|
|
}
|
|
|
|
// End a time frame and make its samples available for reading
|
|
void end_time_frame( cpu_time_t length )
|
|
{
|
|
apu.end_frame( length );
|
|
buf.end_frame( length );
|
|
cpu_time -= length;
|
|
|
|
// Read some samples out of Blip_Buffer if there are enough to
|
|
// fill our output buffer
|
|
if ( buf.samples_avail() >= out_size )
|
|
{
|
|
size_t count = buf.read_samples( out_buf, out_size );
|
|
output_samples( out_buf, count );
|
|
}
|
|
}
|
|
|
|
// Emulator probably has a function which renders a video frame
|
|
void render_video_frame()
|
|
{
|
|
for ( int n = scanline_count; n--; )
|
|
{
|
|
run_cpu( 113 ); // or whatever would be done here
|
|
// ...
|
|
}
|
|
// ...
|
|
end_time_frame( cpu_time );
|
|
}
|
|
|
|
void init()
|
|
{
|
|
blargg_err_t error = buf.sample_rate( 44100 );
|
|
if ( error )
|
|
report_error( error );
|
|
buf.clock_rate( 1789773 );
|
|
apu.output( &buf );
|
|
|
|
apu.dmc_reader( dmc_read );
|
|
apu.irq_notifier( irq_changed );
|
|
}
|
|
|