-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fc: Initial EPSM implementation. (#1274)
More information: https://www.nesdev.org/wiki/Expansion_Port_Sound_Module While this is a homebrew device, it has already shipped to consumers in this manner. TODO (I'm not sure how to fix these issues myself): - [x] Switching from EPSM back to another peripheral (None or Family Keyboard) unloads not just the additional EPSM-provided audio streams, but seems to break *all* audio from the NES core. - [x] NES 2.0 cartridges can declare that they wish to have the EPSM peripheral installed by default (Extended Console Type 0x04), but I'm not sure how to hook this nicely from the pak data to the expansion port node. - [ ] Missing serialization, but this affects the Family Keyboard to some limited extent already, so... Not urgent at all. It has shipped to consumers, but the amount of software using it is still lacking. --------- Co-authored-by: Luke Usher <[email protected]>
- Loading branch information
1 parent
020410e
commit c27f369
Showing
12 changed files
with
178 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
EPSM::EPSM(Node::Port parent) : ymf288(interface) { | ||
node = parent->append<Node::Peripheral>("EPSM"); | ||
ioAddress = 0x401c; | ||
|
||
streamFM = node->append<Node::Audio::Stream>("EPSM FM"); | ||
streamFM->setChannels(2); | ||
streamFM->setFrequency(ymf288.sample_rate(8_MHz)); | ||
streamFM->addHighPassFilter( 20.0, 1); | ||
streamFM->addLowPassFilter(2840.0, 1); | ||
|
||
streamSSG = node->append<Node::Audio::Stream>("EPSM SSG"); | ||
streamSSG->setChannels(1); | ||
streamSSG->setFrequency(ymf288.sample_rate(8_MHz)); | ||
|
||
ymf288.reset(); | ||
clocksPerSample = clocksPerSample = 8_MHz / ymf288.sample_rate(8_MHz); | ||
Thread::create(8_MHz, {&EPSM::main, this}); | ||
} | ||
|
||
EPSM::~EPSM() { | ||
node->remove(streamFM); | ||
node->remove(streamSSG); | ||
node.reset(); | ||
Thread::destroy(); | ||
} | ||
|
||
auto EPSM::main() -> void { | ||
ymfm::ymf288::output_data output; | ||
ymf288.generate(&output); | ||
|
||
streamFM->frame(output.data[0] / 32768.0, output.data[1] / 32768.0); | ||
streamSSG->frame(output.data[2] / (4.0 * 32768.0)); | ||
|
||
step(clocksPerSample); | ||
} | ||
|
||
auto EPSM::step(u32 clocks) -> void { | ||
if(busyCyclesRemaining) { | ||
busyCyclesRemaining -= clocks; | ||
if(busyCyclesRemaining <= 0) { | ||
busyCyclesRemaining = 0; | ||
} | ||
} | ||
|
||
for(u32 timer : range(2)) { | ||
if(timerCyclesRemaining[timer]) { | ||
timerCyclesRemaining[timer] -= clocks; | ||
if(timerCyclesRemaining[timer] <= 0) { | ||
timerCyclesRemaining[timer] = 0; | ||
interface.timerCallback(timer); | ||
} | ||
} | ||
} | ||
|
||
Thread::step(clocks); | ||
Thread::synchronize(); | ||
} | ||
|
||
auto EPSM::read1() -> n1 { | ||
return 0; | ||
} | ||
|
||
auto EPSM::read2() -> n5 { | ||
return 0b00000; | ||
} | ||
|
||
auto EPSM::write(n8 data) -> void { | ||
if (data.bit(1) != latch) { | ||
latch = data.bit(1); | ||
if (latch) { | ||
ymAddress.bit(0,1) = data.bit(2,3); | ||
ymData.bit(4,7) = data.bit(4,7); | ||
} else { | ||
ymData.bit(0,3) = data.bit(4,7); | ||
ymf288.write(ymAddress, ymData); | ||
} | ||
} | ||
} | ||
|
||
auto EPSM::writeIO(n16 address, n8 data) -> void { | ||
if ((address & 0xFFFC) != ioAddress) return; | ||
ymf288.write(address, data); | ||
} | ||
|
||
void EPSM::Interface::ymfm_update_irq(bool asserted) { | ||
// TODO: Handle conflicts between cartridge and EPSM IRQ. | ||
cpu.irqLine(asserted ? 1 : 0); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
struct EPSM : Expansion, Thread { | ||
Node::Audio::Stream streamFM; | ||
Node::Audio::Stream streamSSG; | ||
|
||
EPSM(Node::Port); | ||
~EPSM(); | ||
|
||
auto main() -> void; | ||
auto step(u32 clocks) -> void; | ||
|
||
auto read1() -> n1 override; | ||
auto read2() -> n5 override; | ||
auto write(n8 data) -> void override; | ||
|
||
auto writeIO(n16 address, n8 data) -> void override; | ||
|
||
private: | ||
class Interface : public ymfm::ymfm_interface { | ||
public: | ||
Interface(EPSM& self) : self{self} {} | ||
|
||
void timerCallback(uint32_t timer) { m_engine->engine_timer_expired(timer); } | ||
void ymfm_set_busy_end(uint32_t clocks) override { self.busyCyclesRemaining = clocks; } | ||
bool ymfm_is_busy() override { return self.busyCyclesRemaining > 0; } | ||
void ymfm_update_irq(bool asserted) override; | ||
|
||
void ymfm_set_timer(uint32_t timer, int32_t duration) override { | ||
if (duration < 0) { | ||
self.timerCyclesRemaining[timer] = 0; | ||
} else { | ||
self.timerCyclesRemaining[timer] = duration; | ||
} | ||
} | ||
|
||
EPSM& self; | ||
} interface{*this}; | ||
|
||
n1 latch; | ||
n2 ymAddress; | ||
n8 ymData; | ||
n16 ioAddress; | ||
|
||
ymfm::ymf288 ymf288; | ||
s32 busyCyclesRemaining = 0; | ||
s32 timerCyclesRemaining[2] = {0, 0}; | ||
s32 clocksPerSample = 0; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters