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