| // NES 2A03 APU sound chip emulator |
| |
| // Nes_Snd_Emu 0.1.8 |
| #ifndef NES_APU_H |
| #define NES_APU_H |
| |
| #include "blargg_common.h" |
| #include "nes_oscs.h" |
| |
| enum { apu_status_addr = 0x4015 }; |
| enum { apu_osc_count = 5 }; |
| enum { apu_no_irq = INT_MAX / 2 + 1 }; |
| enum { apu_irq_waiting = 0 }; |
| |
| enum { apu_io_addr = 0x4000 }; |
| enum { apu_io_size = 0x18 }; |
| |
| struct apu_state_t; |
| |
| struct Nes_Apu { |
| nes_time_t last_dmc_time; |
| int osc_enables; |
| |
| struct Nes_Osc* oscs [apu_osc_count]; |
| struct Nes_Square square1; |
| struct Nes_Square square2; |
| struct Nes_Noise noise; |
| struct Nes_Triangle triangle; |
| struct Nes_Dmc dmc; |
| |
| int tempo_; |
| nes_time_t last_time; // has been run until this time in current frame |
| nes_time_t earliest_irq_; |
| nes_time_t next_irq; |
| int frame_period; |
| int frame_delay; // cycles until frame counter runs next |
| int frame; // current frame (0-3) |
| int frame_mode; |
| bool irq_flag; |
| |
| void (*irq_notifier_)( void* user_data ); |
| void* irq_data; |
| |
| Synth square_synth; // shared by squares |
| }; |
| |
| // Init Nes apu |
| void Apu_init( struct Nes_Apu* this ); |
| |
| // Set buffer to generate all sound into, or disable sound if NULL |
| void Apu_output( struct Nes_Apu* this, struct Blip_Buffer* ) ICODE_ATTR; |
| |
| // All time values are the number of cpu clock cycles relative to the |
| // beginning of the current time frame. Before resetting the cpu clock |
| // count, call end_frame( last_cpu_time ). |
| |
| // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) |
| void Apu_write_register( struct Nes_Apu* this, nes_time_t, addr_t, int data ) ICODE_ATTR; |
| |
| // Read from status register at 0x4015 |
| int Apu_read_status( struct Nes_Apu* this, nes_time_t ) ICODE_ATTR; |
| |
| // Run all oscillators up to specified time, end current time frame, then |
| // start a new time frame at time 0. Time frames have no effect on emulation |
| // and each can be whatever length is convenient. |
| void Apu_end_frame( struct Nes_Apu* this, nes_time_t ) ICODE_ATTR; |
| |
| // Additional optional features (can be ignored without any problem) |
| |
| // Reset internal frame counter, registers, and all oscillators. |
| // Use PAL timing if pal_timing is true, otherwise use NTSC timing. |
| // Set the DMC oscillator's initial DAC value to initial_dmc_dac without |
| // any audible click. |
| void Apu_reset( struct Nes_Apu* this, bool pal_mode, int initial_dmc_dac ); |
| |
| // Adjust frame period |
| void Apu_set_tempo( struct Nes_Apu* this, int ); |
| |
| // Set overall volume (default is 1.0) |
| void Apu_volume( struct Nes_Apu* this, double ); |
| |
| // Run DMC until specified time, so that any DMC memory reads can be |
| // accounted for (i.e. inserting cpu wait states). |
| void Apu_run_until( struct Nes_Apu* this, nes_time_t ) ICODE_ATTR; |
| |
| // Set sound output of specific oscillator to buffer. If buffer is NULL, |
| // the specified oscillator is muted and emulation accuracy is reduced. |
| // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, |
| // 2) Triangle, 3) Noise, 4) DMC. |
| static inline void Apu_osc_output( struct Nes_Apu* this, int osc, struct Blip_Buffer* buf ) |
| { |
| assert( (unsigned) osc < apu_osc_count ); |
| this->oscs [osc]->output = buf; |
| } |
| |
| // Set memory reader callback used by DMC oscillator to fetch samples. |
| // When callback is invoked, 'user_data' is passed unchanged as the |
| // first parameter. |
| static inline void Apu_dmc_reader( struct Nes_Apu* this, int (*func)( void*, addr_t ), void* user_data ) |
| { |
| this->dmc.prg_reader_data = user_data; |
| this->dmc.prg_reader = func; |
| } |
| |
| // Set IRQ time callback that is invoked when the time of earliest IRQ |
| // may have changed, or NULL to disable. When callback is invoked, |
| // 'user_data' is passed unchanged as the first parameter. |
| static inline void Apu_irq_notifier( struct Nes_Apu* this, void (*func)( void* user_data ), void* user_data ) |
| { |
| this->irq_notifier_ = func; |
| this->irq_data = user_data; |
| } |
| |
| // Count number of DMC reads that would occur if 'run_until( t )' were executed. |
| // If last_read is not NULL, set *last_read to the earliest time that |
| // 'count_dmc_reads( time )' would result in the same result. |
| static inline int Apu_count_dmc_reads( struct Nes_Apu* this, nes_time_t time, nes_time_t* last_read ) |
| { |
| return Dmc_count_reads( &this->dmc, time, last_read ); |
| } |
| |
| static inline nes_time_t Dmc_next_read_time( struct Nes_Dmc* this ) |
| { |
| if ( this->osc.length_counter == 0 ) |
| return apu_no_irq; // not reading |
| |
| return this->apu->last_dmc_time + this->osc.delay + (long) (this->bits_remain - 1) * this->period; |
| } |
| |
| // Time when next DMC memory read will occur |
| static inline nes_time_t Apu_next_dmc_read_time( struct Nes_Apu* this ) { return Dmc_next_read_time( &this->dmc ); } |
| void Apu_irq_changed( struct Nes_Apu* this ) ICODE_ATTR; |
| |
| #endif |