1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 // Disable symbol overrides so that we can use system headers.
24 #define FORBIDDEN_SYMBOL_ALLOW_ALL
25 
26 #include "common/scummsys.h"
27 
28 #if defined(WIN32) && !defined(_WIN32_WCE)
29 
30 #define WIN32_LEAN_AND_MEAN
31 #include <windows.h>
32 
33 #include "audio/musicplugin.h"
34 #include "audio/mpu401.h"
35 #include "common/config-manager.h"
36 #include "common/translation.h"
37 #include "common/textconsole.h"
38 #include "common/error.h"
39 
40 #include <mmsystem.h>
41 
42 ////////////////////////////////////////
43 //
44 // Windows MIDI driver
45 //
46 ////////////////////////////////////////
47 
48 class MidiDriver_WIN : public MidiDriver_MPU401 {
49 private:
50 	MIDIHDR _streamHeader;
51 	byte _streamBuffer[266];	// SysEx blocks should be no larger than 266 bytes
52 	HANDLE _streamEvent;
53 	HMIDIOUT _mo;
54 	bool _isOpen;
55 	int _device;
56 
57 	void check_error(MMRESULT result);
58 
59 public:
MidiDriver_WIN(int deviceIndex)60 	MidiDriver_WIN(int deviceIndex) : _isOpen(false), _device(deviceIndex) { }
61 	int open();
isOpen() const62 	bool isOpen() const { return _isOpen; }
63 	void close();
64 	void send(uint32 b);
65 	void sysEx(const byte *msg, uint16 length);
66 };
67 
open()68 int MidiDriver_WIN::open() {
69 	if (_isOpen)
70 		return MERR_ALREADY_OPEN;
71 
72 	_streamEvent = CreateEvent(NULL, true, true, NULL);
73 	MMRESULT res = midiOutOpen((HMIDIOUT *)&_mo, _device, (DWORD_PTR)_streamEvent, 0, CALLBACK_EVENT);
74 	if (res != MMSYSERR_NOERROR) {
75 		check_error(res);
76 		CloseHandle(_streamEvent);
77 		return MERR_DEVICE_NOT_AVAILABLE;
78 	}
79 
80 	_isOpen = true;
81 	return 0;
82 }
83 
close()84 void MidiDriver_WIN::close() {
85 	if (!_isOpen)
86 		return;
87 	_isOpen = false;
88 	MidiDriver_MPU401::close();
89 	midiOutUnprepareHeader(_mo, &_streamHeader, sizeof(_streamHeader));
90 	check_error(midiOutClose(_mo));
91 	CloseHandle(_streamEvent);
92 }
93 
send(uint32 b)94 void MidiDriver_WIN::send(uint32 b) {
95 	assert(_isOpen);
96 
97 	union {
98 		DWORD dwData;
99 		BYTE bData[4];
100 	} u;
101 
102 	u.bData[3] = (byte)((b & 0xFF000000) >> 24);
103 	u.bData[2] = (byte)((b & 0x00FF0000) >> 16);
104 	u.bData[1] = (byte)((b & 0x0000FF00) >> 8);
105 	u.bData[0] = (byte)(b & 0x000000FF);
106 
107 	check_error(midiOutShortMsg(_mo, u.dwData));
108 }
109 
sysEx(const byte * msg,uint16 length)110 void MidiDriver_WIN::sysEx(const byte *msg, uint16 length) {
111 	if (!_isOpen)
112 		return;
113 
114 	if (WaitForSingleObject (_streamEvent, 2000) == WAIT_TIMEOUT) {
115 		warning ("Could not send SysEx - MMSYSTEM is still trying to send data");
116 		return;
117 	}
118 
119 	assert(length+2 <= 266);
120 
121 	midiOutUnprepareHeader(_mo, &_streamHeader, sizeof(_streamHeader));
122 
123 	// Add SysEx frame
124 	_streamBuffer[0] = 0xF0;
125 	memcpy(&_streamBuffer[1], msg, length);
126 	_streamBuffer[length+1] = 0xF7;
127 
128 	_streamHeader.lpData = (char *)_streamBuffer;
129 	_streamHeader.dwBufferLength = length + 2;
130 	_streamHeader.dwBytesRecorded = length + 2;
131 	_streamHeader.dwUser = 0;
132 	_streamHeader.dwFlags = 0;
133 
134 	MMRESULT result = midiOutPrepareHeader(_mo, &_streamHeader, sizeof(_streamHeader));
135 	if (result != MMSYSERR_NOERROR) {
136 		check_error (result);
137 		return;
138 	}
139 
140 	ResetEvent(_streamEvent);
141 	result = midiOutLongMsg(_mo, &_streamHeader, sizeof(_streamHeader));
142 	if (result != MMSYSERR_NOERROR) {
143 		check_error(result);
144 		SetEvent(_streamEvent);
145 		return;
146 	}
147 }
148 
check_error(MMRESULT result)149 void MidiDriver_WIN::check_error(MMRESULT result) {
150 	char buf[200];
151 	if (result != MMSYSERR_NOERROR) {
152 		midiOutGetErrorText(result, buf, 200);
153 		warning("MM System Error '%s'", buf);
154 	}
155 }
156 
157 
158 // Plugin interface
159 
160 class WindowsMusicPlugin : public MusicPluginObject {
161 public:
getName() const162 	const char *getName() const {
163 		return _s("Windows MIDI");
164 	}
165 
getId() const166 	const char *getId() const {
167 		return "windows";
168 	}
169 
170 	MusicDevices getDevices() const;
171 	Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
172 };
173 
getDevices() const174 MusicDevices WindowsMusicPlugin::getDevices() const {
175 	MusicDevices devices;
176 	int numDevs = midiOutGetNumDevs();
177 	MIDIOUTCAPS tmp;
178 
179 	Common::StringArray deviceNames;
180 	for (int i = 0; i < numDevs; i++) {
181 		if (midiOutGetDevCaps(i, &tmp, sizeof(MIDIOUTCAPS)) != MMSYSERR_NOERROR)
182 			break;
183 		deviceNames.push_back(tmp.szPname);
184 	}
185 
186 	// Limit us to the number of actually retrieved devices.
187 	numDevs = deviceNames.size();
188 
189 	// Check for non-unique device names. This may happen if someone has devices with identical
190 	// names (e. g. more than one USB device of the exact same hardware type). It seems that this
191 	// does happen in reality sometimes. We generate index numbers for these devices.
192 	// This is not an ideal solution, since this index could change whenever another USB
193 	// device gets plugged in or removed, switched off or just plugged into a different port.
194 	// Unfortunately midiOutGetDevCaps() does not generate any other unique information
195 	// that could be used. Our index numbers which match the device order should at least be
196 	// a little more stable than just using the midiOutGetDevCaps() device ID, since a missing
197 	// device (e.g. switched off) should actually not be harmful to our indices (as it would be
198 	// when using the device IDs). The cases where users have devices with identical names should
199 	// be rare enough anyway.
200 	Common::Array<int> nonUniqueIndex;
201 	for (int i = 0; i < numDevs; i++) {
202 		int match = -1;
203 		for (int ii = 0; ii < i; ii++) {
204 			if (deviceNames[i] == deviceNames[ii]) {
205 				if (nonUniqueIndex[ii] == -1)
206 					nonUniqueIndex[ii] = 0;
207 				if (++match == 0)
208 					++match;
209 			}
210 		}
211 		nonUniqueIndex.push_back(match);
212 	}
213 
214 	// We now add the index number to the non-unique device names to make them unique.
215 	for (int i = 0; i < numDevs; i++) {
216 		if (nonUniqueIndex[i] != -1)
217 			deviceNames[i] = Common::String::format("%s - #%.02d", deviceNames[i].c_str(), nonUniqueIndex[i]);
218 	}
219 
220 	for (Common::StringArray::iterator i = deviceNames.begin(); i != deviceNames.end(); ++i)
221 		// There is no way to detect the "MusicType" so I just set it to MT_GM
222 		// The user will have to manually select his MT32 type device and his GM type device.
223 		devices.push_back(MusicDevice(this, *i, MT_GM));
224 
225 	return devices;
226 }
227 
createInstance(MidiDriver ** mididriver,MidiDriver::DeviceHandle dev) const228 Common::Error WindowsMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle dev) const {
229 	int devIndex = 0;
230 	bool found = false;
231 
232 	if (dev) {
233 		MusicDevices i = getDevices();
234 		for (MusicDevices::iterator d = i.begin(); d != i.end(); d++) {
235 			if (d->getCompleteId().equals(MidiDriver::getDeviceString(dev, MidiDriver::kDeviceId))) {
236 				found = true;
237 				break;
238 			}
239 			devIndex++;
240 		}
241 	}
242 
243 	*mididriver = new MidiDriver_WIN(found ? devIndex : 0);
244 	return Common::kNoError;
245 }
246 
247 //#if PLUGIN_ENABLED_DYNAMIC(WINDOWS)
248 	//REGISTER_PLUGIN_DYNAMIC(WINDOWS, PLUGIN_TYPE_MUSIC, WindowsMusicPlugin);
249 //#else
250 	REGISTER_PLUGIN_STATIC(WINDOWS, PLUGIN_TYPE_MUSIC, WindowsMusicPlugin);
251 //#endif
252 
253 #endif
254