diff --git a/src/app/emu/audio/apu.cc b/src/app/emu/audio/apu.cc index 7b48adc8..32e26ba6 100644 --- a/src/app/emu/audio/apu.cc +++ b/src/app/emu/audio/apu.cc @@ -17,6 +17,14 @@ namespace app { namespace emu { namespace audio { +static const uint8_t bootRom[0x40] = { + 0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa, + 0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19, + 0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb, + 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e, + 0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4, + 0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff}; + void Apu::Init() { // Set the clock frequency clock_.SetFrequency(kApuClockSpeed); @@ -24,8 +32,9 @@ void Apu::Init() { void Apu::Reset() { clock_.ResetAccumulatedTime(); - spc700_.Reset(); + spc700_.Reset(true); dsp_.Reset(); + romReadable = true; } void Apu::Update() { @@ -39,31 +48,147 @@ void Apu::Update() { } } -void Apu::Notify(uint32_t address, uint16_t data) { - if (address < 0x2140 || address > 0x2143) { - return; - } - auto offset = address - 0x2140; - spc700_.write(offset, data); +int Apu::RunCycles(uint32_t wanted_cycles) { + int run_cycles = 0; + uint32_t start_cycles = cycles_; - // HACK - This is a temporary solution to get the Apu to play audio - ports_[address - 0x2140] = data; - switch (address) { - case 0x2140: - SignalReady(); - break; - case 0x2141: - // TODO: Handle data byte transfer here - break; - case 0x2142: - // TODO: Handle the setup of destination address - break; - case 0x2143: - // TODO: Handle additional communication/commands - break; + while (run_cycles < wanted_cycles) { + spc700_.RunOpcode(); + + run_cycles += (uint32_t)cycles_ - start_cycles; + start_cycles = cycles_; } + return run_cycles; } +void Apu::Cycle() { + if ((cycles_ & 0x1f) == 0) { + // every 32 cycles + dsp_.Cycle(); + } + + // handle timers + for (int i = 0; i < 3; i++) { + if (timer[i].cycles == 0) { + timer[i].cycles = i == 2 ? 16 : 128; + if (timer[i].enabled) { + timer[i].divider++; + if (timer[i].divider == timer[i].target) { + timer[i].divider = 0; + timer[i].counter++; + timer[i].counter &= 0xf; + } + } + } + timer[i].cycles--; + } + + cycles_++; +} + +uint8_t Apu::Read(uint16_t adr) { + switch (adr) { + case 0xf0: + case 0xf1: + case 0xfa: + case 0xfb: + case 0xfc: { + return 0; + } + case 0xf2: { + return dspAdr; + } + case 0xf3: { + return dsp_.Read(dspAdr & 0x7f); + } + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + case 0xf8: + case 0xf9: { + return inPorts[adr - 0xf4]; + } + case 0xfd: + case 0xfe: + case 0xff: { + uint8_t ret = timer[adr - 0xfd].counter; + timer[adr - 0xfd].counter = 0; + return ret; + } + } + if (romReadable && adr >= 0xffc0) { + return bootRom[adr - 0xffc0]; + } + return aram_.read(adr); +} + +void Apu::Write(uint16_t adr, uint8_t val) { + switch (adr) { + case 0xf0: { + break; // test register + } + case 0xf1: { + for (int i = 0; i < 3; i++) { + if (!timer[i].enabled && (val & (1 << i))) { + timer[i].divider = 0; + timer[i].counter = 0; + } + timer[i].enabled = val & (1 << i); + } + if (val & 0x10) { + inPorts[0] = 0; + inPorts[1] = 0; + } + if (val & 0x20) { + inPorts[2] = 0; + inPorts[3] = 0; + } + romReadable = val & 0x80; + break; + } + case 0xf2: { + dspAdr = val; + break; + } + case 0xf3: { + if (dspAdr < 0x80) dsp_.Write(dspAdr, val); + break; + } + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: { + outPorts[adr - 0xf4] = val; + break; + } + case 0xf8: + case 0xf9: { + inPorts[adr - 0xf4] = val; + break; + } + case 0xfa: + case 0xfb: + case 0xfc: { + timer[adr - 0xfa].target = val; + break; + } + } + aram_.write(adr, val); +} + +uint8_t Apu::SpcRead(uint16_t adr) { + Cycle(); + return Read(adr); +} + +void Apu::SpcWrite(uint16_t adr, uint8_t val) { + Cycle(); + Write(adr, val); +} + +void Apu::SpcIdle(bool waiting) { Cycle(); } + } // namespace audio } // namespace emu } // namespace app diff --git a/src/app/emu/audio/apu.h b/src/app/emu/audio/apu.h index 7e187ad8..b6f81598 100644 --- a/src/app/emu/audio/apu.h +++ b/src/app/emu/audio/apu.h @@ -21,6 +21,14 @@ const int kApuClockSpeed = 1024000; // 1.024 MHz const int apuSampleRate = 32000; // 32 KHz const int apuClocksPerSample = 64; // 64 clocks per sample +typedef struct Timer { + uint8_t cycles; + uint8_t divider; + uint8_t target; + uint8_t counter; + bool enabled; +} Timer; + /** * @class Apu * @brief The Apu class represents the Audio Processing Unit (APU) of a system. @@ -46,7 +54,7 @@ const int apuClocksPerSample = 64; // 64 clocks per sample * register $F1 can be cleared to unmap the IPL ROM and allow read access to * this RAM. */ -class Apu : public Observer { +class Apu { public: Apu(MemoryImpl &memory, AudioRam &aram, Clock &clock) : aram_(aram), clock_(clock), memory_(memory) {} @@ -54,7 +62,16 @@ class Apu : public Observer { void Init(); void Reset(); void Update(); - void Notify(uint32_t address, uint16_t data) override; + + int RunCycles(uint32_t wanted_cycles); + uint8_t SpcRead(uint16_t address); + void SpcWrite(uint16_t address, uint8_t data); + void SpcIdle(bool waiting); + + void Cycle(); + + uint8_t Read(uint16_t address); + void Write(uint16_t address, uint8_t data); // Called upon a reset void Initialize() { @@ -62,34 +79,13 @@ class Apu : public Observer { dsp_.Reset(); } - // Set Port 0 = $AA and Port 1 = $BB - void SignalReady() { - memory_.WriteByte(0x2140, READY_SIGNAL_0); - memory_.WriteByte(0x2141, READY_SIGNAL_1); - } - - void WriteToPort(uint8_t portNum, uint8_t value) { - ports_[portNum] = value; - switch (portNum) { - case 0: - memory_.WriteByte(0x2140, value); - break; - case 1: - memory_.WriteByte(0x2141, value); - break; - case 2: - memory_.WriteByte(0x2142, value); - break; - case 3: - memory_.WriteByte(0x2143, value); - break; - } - } - void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); } auto dsp() -> Dsp & { return dsp_; } + uint8_t inPorts[6]; // includes 2 bytes of ram + uint8_t outPorts[4]; + private: // Constants for communication static const uint8_t READY_SIGNAL_0 = 0xAA; @@ -98,14 +94,23 @@ class Apu : public Observer { // Port buffers (equivalent to $2140 to $2143 for the main CPU) uint8_t ports_[4] = {0}; + Timer timer[3]; + uint32_t cycles_; + uint8_t dspAdr; + bool romReadable = true; // Member variables to store internal APU state and resources AudioRam &aram_; Clock &clock_; MemoryImpl &memory_; - Dsp dsp_; - Spc700 spc700_{aram_}; + ApuCallbacks callbacks_ = { + [this](uint16_t adr, uint8_t val) { SpcWrite(adr, val); }, + [this](uint16_t adr) { return SpcRead(adr); }, + [this](bool waiting) { SpcIdle(waiting); }, + }; + Dsp dsp_{aram_}; + Spc700 spc700_{aram_, callbacks_}; }; } // namespace audio