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