1 #pragma once
2 #include "stdafx.h"
3 #include "SquareChannel.h"
4 #include "BaseExpansionAudio.h"
5 #include "CPU.h"
6 #include "Console.h"
7 #include "MemoryManager.h"
8 
9 class MMC5Square : public SquareChannel
10 {
11 	int8_t _currentOutput;
12 
13 private:
InitializeSweep(uint8_t regValue)14 	virtual void InitializeSweep(uint8_t regValue) override
15 	{
16 		//"$5001 has no effect. The MMC5 pulse channels will not sweep, as they have no sweep unit."
17 	}
18 
19 public:
MMC5Square(shared_ptr<Console> console)20 	MMC5Square(shared_ptr<Console> console) : SquareChannel(AudioChannel::MMC5, console, nullptr, false)
21 	{
22 		_currentOutput = 0;
23 		_isMmc5Square = true;
24 		Reset(false);
25 	}
26 
GetOutput()27 	int8_t GetOutput()
28 	{
29 		return _currentOutput;
30 	}
31 
RunChannel()32 	void RunChannel()
33 	{
34 		if(_timer == 0) {
35 			_dutyPos = (_dutyPos - 1) & 0x07;
36 			//"Frequency values less than 8 do not silence the MMC5 pulse channels; they can output ultrasonic frequencies."
37 			_currentOutput = _dutySequences[_duty][_dutyPos] * GetVolume();
38 			_timer = _period;
39 		} else {
40 			_timer--;
41 		}
42 	}
43 };
44 
45 class MMC5Audio : public BaseExpansionAudio
46 {
47 private:
48 	MMC5Square _square1;
49 	MMC5Square _square2;
50 	int16_t _audioCounter;
51 	int16_t _lastOutput;
52 
53 	bool _pcmReadMode;
54 	bool _pcmIrqEnabled;
55 	uint8_t _pcmOutput;
56 
57 protected:
StreamState(bool saving)58 	void StreamState(bool saving) override
59 	{
60 		BaseExpansionAudio::StreamState(saving);
61 
62 		SnapshotInfo square1{ &_square1 };
63 		SnapshotInfo square2{ &_square2 };
64 		Stream(square1, square2, _audioCounter, _lastOutput, _pcmReadMode, _pcmIrqEnabled, _pcmOutput);
65 	}
66 
ClockAudio()67 	void ClockAudio() override
68 	{
69 		_audioCounter--;
70 		_square1.RunChannel();
71 		_square2.RunChannel();
72 		if(_audioCounter <= 0) {
73 			//~240hz envelope/length counter
74 			_audioCounter = _console->GetCpu()->GetClockRate(_console->GetModel()) / 240;
75 			_square1.TickLengthCounter();
76 			_square1.TickEnvelope();
77 			_square2.TickLengthCounter();
78 			_square2.TickEnvelope();
79 		}
80 
81 		//"The sound output of the square channels are equivalent in volume to the corresponding APU channels"
82 		//"The polarity of all MMC5 channels is reversed compared to the APU."
83 		int16_t summedOutput = -(_square1.GetOutput() + _square2.GetOutput() + _pcmOutput);
84 		if(summedOutput != _lastOutput) {
85 			_console->GetApu()->AddExpansionAudioDelta(AudioChannel::MMC5, summedOutput - _lastOutput);
86 			_lastOutput = summedOutput;
87 		}
88 
89 		_square1.ReloadCounter();
90 		_square2.ReloadCounter();
91 	}
92 
93 public:
MMC5Audio(shared_ptr<Console> console)94 	MMC5Audio(shared_ptr<Console> console) : BaseExpansionAudio(console), _square1(console), _square2(console)
95 	{
96 		_audioCounter = 0;
97 		_lastOutput = 0;
98 		_pcmReadMode = false;
99 		_pcmIrqEnabled = false;
100 		_pcmOutput = 0;
101 	}
102 
ReadRegister(uint16_t addr)103 	uint8_t ReadRegister(uint16_t addr)
104 	{
105 		switch(addr) {
106 			case 0x5010:
107 				//TODO: PCM IRQ
108 				return 0;
109 
110 			case 0x5015:
111 				uint8_t status = 0;
112 				status |= _square1.GetStatus() ? 0x01 : 0x00;
113 				status |= _square2.GetStatus() ? 0x02 : 0x00;
114 				return status;
115 		}
116 
117 		return _console->GetMemoryManager()->GetOpenBus();
118 	}
119 
WriteRegister(uint16_t addr,uint8_t value)120 	void WriteRegister(uint16_t addr, uint8_t value)
121 	{
122 		switch(addr) {
123 			case 0x5000: case 0x5001: case 0x5002: case 0x5003:
124 				_square1.WriteRAM(addr, value);
125 				break;
126 
127 			case 0x5004: case 0x5005: case 0x5006: case 0x5007:
128 				_square2.WriteRAM(addr, value);
129 				break;
130 
131 			case 0x5010:
132 				//TODO: Read mode & PCM IRQs are not implemented
133 				_pcmReadMode = (value & 0x01) == 0x01;
134 				_pcmIrqEnabled = (value & 0x80) == 0x80;
135 				break;
136 
137 			case 0x5011:
138 				if(!_pcmReadMode) {
139 					if(value != 0) {
140 						_pcmOutput = value;
141 					}
142 				}
143 				break;
144 
145 			case 0x5015:
146 				_square1.SetEnabled((value & 0x01) == 0x01);
147 				_square2.SetEnabled((value & 0x02) == 0x02);
148 				break;
149 		}
150 	}
151 };