ANESE/thirdparty/nes_snd_emu/usage.txt
Daniel Prilik edb40e42e7 APU courtesy of Blargg's 💯 nes_snd_emu library
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!
2017-12-20 00:00:56 -05:00

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