OXYGENHUB
◆ main openmsx / src / serial / MidiInWindows.cc
4.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifdef _WIN32
2
3
#include "MidiInWindows.hh"
4
5
#include "EventDistributor.hh"
6
#include "MidiInConnector.hh"
7
#include "Midi_w32.hh"
8
#include "PluggingController.hh"
9
#include "PlugException.hh"
10
#include "Scheduler.hh"
11
12
#include "one_of.hh"
13
#include "serialize.hh"
14
#include "xrange.hh"
15
16
#include <cstring>
17
#include <cerrno>
18
#ifndef WIN32_LEAN_AND_MEAN
19
#define WIN32_LEAN_AND_MEAN
20
#endif
21
#include <windows.h>
22
#include <mmsystem.h>
23
#include <memory>
24
#include <sys/types.h>
25
#include <sys/stat.h>
26
27
namespace openmsx {
28
29
void MidiInWindows::registerAll(EventDistributor& eventDistributor,
30
                                Scheduler& scheduler,
31
                                PluggingController& controller)
32
{
33
	w32_midiInInit();
34
	for (auto i : xrange(w32_midiInGetVFNsNum())) {
35
		controller.registerPluggable(std::make_unique<MidiInWindows>(
36
			eventDistributor, scheduler, i));
37
	}
38
}
39
40
41
MidiInWindows::MidiInWindows(EventDistributor& eventDistributor_,
42
                             Scheduler& scheduler_, unsigned num)
43
	: eventDistributor(eventDistributor_), scheduler(scheduler_)
44
{
45
	name = w32_midiInGetVFN(num);
46
	desc = w32_midiInGetRDN(num);
47
48
	eventDistributor.registerEventListener(EventType::MIDI_IN_WINDOWS, *this);
49
}
50
51
MidiInWindows::~MidiInWindows()
52
{
53
	eventDistributor.unregisterEventListener(EventType::MIDI_IN_WINDOWS, *this);
54
55
	//w32_midiInClean(); // TODO
56
}
57
58
// Pluggable
59
void MidiInWindows::plugHelper(Connector& connector_, EmuTime /*time*/)
60
{
61
	auto& midiConnector = static_cast<MidiInConnector&>(connector_);
62
	midiConnector.setDataBits(SerialDataInterface::DataBits::D8); // 8 data bits
63
	midiConnector.setStopBits(SerialDataInterface::StopBits::S1); // 1 stop bit
64
	midiConnector.setParityBit(false, SerialDataInterface::Parity::EVEN); // no parity
65
66
	setConnector(&connector_); // base class will do this in a moment,
67
	                           // but thread already needs it
68
69
	threadIdLatch.emplace(1); // (re)initialize latches
70
	devIdxLatch.emplace(1);
71
	thread = std::thread([this]() { run(); });
72
73
	threadIdLatch->wait(); // wait till helper thread has set threadId
74
	devIdx = w32_midiInOpen(name.c_str(), threadId);
75
	devIdxLatch->count_down(); // unblock helper thread waiting for devIdx
76
77
	if (devIdx == unsigned(-1)) {
78
		throw PlugException("Failed to open " + name);
79
	}
80
}
81
82
void MidiInWindows::unplugHelper(EmuTime /*time*/)
83
{
84
	assert(devIdx != unsigned(-1));
85
	w32_midiInClose(devIdx);
86
	thread.join();
87
	devIdx = unsigned(-1);
88
}
89
90
zstring_view MidiInWindows::getName() const
91
{
92
	return name;
93
}
94
95
zstring_view MidiInWindows::getDescription() const
96
{
97
	return desc;
98
}
99
100
void MidiInWindows::procLongMsg(LPMIDIHDR p)
101
{
102
	if (p->dwBytesRecorded) {
103
		{
104
			std::scoped_lock lock(queueMutex);
105
			for (auto i : xrange(p->dwBytesRecorded)) {
106
				queue.push_back(p->lpData[i]);
107
			}
108
		}
109
		eventDistributor.distributeEvent(MidiInWindowsEvent());
110
	}
111
}
112
113
void MidiInWindows::procShortMsg(DWORD param)
114
{
115
	int num;
116
	switch (param & 0xF0) {
117
		case 0x80: case 0x90: case 0xA0: case 0xB0: case 0xE0:
118
			num = 3; break;
119
		case 0xC0: case 0xD0:
120
			num = 2; break;
121
		default:
122
			num = 1; break;
123
	}
124
	std::scoped_lock lock(queueMutex);
125
	while (num--) {
126
		queue.push_back(param & 0xFF);
127
		param >>= 8;
128
	}
129
	eventDistributor.distributeEvent(MidiInWindowsEvent());
130
}
131
132
void MidiInWindows::run()
133
{
134
	assert(isPluggedIn());
135
136
	threadId = GetCurrentThreadId();
137
	threadIdLatch->count_down(); // unblock plugHelper waiting for threadId
138
	devIdxLatch->wait(); // wait for plugHelper to set devIdx
139
140
	bool fexit = devIdx == unsigned(-1);
141
	while (!fexit) {
142
		MSG msg;
143
		if (GetMessage(&msg, nullptr, 0, 0) == one_of(0, -1)) {
144
			break;
145
		}
146
		switch (msg.message) {
147
			case MM_MIM_OPEN:
148
				break;
149
			case MM_MIM_DATA:
150
			case MM_MIM_MOREDATA:
151
				procShortMsg(DWORD(msg.lParam));
152
				break;
153
			case MM_MIM_LONGDATA:
154
				procLongMsg(reinterpret_cast<LPMIDIHDR>(msg.lParam));
155
				break;
156
			case MM_MIM_ERROR:
157
			case MM_MIM_LONGERROR:
158
			case MM_MIM_CLOSE:
159
				fexit = true;
160
				break;
161
			default:
162
				break;
163
		}
164
	}
165
	threadId = 0;
166
}
167
168
// MidiInDevice
169
void MidiInWindows::signal(EmuTime time)
170
{
171
	auto* conn = static_cast<MidiInConnector*>(getConnector());
172
	if (!conn->acceptsData()) {
173
		std::scoped_lock lock(queueMutex);
174
		queue.clear();
175
		return;
176
	}
177
	if (!conn->ready()) return;
178
179
	uint8_t data;
180
	{
181
		std::scoped_lock lock(queueMutex);
182
		if (queue.empty()) return;
183
		data = queue.pop_front();
184
	}
185
	conn->recvByte(data, time);
186
}
187
188
// EventListener
189
bool MidiInWindows::signalEvent(const Event& /*event*/)
190
{
191
	if (isPluggedIn()) {
192
		signal(scheduler.getCurrentTime());
193
	} else {
194
		std::scoped_lock lock(queueMutex);
195
		queue.clear();
196
	}
197
	return false;
198
}
199
200
template<typename Archive>
201
void MidiInWindows::serialize(Archive& /*ar*/, unsigned /*version*/)
202
{
203
	// don't restore this after loadstate
204
}
205
INSTANTIATE_SERIALIZE_METHODS(MidiInWindows);
206
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInWindows, "MidiInWindows");
207
208
} // namespace openmsx
209
210
#endif // defined(_WIN32)