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