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