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()) {
332  clk.setPeriodicState(pin.getTotalDuration(),
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()) {
352  clk.setPeriodicState(pin.getTotalDuration(),
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 }
428 
429 } // namespace openmsx
openmsx::I8251::setParityBit
void setParityBit(bool enable, ParityBit parity) override
Definition: I8251.cc:294
openmsx::MSXDevice
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition: MSXDevice.hh:32
openmsx::ClockPin::setPeriodicState
void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time)
Definition: ClockPin.cc:36
openmsx::I8251::peekIO
byte peekIO(word port, EmuTime::param time) const
Definition: I8251.cc:95
openmsx::MSXMidi::setParityBit
void setParityBit(bool enable, ParityBit parity) override
Definition: MSXMidi.cc:388
openmsx::MidiInConnector
Definition: MidiInConnector.hh:13
openmsx::I8254::getOutputPin
ClockPin & getOutputPin(unsigned cntr)
Definition: I8254.cc:174
openmsx::MSXMidi
Definition: MSXMidi.hh:14
serialize.hh
openmsx::I8251::setDataBits
void setDataBits(DataBits bits) override
Definition: I8251.hh:44
openmsx::EmuDuration
Definition: EmuDuration.hh:19
openmsx::I8251::setStopBits
void setStopBits(StopBits bits) override
Definition: I8251.hh:45
openmsx::DeviceConfig
Definition: DeviceConfig.hh:20
openmsx::MSXCPUInterface::register_IO_Out
void register_IO_Out(byte port, MSXDevice *device)
Devices can register their Out ports.
Definition: MSXCPUInterface.cc:350
openmsx::I8251::readIO
byte readIO(word port, EmuTime::param time)
Definition: I8251.cc:79
openmsx::MSXMidi::readIO
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
openmsx::DeviceConfig::findChild
const XMLElement * findChild(std::string_view name) const
Definition: DeviceConfig.cc:61
openmsx::DISABLED_VALUE
constexpr byte DISABLED_VALUE
Definition: MSXMidi.cc:14
openmsx::MSXMidi::setStopBits
void setStopBits(StopBits bits) override
Definition: MSXMidi.cc:383
MSXException.hh
openmsx::I8254::peekIO
byte peekIO(word port, EmuTime::param time) const
Definition: I8254.cc:107
openmsx::MSXException
Definition: MSXException.hh:10
MidiInDevice.hh
openmsx::MSXDevice::getCPUInterface
MSXCPUInterface & getCPUInterface() const
Definition: MSXDevice.cc:139
openmsx::MSXMidi::ready
bool ready() override
Definition: MSXMidi.cc:368
openmsx::I8251::reset
void reset(EmuTime::param time)
Definition: I8251.cc:57
openmsx::REGISTER_MSXDEVICE
REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy")
openmsx::ClockPin
ClockPin
Definition: ClockPin.cc:149
OUTER
#define OUTER(type, member)
Definition: outer.hh:41
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::Keys::getName
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:740
openmsx::I8254::getClockPin
ClockPin & getClockPin(unsigned cntr)
Definition: I8254.cc:168
openmsx::MSXDevice::getCurrentTime
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:131
openmsx::MSXMidi::recvByte
void recvByte(byte value, EmuTime::param time) override
Definition: MSXMidi.cc:393
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::I8251::writeIO
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8251.cc:108
openmsx::MSXMidi::writeIO
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
openmsx::SerialDataInterface::StopBits
StopBits
Definition: SerialDataInterface.hh:15
openmsx::MSXCPUInterface::register_IO_In
void register_IO_In(byte port, MSXDevice *device)
Devices can register their In ports.
Definition: MSXCPUInterface.cc:338
openmsx::IntHelper::reset
void reset()
Reset the interrupt request on the bus.
Definition: IRQHelper.hh:96
openmsx::ClockPin::generateEdgeSignals
void generateEdgeSignals(bool wanted, EmuTime::param time)
Definition: ClockPin.cc:97
openmsx::MSXCPUInterface::unregister_IO_In
void unregister_IO_In(byte port, MSXDevice *device)
Definition: MSXCPUInterface.cc:344
openmsx::SerialDataInterface::DataBits
DataBits
Definition: SerialDataInterface.hh:12
outer.hh
openmsx::MSXMidi::reset
void reset(EmuTime::param time) override
This method is called on reset.
Definition: MSXMidi.cc:63
openmsx::MSXMidi
MSXMidi
Definition: MSXMidi.cc:426
openmsx::I8251::isRecvEnabled
bool isRecvEnabled() const
Definition: I8251.cc:318
openmsx::MSXMidi::peekIO
byte peekIO(word port, EmuTime::param time) const override
Read a byte from a given IO port.
Definition: MSXMidi.cc:102
openmsx::IntHelper::set
void set()
Set the interrupt request on the bus.
Definition: IRQHelper.hh:87
openmsx::word
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
openmsx::MSXMidi::acceptsData
bool acceptsData() override
Definition: MSXMidi.cc:373
MSXCPUInterface.hh
MSXMidi.hh
openmsx::SerialDataInterface::ParityBit
ParityBit
Definition: SerialDataInterface.hh:19
openmsx::MSXCPUInterface::unregister_IO_Out
void unregister_IO_Out(byte port, MSXDevice *device)
Definition: MSXCPUInterface.cc:356
unreachable.hh
openmsx::MSXMidi::~MSXMidi
~MSXMidi() override
Definition: MSXMidi.cc:55
openmsx::ClockPin::setState
void setState(bool status, EmuTime::param time)
Definition: ClockPin.cc:16
openmsx::I8254::writeIO
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8254.cc:120
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::MSXMidi::setDataBits
void setDataBits(DataBits bits) override
Definition: MSXMidi.cc:378
openmsx::MSXMidi::serialize
void serialize(Archive &ar, unsigned version)
Definition: MSXMidi.cc:400
openmsx::I8251::recvByte
void recvByte(byte value, EmuTime::param time) override
Definition: I8251.cc:300
openmsx::MSXMidi::MSXMidi
MSXMidi(const DeviceConfig &config)
Definition: MSXMidi.cc:16
openmsx::I8254::readIO
byte readIO(word port, EmuTime::param time)
Definition: I8254.cc:94
openmsx::I8251::isRecvReady
bool isRecvReady() const
Definition: I8251.hh:40
openmsx::LIMITED_RANGE_VALUE
constexpr byte LIMITED_RANGE_VALUE
Definition: MSXMidi.cc:13