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