openMSX
MSXMidi.cc
Go to the documentation of this file.
1#include "MSXMidi.hh"
2#include "MidiInDevice.hh"
3#include "MSXCPUInterface.hh"
4#include "MSXException.hh"
5#include "narrow.hh"
6#include "outer.hh"
7#include "serialize.hh"
8#include "unreachable.hh"
9#include "xrange.hh"
10#include <cassert>
11
12namespace openmsx {
13
14// Documented in MSX-Datapack Vol. 3, section 4 (MSX-MIDI), from page 634
15static constexpr byte LIMITED_RANGE_VALUE = 0x01; // b0 = "E8" => determines port range
16static constexpr byte DISABLED_VALUE = 0x80; // b7 = EN
17
19 : MSXDevice(config)
20 , MidiInConnector(MSXDevice::getPluggingController(), "MSX-MIDI-in")
21 , timerIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQtimer")
22 , rxrdyIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQrxrdy")
23 , isExternalMSXMIDI(config.findChild("external") != nullptr)
24 , isEnabled(!isExternalMSXMIDI)
25 , outConnector(MSXDevice::getPluggingController(), "MSX-MIDI-out")
26 , i8251(getScheduler(), interface, getCurrentTime())
27 , i8254(getScheduler(), &cntr0, nullptr, &cntr2, getCurrentTime())
28{
29 EmuDuration total(1.0 / 4e6); // 4MHz
30 EmuDuration hi (1.0 / 8e6); // 8MHz half clock period
31 EmuTime::param time = getCurrentTime();
32 i8254.getClockPin(0).setPeriodicState(total, hi, time);
33 i8254.getClockPin(1).setState(false, time);
34 i8254.getClockPin(2).setPeriodicState(total, hi, time);
35 i8254.getOutputPin(2).generateEdgeSignals(true, time);
36 reset(time);
37
38 if (isExternalMSXMIDI) {
39 // Ports are dynamically registered.
40 if (config.findChild("io")) {
41 throw MSXException(
42 "Bad MSX-MIDI configuration, when using "
43 "'external', you cannot specify I/O ports!");
44 }
45 // Out-port 0xE2 is always enabled.
46 getCPUInterface().register_IO_Out(0xE2, this);
47 // Not enabled, so no other ports need to be registered yet.
48 assert(!isEnabled);
49 } else {
50 // Ports are statically registered (via the config file).
51 }
52}
53
55{
56 if (isExternalMSXMIDI) {
57 registerIOports(DISABLED_VALUE | LIMITED_RANGE_VALUE); // disabled, unregisters ports
59 }
60}
61
62void MSXMidi::reset(EmuTime::param time)
63{
64 timerIRQlatch = false;
65 timerIRQenabled = false;
66 timerIRQ.reset();
67 rxrdyIRQlatch = false;
68 rxrdyIRQenabled = false;
69 rxrdyIRQ.reset();
70
71 if (isExternalMSXMIDI) {
72 registerIOports(DISABLED_VALUE | LIMITED_RANGE_VALUE); // also resets state
73 }
74 i8251.reset(time);
75}
76
77byte MSXMidi::readIO(word port, EmuTime::param time)
78{
79 // If not enabled then no ports should have been registered.
80 assert(isEnabled);
81
82 // This handles both ports in range 0xE0-0xE1 and 0xE8-0xEF
83 // Depending on the value written to 0xE2 they are active or not
84 switch (port & 7) {
85 case 0: // UART data register
86 case 1: // UART status register
87 return i8251.readIO(port & 1, time);
88 case 2: // timer interrupt flag off
89 case 3: // no function
90 return 0xFF;
91 case 4: // counter 0 data port
92 case 5: // counter 1 data port
93 case 6: // counter 2 data port
94 case 7: // timer command register
95 return i8254.readIO(port & 3, time);
96 default:
98 }
99}
100
101byte MSXMidi::peekIO(word port, EmuTime::param time) const
102{
103 // If not enabled then no ports should have been registered.
104 assert(isEnabled);
105
106 // This handles both ports in range 0xE0-0xE1 and 0xE8-0xEF
107 // Depending on the value written to 0xE2 they are active or not
108 switch (port & 7) {
109 case 0: // UART data register
110 case 1: // UART status register
111 return i8251.peekIO(port & 1, time);
112 case 2: // timer interrupt flag off
113 case 3: // no function
114 return 0xFF;
115 case 4: // counter 0 data port
116 case 5: // counter 1 data port
117 case 6: // counter 2 data port
118 case 7: // timer command register
119 return i8254.peekIO(port & 3, time);
120 default:
122 }
123}
124
125void MSXMidi::writeIO(word port, byte value, EmuTime::param time)
126{
127 if (isExternalMSXMIDI && ((port & 0xFF) == 0xE2)) {
128 // control register
129 registerIOports(value);
130 return;
131 }
132 assert(isEnabled);
133
134 // This handles both ports in range 0xE0-0xE1 and 0xE8-0xEF
135 switch (port & 7) {
136 case 0: // UART data register
137 case 1: // UART command register
138 i8251.writeIO(port & 1, value, time);
139 break;
140 case 2: // timer interrupt flag off
141 setTimerIRQ(false, time);
142 break;
143 case 3: // no function
144 break;
145 case 4: // counter 0 data port
146 case 5: // counter 1 data port
147 case 6: // counter 2 data port
148 case 7: // timer command register
149 i8254.writeIO(port & 3, value, time);
150 break;
151 }
152}
153
154void MSXMidi::registerIOports(byte value)
155{
156 assert(isExternalMSXMIDI);
157 bool newIsEnabled = (value & DISABLED_VALUE) == 0;
158 bool newIsLimited = (value & LIMITED_RANGE_VALUE) != 0;
159
160 auto& cpuInterface = getCPUInterface();
161 if (newIsEnabled != isEnabled) {
162 // Enable/disabled status changes, possibly limited status
163 // changes as well but that doesn't matter, we anyway need
164 // to (un)register the full port range.
165 if (newIsEnabled) {
166 // disabled -> enabled
167 if (newIsLimited) {
168 cpuInterface.register_IO_InOut_range(0xE0, 2, this);
169 } else {
170 cpuInterface.register_IO_InOut_range(0xE8, 8, this);
171 }
172 } else {
173 // enabled -> disabled
174 if (isLimitedTo8251) { // note: old isLimited status
175 cpuInterface.unregister_IO_InOut_range(0xE0, 2, this);
176 } else {
177 cpuInterface.unregister_IO_InOut_range(0xE8, 8, this);
178 }
179 }
180
181 } else if (isEnabled && (newIsLimited != isLimitedTo8251)) {
182 // Remains enabled, and only 'isLimited' status changes.
183 // Need to switch between the low/high range.
184 if (newIsLimited) {
185 // Switch high->low range.
186 cpuInterface.unregister_IO_InOut_range(0xE8, 8, this);
187 cpuInterface.register_IO_InOut_range (0xE0, 2, this);
188 } else {
189 // Switch low->high range.
190 cpuInterface.unregister_IO_InOut_range(0xE0, 2, this);
191 cpuInterface.register_IO_InOut_range (0xE8, 8, this);
192 }
193 }
194
195 isEnabled = newIsEnabled;
196 isLimitedTo8251 = newIsLimited;
197}
198
199void MSXMidi::setTimerIRQ(bool status, EmuTime::param time)
200{
201 if (timerIRQlatch != status) {
202 timerIRQlatch = status;
203 if (timerIRQenabled) {
204 timerIRQ.set(timerIRQlatch);
205 }
206 updateEdgeEvents(time);
207 }
208}
209
210void MSXMidi::enableTimerIRQ(bool enabled, EmuTime::param time)
211{
212 if (timerIRQenabled != enabled) {
213 timerIRQenabled = enabled;
214 if (timerIRQlatch) {
215 timerIRQ.set(timerIRQenabled);
216 }
217 updateEdgeEvents(time);
218 }
219}
220
221void MSXMidi::updateEdgeEvents(EmuTime::param time)
222{
223 bool wantEdges = timerIRQenabled && !timerIRQlatch;
224 i8254.getOutputPin(2).generateEdgeSignals(wantEdges, time);
225}
226
227void MSXMidi::setRxRDYIRQ(bool status)
228{
229 if (rxrdyIRQlatch != status) {
230 rxrdyIRQlatch = status;
231 if (rxrdyIRQenabled) {
232 rxrdyIRQ.set(rxrdyIRQlatch);
233 }
234 }
235}
236
237void MSXMidi::enableRxRDYIRQ(bool enabled)
238{
239 if (rxrdyIRQenabled != enabled) {
240 rxrdyIRQenabled = enabled;
241 if (!rxrdyIRQenabled && rxrdyIRQlatch) {
242 rxrdyIRQ.reset();
243 }
244 }
245}
246
247
248// I8251Interface (pass calls from I8251 to outConnector)
249
250void MSXMidi::Interface::setRxRDY(bool status, EmuTime::param /*time*/)
251{
252 auto& midi = OUTER(MSXMidi, interface);
253 midi.setRxRDYIRQ(status);
254}
255
256void MSXMidi::Interface::setDTR(bool status, EmuTime::param time)
257{
258 auto& midi = OUTER(MSXMidi, interface);
259 midi.enableTimerIRQ(status, time);
260}
261
262void MSXMidi::Interface::setRTS(bool status, EmuTime::param /*time*/)
263{
264 auto& midi = OUTER(MSXMidi, interface);
265 midi.enableRxRDYIRQ(status);
266}
267
268bool MSXMidi::Interface::getDSR(EmuTime::param /*time*/)
269{
270 const auto& midi = OUTER(MSXMidi, interface);
271 return midi.timerIRQ.getState();
272}
273
274bool MSXMidi::Interface::getCTS(EmuTime::param /*time*/)
275{
276 return true;
277}
278
279void MSXMidi::Interface::setDataBits(DataBits bits)
280{
281 auto& midi = OUTER(MSXMidi, interface);
282 midi.outConnector.setDataBits(bits);
283}
284
285void MSXMidi::Interface::setStopBits(StopBits bits)
286{
287 auto& midi = OUTER(MSXMidi, interface);
288 midi.outConnector.setStopBits(bits);
289}
290
291void MSXMidi::Interface::setParityBit(bool enable, Parity parity)
292{
293 auto& midi = OUTER(MSXMidi, interface);
294 midi.outConnector.setParityBit(enable, parity);
295}
296
297void MSXMidi::Interface::recvByte(byte value, EmuTime::param time)
298{
299 auto& midi = OUTER(MSXMidi, interface);
300 midi.outConnector.recvByte(value, time);
301}
302
303void MSXMidi::Interface::signal(EmuTime::param time)
304{
305 const auto& midi = OUTER(MSXMidi, interface);
306 midi.getPluggedMidiInDev().signal(time);
307}
308
309
310// Counter 0 output
311
312void MSXMidi::Counter0::signal(ClockPin& pin, EmuTime::param time)
313{
314 auto& midi = OUTER(MSXMidi, cntr0);
315 ClockPin& clk = midi.i8251.getClockPin();
316 if (pin.isPeriodic()) {
317 clk.setPeriodicState(pin.getTotalDuration(),
318 pin.getHighDuration(), time);
319 } else {
320 clk.setState(pin.getState(time), time);
321 }
322}
323
324void MSXMidi::Counter0::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/)
325{
327}
328
329
330// Counter 2 output
331
332void MSXMidi::Counter2::signal(ClockPin& pin, EmuTime::param time)
333{
334 auto& midi = OUTER(MSXMidi, cntr2);
335 ClockPin& clk = midi.i8254.getClockPin(1);
336 if (pin.isPeriodic()) {
337 clk.setPeriodicState(pin.getTotalDuration(),
338 pin.getHighDuration(), time);
339 } else {
340 clk.setState(pin.getState(time), time);
341 }
342}
343
344void MSXMidi::Counter2::signalPosEdge(ClockPin& /*pin*/, EmuTime::param time)
345{
346 auto& midi = OUTER(MSXMidi, cntr2);
347 midi.setTimerIRQ(true, time);
348}
349
350
351// MidiInConnector
352
354{
355 return i8251.isRecvReady();
356}
357
359{
360 return i8251.isRecvEnabled();
361}
362
364{
365 i8251.setDataBits(bits);
366}
367
369{
370 i8251.setStopBits(bits);
371}
372
373void MSXMidi::setParityBit(bool enable, Parity parity)
374{
375 i8251.setParityBit(enable, parity);
376}
377
378void MSXMidi::recvByte(byte value, EmuTime::param time)
379{
380 i8251.recvByte(value, time);
381}
382
383
384template<typename Archive>
385void MSXMidi::serialize(Archive& ar, unsigned version)
386{
387 ar.template serializeBase<MSXDevice>(*this);
388
389 ar.template serializeBase<MidiInConnector>(*this);
390 ar.serialize("outConnector", outConnector,
391 "timerIRQ", timerIRQ,
392 "rxrdyIRQ", rxrdyIRQ,
393 "timerIRQlatch", timerIRQlatch,
394 "timerIRQenabled", timerIRQenabled,
395 "rxrdyIRQlatch", rxrdyIRQlatch,
396 "rxrdyIRQenabled", rxrdyIRQenabled,
397 "I8251", i8251,
398 "I8254", i8254);
399 if (ar.versionAtLeast(version, 2)) {
400 bool newIsEnabled = isEnabled; // copy for saver
401 bool newIsLimitedTo8251 = isLimitedTo8251; // copy for saver
402 ar.serialize("isEnabled", newIsEnabled,
403 "isLimitedTo8251", newIsLimitedTo8251);
404 if constexpr (Archive::IS_LOADER) {
405 if (isExternalMSXMIDI) {
406 registerIOports((newIsEnabled ? 0x00 : DISABLED_VALUE) | (newIsLimitedTo8251 ? LIMITED_RANGE_VALUE : 0x00));
407 }
408 }
409 }
410
411 // don't serialize: cntr0, cntr2, interface
412}
415
416} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
void generateEdgeSignals(bool wanted, EmuTime::param time)
Definition ClockPin.cc:94
void setState(bool status, EmuTime::param time)
Definition ClockPin.cc:13
void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time)
Definition ClockPin.cc:33
const XMLElement * findChild(std::string_view name) const
byte peekIO(word port, EmuTime::param time) const
Definition I8251.cc:86
void setStopBits(StopBits bits) override
Definition I8251.hh:46
bool isRecvReady() const
Definition I8251.hh:41
void recvByte(byte value, EmuTime::param time) override
Definition I8251.cc:264
void writeIO(word port, byte value, EmuTime::param time)
Definition I8251.cc:96
void reset(EmuTime::param time)
Definition I8251.cc:55
void setDataBits(DataBits bits) override
Definition I8251.hh:45
byte readIO(word port, EmuTime::param time)
Definition I8251.cc:77
void setParityBit(bool enable, Parity parity) override
Definition I8251.cc:258
bool isRecvEnabled() const
Definition I8251.cc:282
uint8_t peekIO(uint16_t port, EmuTime::param time) const
Definition I8254.cc:54
void writeIO(uint16_t port, uint8_t value, EmuTime::param time)
Definition I8254.cc:67
uint8_t readIO(uint16_t port, EmuTime::param time)
Definition I8254.cc:41
ClockPin & getClockPin(unsigned cntr)
Definition I8254.cc:115
ClockPin & getOutputPin(unsigned cntr)
Definition I8254.cc:121
void set()
Set the interrupt request on the bus.
Definition IRQHelper.hh:76
void reset()
Reset the interrupt request on the bus.
Definition IRQHelper.hh:85
void register_IO_Out(byte port, MSXDevice *device)
Devices can register their Out ports.
void unregister_IO_Out(byte port, MSXDevice *device)
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition MSXDevice.hh:36
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
MSXCPUInterface & getCPUInterface() const
Definition MSXDevice.cc:133
bool acceptsData() override
Definition MSXMidi.cc:358
MSXMidi(const DeviceConfig &config)
Definition MSXMidi.cc:18
void recvByte(byte value, EmuTime::param time) override
Definition MSXMidi.cc:378
void serialize(Archive &ar, unsigned version)
Definition MSXMidi.cc:385
byte readIO(word port, EmuTime::param time) override
Read a byte from an IO port at a certain time from this device.
Definition MSXMidi.cc:77
void setDataBits(DataBits bits) override
Definition MSXMidi.cc:363
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition MSXMidi.cc:101
bool ready() override
Definition MSXMidi.cc:353
void setStopBits(StopBits bits) override
Definition MSXMidi.cc:368
void setParityBit(bool enable, Parity parity) override
Definition MSXMidi.cc:373
void reset(EmuTime::param time) override
This method is called on reset.
Definition MSXMidi.cc:62
~MSXMidi() override
Definition MSXMidi.cc:54
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
Definition MSXMidi.cc:125
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
#define OUTER(type, member)
Definition outer.hh:42
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define UNREACHABLE