1 /** 2 * Copyright: Enalye 3 * License: Zlib 4 * Authors: Enalye 5 */ 6 module minuit.device.winmm; 7 8 version(Windows) { 9 import std.conv: to; 10 import core.sys.windows.mmsystem, core.sys.windows.basetsd; 11 import core.stdc.string; 12 import core.sync.mutex, core.sync.semaphore; 13 14 import minuit.common; 15 16 private enum MnInputBufferSize = 512; 17 18 /** 19 * A single midi output port. 20 * 21 * It cannot be instantiated. 22 * Fetch them using mnFetchOutputs instead. 23 * 24 * See_Also: 25 * MnInputPort, mnFetchOutputs 26 */ 27 final class MnOutputPort { 28 private { 29 uint _id; 30 string _name; 31 } 32 33 @property { 34 ///User-friendly name of the port. 35 string name() const { return _name; } 36 } 37 38 private this() {} 39 } 40 41 /** 42 * A single midi input port. 43 * 44 * It cannot be instantiated. 45 * Fetch them using mnFetchInputs instead. 46 * 47 * See_Also: 48 * MnOutputPort, mnFetchOutputs 49 */ 50 final class MnInputPort { 51 private { 52 uint _id; 53 string _name; 54 } 55 56 @property { 57 ///User-friendly name of the port. 58 string name() const { return _name; } 59 } 60 61 private this() {} 62 } 63 64 /** 65 * Handle of an output port. 66 * 67 * See_Also: 68 * MnOutputPort, mnOpenOutput 69 */ 70 final class MnOutputHandle { 71 private { 72 HMIDIOUT _handle; 73 MnOutputPort _port; 74 Mutex _mutex; 75 } 76 77 @property { 78 ///The port associated with this handle. 79 MnOutputPort port() { return _port; } 80 } 81 82 private this() { 83 _mutex = new Mutex; 84 } 85 } 86 87 /** 88 * Handle of an input port. 89 * 90 * See_Also: 91 * MnInputPort, mnOpenInput 92 */ 93 final class MnInputHandle { 94 private { 95 HMIDIIN _handle; 96 ubyte[MnInputBufferSize] _buffer; 97 ushort _pread, _pwrite, _size; 98 Mutex _mutex; 99 MnInputPort _port; 100 } 101 102 @property { 103 ///The port associated with this handle. 104 MnInputPort port() { return _port; } 105 } 106 107 private this() { 108 _mutex = new Mutex; 109 } 110 } 111 112 private union MnWord { 113 uint word; 114 ubyte[4] bytes; 115 } 116 117 MnOutputPort[] mnFetchOutputs() { 118 wchar[MAXPNAMELEN] name; 119 uint midiPort; 120 const maxDevs = midiOutGetNumDevs(); 121 MnOutputPort[] midiDevices; 122 123 for(; midiPort < maxDevs; midiPort++) { 124 MnOutputPort midiDevice = new MnOutputPort; 125 midiDevice._id = midiPort; 126 127 MIDIOUTCAPS midiOutCaps; 128 midiOutGetDevCaps(midiPort, &midiOutCaps, midiOutCaps.sizeof); 129 130 int len; 131 foreach(i; 0.. MAXPNAMELEN) { 132 if(midiOutCaps.szPname[i] >= 0x20 && midiOutCaps.szPname[i] < 0x7F) { 133 name[i] = midiOutCaps.szPname[i]; 134 continue; 135 } 136 len = i; 137 break; 138 } 139 midiDevice._name = to!string(name); 140 midiDevice._name.length = len; 141 midiDevices ~= midiDevice; 142 } 143 return midiDevices; 144 } 145 146 MnInputPort[] mnFetchInputs() { 147 wchar[MAXPNAMELEN] name; 148 uint midiPort; 149 const maxDevs = midiInGetNumDevs(); 150 MnInputPort[] midiDevices; 151 152 for(; midiPort < maxDevs; midiPort++) { 153 MnInputPort midiDevice = new MnInputPort; 154 midiDevice._id = midiPort; 155 156 MIDIINCAPS midiInCaps; 157 midiInGetDevCaps(midiPort, &midiInCaps, midiInCaps.sizeof); 158 159 int len; 160 foreach(i; 0.. MAXPNAMELEN) { 161 if(midiInCaps.szPname[i] >= 0x20 && midiInCaps.szPname[i] < 0x7F) { 162 name[i] = midiInCaps.szPname[i]; 163 continue; 164 } 165 len = i; 166 break; 167 } 168 midiDevice._name = to!string(name); 169 midiDevice._name.length = len; 170 midiDevices ~= midiDevice; 171 } 172 return midiDevices; 173 } 174 175 MnOutputHandle mnOpenOutput(MnOutputPort port) { 176 if(!port) 177 return null; 178 179 if(port._id >= midiOutGetNumDevs()) 180 return null; 181 182 HMIDIOUT handle; 183 const flag = midiOutOpen(&handle, port._id, 0, 0, CALLBACK_NULL); 184 if(flag != MMSYSERR_NOERROR) 185 return null; 186 187 MnOutputHandle midiOutHandle = new MnOutputHandle; 188 midiOutHandle._handle = handle; 189 midiOutHandle._port = port; 190 191 return midiOutHandle; 192 } 193 194 MnInputHandle mnOpenInput(MnInputPort port) { 195 if(!port) 196 return null; 197 198 if(port._id >= midiInGetNumDevs()) 199 return null; 200 201 MnInputHandle midiInHandle = new MnInputHandle; 202 HMIDIIN handle; 203 const flag = midiInOpen( 204 &handle, 205 port._id, 206 cast(DWORD_PTR)(&_mnListen), 207 cast(DWORD_PTR)cast(void*)midiInHandle, 208 CALLBACK_FUNCTION); 209 210 if(flag != MMSYSERR_NOERROR) 211 return null; 212 213 midiInHandle._handle = handle; 214 midiInHandle._port = port; 215 216 if(midiInStart(handle) != MMSYSERR_NOERROR) 217 return null; 218 219 return midiInHandle; 220 } 221 222 extern(Windows) private void _mnListen(HMIDIIN, uint msg, DWORD_PTR dwHandle, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { 223 if(!dwHandle) 224 return; 225 226 version(X86_64) { 227 MnInputHandle handle = cast(MnInputHandle)(cast(void*)dwHandle); 228 } 229 else version(X86) { 230 MnInputHandle handle = cast(MnInputHandle)(cast(void*)(dwHandle)); 231 } 232 233 if (msg != MIM_DATA) return; 234 235 MnWord msgWord; 236 msgWord.word = cast(uint) dwParam1; 237 cast(void) dwParam2; // dwParam2 = timestamp in milliseconds since device open, unused here 238 239 const uint dataBytesCount = mnGetDataBytesCountByStatus(msgWord.bytes[0]); 240 if(dataBytesCount >= 3) 241 return; 242 for(ushort i; i <= dataBytesCount; i ++) 243 _mnPush(handle, msgWord.bytes[i]); 244 } 245 246 private void _mnPush(MnInputHandle handle, ubyte value) { 247 synchronized(handle._mutex) { 248 if(handle._size == MnInputBufferSize) { 249 //If full, we replace old data. 250 handle._buffer[handle._pwrite] = value; 251 handle._pwrite = (handle._pwrite + 1u) & (MnInputBufferSize - 1); 252 handle._pread = (handle._pread + 1u) & (MnInputBufferSize - 1); 253 } 254 else { 255 handle._buffer[handle._pwrite] = value; 256 handle._pwrite = (handle._pwrite + 1u) & (MnInputBufferSize - 1); 257 handle._size ++; 258 } 259 } 260 } 261 262 void mnCloseOutput(MnOutputHandle handle) { 263 if(!handle) 264 return; 265 synchronized(handle._mutex) { 266 midiOutReset(handle._handle); 267 midiOutClose(handle._handle); 268 } 269 } 270 271 void mnCloseInput(MnInputHandle handle) { 272 if(!handle) 273 return; 274 synchronized(handle._mutex) { 275 midiInStop(handle._handle); 276 277 handle._mutex.unlock(); 278 handle._size = 0u; 279 handle._pread = 0u; 280 handle._pwrite = 0u; 281 282 midiInReset(handle._handle); 283 midiInClose(handle._handle); 284 } 285 } 286 287 void mnSendOutput(MnOutputHandle handle, ubyte a) { 288 if(!handle) 289 return; 290 synchronized(handle._mutex) { 291 MnWord midiWord; 292 midiWord.bytes[0] = a; 293 midiOutShortMsg(handle._handle, midiWord.word); 294 } 295 } 296 297 void mnSendOutput(MnOutputHandle handle, ubyte a, byte b) { 298 if(!handle) 299 return; 300 synchronized(handle._mutex) { 301 MnWord midiWord; 302 midiWord.bytes[0] = a; 303 midiWord.bytes[1] = b; 304 midiOutShortMsg(handle._handle, midiWord.word); 305 } 306 } 307 308 void mnSendOutput(MnOutputHandle handle, ubyte a, ubyte b, ubyte c) { 309 if(!handle) 310 return; 311 synchronized(handle._mutex) { 312 MnWord midiWord; 313 midiWord.bytes[0] = a; 314 midiWord.bytes[1] = b; 315 midiWord.bytes[2] = c; 316 midiOutShortMsg(handle._handle, midiWord.word); 317 } 318 } 319 320 void mnSendOutput(MnOutputHandle handle, ubyte a, ubyte b, ubyte c, ubyte d) { 321 if(!handle) 322 return; 323 synchronized(handle._mutex) { 324 MnWord midiWord; 325 midiWord.bytes[0] = a; 326 midiWord.bytes[1] = b; 327 midiWord.bytes[2] = c; 328 midiWord.bytes[3] = d; 329 midiOutShortMsg(handle._handle, midiWord.word); 330 } 331 } 332 333 private void _mnSendOutput(MnOutputHandle handle, MnWord midiWord) { 334 if(!handle) 335 return; 336 synchronized(handle._mutex) { 337 midiOutShortMsg(handle._handle, midiWord.word); 338 } 339 } 340 341 void mnSendOutput(MnOutputHandle handle, const(ubyte)[] data) { 342 if(!handle) 343 return; 344 synchronized(handle._mutex) { 345 MIDIHDR midiHeader; 346 midiHeader.lpData = cast(char*)data; 347 midiHeader.dwBufferLength = cast(uint)data.length; 348 midiHeader.dwFlags = 0; 349 midiOutPrepareHeader(handle._handle, &midiHeader, midiHeader.sizeof); 350 midiOutLongMsg(handle._handle, &midiHeader, midiHeader.sizeof); 351 midiOutUnprepareHeader(handle._handle, &midiHeader, midiHeader.sizeof); 352 } 353 } 354 355 private ubyte _mnPeek(MnInputHandle handle, ushort offset) { 356 return handle._buffer[(handle._pread + offset) & (MnInputBufferSize - 1)]; 357 } 358 359 private void _mnConsume(MnInputHandle handle, ushort count) { 360 handle._pread = (handle._pread + count) & (MnInputBufferSize - 1); 361 handle._size -= count; 362 } 363 364 private void _mnCleanup(MnInputHandle handle) { 365 while(handle._size) { 366 const ubyte status = _mnPeek(handle, 0u); 367 if((status & 0x80) != 0u) 368 return; 369 _mnConsume(handle, 1u); 370 } 371 } 372 373 ubyte[] mnReceiveInput(MnInputHandle handle) { 374 if(!handle) 375 return []; 376 ubyte[] data; 377 synchronized(handle._mutex) { 378 _mnCleanup(handle); 379 if(handle._size != 0u) { 380 const ubyte status = _mnPeek(handle, 0u); 381 data ~= status; 382 //Fill SysEx message 383 if(status == 0xF0) { 384 for(ushort i = 1u; i < handle._size; i ++) { 385 const ubyte value = _mnPeek(handle, i); 386 data ~= value; 387 if(value == 0xF7) 388 break; 389 } 390 //Incomplete SysEx 391 if(data[$ - 1] != 0xF7) 392 return []; 393 } 394 //Common messages 395 else { 396 const int messageDataCount = mnGetDataBytesCountByStatus(status); 397 if(messageDataCount > handle._size) 398 return []; 399 400 for(ushort i = 1u; i <= messageDataCount; i ++) 401 data ~= _mnPeek(handle, i); 402 } 403 } 404 } 405 _mnConsume(handle, cast(ushort)data.length); 406 return data; 407 } 408 409 bool mnCanReceiveInput(MnInputHandle handle) { 410 if(!handle) 411 return false; 412 bool isNotEmpty; 413 synchronized(handle._mutex) { 414 _mnCleanup(handle); 415 if(handle._size != 0u) { 416 const ubyte status = _mnPeek(handle, 0u); 417 //Check SysEx validity 418 if(status == 0xF0) { 419 for(ushort i = 1; i < handle._size; i ++) { 420 if(_mnPeek(handle, i) == 0xF7) { 421 isNotEmpty = true; 422 break; 423 } 424 } 425 } 426 //Common messages 427 else { 428 const int messageDataCount = mnGetDataBytesCountByStatus(status); 429 if((messageDataCount + 1) <= handle._size) 430 isNotEmpty = true; 431 } 432 } 433 } 434 return isNotEmpty; 435 } 436 437 //WINMM Bindings 438 private: 439 extern(C): 440 @nogc nothrow: 441 442 //MMRESULT _midiInOpen(HMIDIIN, uint, void*, void*, uint); 443 444 }