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
14constexpr byte LIMITED_RANGE_VALUE = 0x01; // b0 = "E8" => determines port range
15constexpr 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
61 }
62}
63
64void 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
79byte 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
103byte 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
127void 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
156void 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
200void 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}
207void 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
215void 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
226void 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
237void MSXMidi::updateEdgeEvents(EmuTime::param time)
238{
239 bool wantEdges = timerIRQenabled && !timerIRQlatch;
240 i8254.getOutputPin(2).generateEdgeSignals(wantEdges, time);
241}
242
243void MSXMidi::setRxRDYIRQ(bool status)
244{
245 if (rxrdyIRQlatch != status) {
246 rxrdyIRQlatch = status;
247 if (rxrdyIRQenabled) {
248 rxrdyIRQ.set(rxrdyIRQlatch);
249 }
250 }
251}
252
253void 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
266void MSXMidi::I8251Interf::setRxRDY(bool status, EmuTime::param /*time*/)
267{
268 auto& midi = OUTER(MSXMidi, interf);
269 midi.setRxRDYIRQ(status);
270}
271
272void MSXMidi::I8251Interf::setDTR(bool status, EmuTime::param time)
273{
274 auto& midi = OUTER(MSXMidi, interf);
275 midi.enableTimerIRQ(status, time);
276}
277
278void MSXMidi::I8251Interf::setRTS(bool status, EmuTime::param /*time*/)
279{
280 auto& midi = OUTER(MSXMidi, interf);
281 midi.enableRxRDYIRQ(status);
282}
283
284bool MSXMidi::I8251Interf::getDSR(EmuTime::param /*time*/)
285{
286 auto& midi = OUTER(MSXMidi, interf);
287 return midi.timerIRQ.getState();
288}
289
290bool MSXMidi::I8251Interf::getCTS(EmuTime::param /*time*/)
291{
292 return true;
293}
294
295void MSXMidi::I8251Interf::setDataBits(DataBits bits)
296{
297 auto& midi = OUTER(MSXMidi, interf);
298 midi.outConnector.setDataBits(bits);
299}
300
301void MSXMidi::I8251Interf::setStopBits(StopBits bits)
302{
303 auto& midi = OUTER(MSXMidi, interf);
304 midi.outConnector.setStopBits(bits);
305}
306
307void MSXMidi::I8251Interf::setParityBit(bool enable, ParityBit parity)
308{
309 auto& midi = OUTER(MSXMidi, interf);
310 midi.outConnector.setParityBit(enable, parity);
311}
312
313void MSXMidi::I8251Interf::recvByte(byte value, EmuTime::param time)
314{
315 auto& midi = OUTER(MSXMidi, interf);
316 midi.outConnector.recvByte(value, time);
317}
318
319void 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
328void 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
340void MSXMidi::Counter0::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/)
341{
343}
344
345
346// Counter 2 output
347
348void 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
360void 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
389void MSXMidi::setParityBit(bool enable, ParityBit parity)
390{
391 i8251.setParityBit(enable, parity);
392}
393
394void MSXMidi::recvByte(byte value, EmuTime::param time)
395{
396 i8251.recvByte(value, time);
397}
398
399
400template<typename Archive>
401void 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 constexpr (Archive::IS_LOADER) {
421 if (isExternalMSXMIDI) {
422 registerIOports((newIsEnabled ? 0x00 : DISABLED_VALUE) | (newIsLimitedTo8251 ? LIMITED_RANGE_VALUE : 0x00));
423 }
424 }
425 }
426
427 // don't serialize: cntr0, cntr2, interf
428}
431
432} // namespace openmsx
void generateEdgeSignals(bool wanted, EmuTime::param time)
Definition: ClockPin.cc:95
void setState(bool status, EmuTime::param time)
Definition: ClockPin.cc:14
void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time)
Definition: ClockPin.cc:34
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
byte peekIO(word port, EmuTime::param time) const
Definition: I8254.cc:54
byte readIO(word port, EmuTime::param time)
Definition: I8254.cc:41
ClockPin & getClockPin(unsigned cntr)
Definition: I8254.cc:115
void writeIO(word port, byte value, EmuTime::param time)
Definition: I8254.cc:67
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:33
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:126
MSXCPUInterface & getCPUInterface() const
Definition: MSXDevice.cc:134
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
std::string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:727
This file implemented 3 utility functions:
Definition: Autofire.cc:9
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:1009
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:133