PageRenderTime 47ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/Samples/Chap22/KBMidi/KBMidi.d

http://github.com/AndrejMitrovic/DWinProgramming
D | 775 lines | 603 code | 139 blank | 33 comment | 49 complexity | 82e3257b7fcac6a0a54211ab05e42fbc MD5 | raw file
  1. /+
  2. + Copyright (c) Charles Petzold, 1998.
  3. + Ported to the D Programming Language by Andrej Mitrovic, 2011.
  4. +/
  5. module KBMidi;
  6. import core.memory;
  7. import core.runtime;
  8. import core.thread;
  9. import std.algorithm : max, min;
  10. import std.conv;
  11. import std.math;
  12. import std.range;
  13. import std.string;
  14. import std.utf : count, toUTFz;
  15. auto toUTF16z(S)(S s)
  16. {
  17. return toUTFz!(const(wchar)*)(s);
  18. }
  19. pragma(lib, "gdi32.lib");
  20. pragma(lib, "comdlg32.lib");
  21. pragma(lib, "winmm.lib");
  22. import core.sys.windows.windef;
  23. import core.sys.windows.winuser;
  24. import core.sys.windows.wingdi;
  25. import core.sys.windows.winbase;
  26. import core.sys.windows.commdlg;
  27. import core.sys.windows.mmsystem;
  28. string appName = "KBMidi";
  29. string description = "Keyboard MIDI Player";
  30. HINSTANCE hinst;
  31. extern (Windows)
  32. int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  33. {
  34. int result;
  35. try
  36. {
  37. Runtime.initialize();
  38. result = myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
  39. Runtime.terminate();
  40. }
  41. catch (Throwable o)
  42. {
  43. MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
  44. result = 0;
  45. }
  46. return result;
  47. }
  48. int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  49. {
  50. hinst = hInstance;
  51. HACCEL hAccel;
  52. HWND hwnd;
  53. MSG msg;
  54. WNDCLASS wndclass;
  55. wndclass.style = CS_HREDRAW | CS_VREDRAW;
  56. wndclass.lpfnWndProc = &WndProc;
  57. wndclass.cbClsExtra = 0;
  58. wndclass.cbWndExtra = 0;
  59. wndclass.hInstance = hInstance;
  60. wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  61. wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
  62. wndclass.hbrBackground = cast(HBRUSH) GetStockObject(WHITE_BRUSH);
  63. wndclass.lpszMenuName = appName.toUTF16z;
  64. wndclass.lpszClassName = appName.toUTF16z;
  65. if (!RegisterClass(&wndclass))
  66. {
  67. MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
  68. return 0;
  69. }
  70. hwnd = CreateWindow(appName.toUTF16z, // window class name
  71. description.toUTF16z, // window caption
  72. WS_OVERLAPPEDWINDOW, // window style
  73. CW_USEDEFAULT, // initial x position
  74. CW_USEDEFAULT, // initial y position
  75. CW_USEDEFAULT, // initial x size
  76. CW_USEDEFAULT, // initial y size
  77. NULL, // parent window handle
  78. NULL, // window menu handle
  79. hInstance, // program instance handle
  80. NULL); // creation parameters
  81. ShowWindow(hwnd, iCmdShow);
  82. UpdateWindow(hwnd);
  83. while (GetMessage(&msg, NULL, 0, 0))
  84. {
  85. TranslateMessage(&msg);
  86. DispatchMessage(&msg);
  87. }
  88. return msg.wParam;
  89. }
  90. enum IDM_OPEN = 0x100;
  91. enum IDM_CLOSE = 0x101;
  92. enum IDM_DEVICE = 0x200;
  93. enum IDM_CHANNEL = 0x300;
  94. enum IDM_VOICE = 0x400;
  95. HMIDIOUT hMidiOut;
  96. int iDevice = MIDIMAPPER, iChannel = 0, iVoice = 0, iVelocity = 64;
  97. int cxCaps, cyChar, xOffset, yOffset;
  98. // Structures and data for showing families and instruments on menu
  99. struct INSTRUMENT
  100. {
  101. string szInst;
  102. int iVoice;
  103. }
  104. struct FAMILY
  105. {
  106. string szFam;
  107. INSTRUMENT[8] inst;
  108. }
  109. FAMILY[16] fam =
  110. [
  111. FAMILY("Piano",
  112. [INSTRUMENT("Acoustic Grand Piano", 0),
  113. INSTRUMENT("Bright Acoustic Piano", 1),
  114. INSTRUMENT("Electric Grand Piano", 2),
  115. INSTRUMENT("Honky-tonk Piano", 3),
  116. INSTRUMENT("Rhodes Piano", 4),
  117. INSTRUMENT("Chorused Piano", 5),
  118. INSTRUMENT("Harpsichord", 6),
  119. INSTRUMENT("Clavinet", 7)]),
  120. FAMILY("Chromatic Percussion",
  121. [INSTRUMENT("Celesta", 8),
  122. INSTRUMENT("Glockenspiel", 9),
  123. INSTRUMENT("Music Box", 10),
  124. INSTRUMENT("Vibraphone", 11),
  125. INSTRUMENT("Marimba", 12),
  126. INSTRUMENT("Xylophone", 13),
  127. INSTRUMENT("Tubular Bells", 14),
  128. INSTRUMENT("Dulcimer", 15)]),
  129. FAMILY("Organ",
  130. [INSTRUMENT("Hammond Organ", 16),
  131. INSTRUMENT("Percussive Organ", 17),
  132. INSTRUMENT("Rock Organ", 18),
  133. INSTRUMENT("Church Organ", 19),
  134. INSTRUMENT("Reed Organ", 20),
  135. INSTRUMENT("Accordian", 21),
  136. INSTRUMENT("Harmonica", 22),
  137. INSTRUMENT("Tango Accordian", 23)]),
  138. FAMILY("Guitar",
  139. [INSTRUMENT("Acoustic Guitar (nylon)", 24),
  140. INSTRUMENT("Acoustic Guitar (steel)", 25),
  141. INSTRUMENT("Electric Guitar (jazz)", 26),
  142. INSTRUMENT("Electric Guitar (clean)", 27),
  143. INSTRUMENT("Electric Guitar (muted)", 28),
  144. INSTRUMENT("Overdriven Guitar", 29),
  145. INSTRUMENT("Distortion Guitar", 30),
  146. INSTRUMENT("Guitar Harmonics", 31)]),
  147. FAMILY("Bass",
  148. [INSTRUMENT("Acoustic Bass", 32),
  149. INSTRUMENT("Electric Bass (finger)", 33),
  150. INSTRUMENT("Electric Bass (pick)", 34),
  151. INSTRUMENT("Fretless Bass", 35),
  152. INSTRUMENT("Slap Bass 1", 36),
  153. INSTRUMENT("Slap Bass 2", 37),
  154. INSTRUMENT("Synth Bass 1", 38),
  155. INSTRUMENT("Synth Bass 2", 39)]),
  156. FAMILY("Strings",
  157. [INSTRUMENT("Violin", 40),
  158. INSTRUMENT("Viola", 41),
  159. INSTRUMENT("Cello", 42),
  160. INSTRUMENT("Contrabass", 43),
  161. INSTRUMENT("Tremolo Strings", 44),
  162. INSTRUMENT("Pizzicato Strings", 45),
  163. INSTRUMENT("Orchestral Harp", 46),
  164. INSTRUMENT("Timpani", 47)]),
  165. FAMILY("Ensemble",
  166. [INSTRUMENT("String Ensemble 1", 48),
  167. INSTRUMENT("String Ensemble 2", 49),
  168. INSTRUMENT("Synth Strings 1", 50),
  169. INSTRUMENT("Synth Strings 2", 51),
  170. INSTRUMENT("Choir Aahs", 52),
  171. INSTRUMENT("Voice Oohs", 53),
  172. INSTRUMENT("Synth Voice", 54),
  173. INSTRUMENT("Orchestra Hit", 55)]),
  174. FAMILY("Brass",
  175. [INSTRUMENT("Trumpet", 56),
  176. INSTRUMENT("Trombone", 57),
  177. INSTRUMENT("Tuba", 58),
  178. INSTRUMENT("Muted Trumpet", 59),
  179. INSTRUMENT("French Horn", 60),
  180. INSTRUMENT("Brass Section", 61),
  181. INSTRUMENT("Synth Brass 1", 62),
  182. INSTRUMENT("Synth Brass 2", 63)]),
  183. FAMILY("Reed",
  184. [INSTRUMENT("Soprano Sax", 64),
  185. INSTRUMENT("Alto Sax", 65),
  186. INSTRUMENT("Tenor Sax", 66),
  187. INSTRUMENT("Baritone Sax", 67),
  188. INSTRUMENT("Oboe", 68),
  189. INSTRUMENT("English Horn", 69),
  190. INSTRUMENT("Bassoon", 70),
  191. INSTRUMENT("Clarinet", 71)]),
  192. FAMILY("Pipe",
  193. [INSTRUMENT("Piccolo", 72),
  194. INSTRUMENT("Flute", 73),
  195. INSTRUMENT("Recorder", 74),
  196. INSTRUMENT("Pan Flute", 75),
  197. INSTRUMENT("Bottle Blow", 76),
  198. INSTRUMENT("Shakuhachi", 77),
  199. INSTRUMENT("Whistle", 78),
  200. INSTRUMENT("Ocarina", 79)]),
  201. FAMILY("Synth Lead",
  202. [INSTRUMENT("Lead 1 (square)", 80),
  203. INSTRUMENT("Lead 2 (sawtooth)", 81),
  204. INSTRUMENT("Lead 3 (caliope lead)", 82),
  205. INSTRUMENT("Lead 4 (chiff lead)", 83),
  206. INSTRUMENT("Lead 5 (charang)", 84),
  207. INSTRUMENT("Lead 6 (voice)", 85),
  208. INSTRUMENT("Lead 7 (fifths)", 86),
  209. INSTRUMENT("Lead 8 (brass + lead)", 87)]),
  210. FAMILY("Synth Pad",
  211. [INSTRUMENT("Pad 1 (new age)", 88),
  212. INSTRUMENT("Pad 2 (warm)", 89),
  213. INSTRUMENT("Pad 3 (polysynth)", 90),
  214. INSTRUMENT("Pad 4 (choir)", 91),
  215. INSTRUMENT("Pad 5 (bowed)", 92),
  216. INSTRUMENT("Pad 6 (metallic)", 93),
  217. INSTRUMENT("Pad 7 (halo)", 94),
  218. INSTRUMENT("Pad 8 (sweep)", 95)]),
  219. FAMILY("Synth Effects",
  220. [INSTRUMENT("FX 1 (rain)", 96),
  221. INSTRUMENT("FX 2 (soundtrack)", 97),
  222. INSTRUMENT("FX 3 (crystal)", 98),
  223. INSTRUMENT("FX 4 (atmosphere)", 99),
  224. INSTRUMENT("FX 5 (brightness)", 100),
  225. INSTRUMENT("FX 6 (goblins)", 101),
  226. INSTRUMENT("FX 7 (echoes)", 102),
  227. INSTRUMENT("FX 8 (sci-fi)", 103)]),
  228. FAMILY("Ethnic",
  229. [INSTRUMENT("Sitar", 104),
  230. INSTRUMENT("Banjo", 105),
  231. INSTRUMENT("Shamisen", 106),
  232. INSTRUMENT("Koto", 107),
  233. INSTRUMENT("Kalimba", 108),
  234. INSTRUMENT("Bagpipe", 109),
  235. INSTRUMENT("Fiddle", 110),
  236. INSTRUMENT("Shanai", 111)]),
  237. FAMILY("Percussive",
  238. [INSTRUMENT("Tinkle Bell", 112),
  239. INSTRUMENT("Agogo", 113),
  240. INSTRUMENT("Steel Drums", 114),
  241. INSTRUMENT("Woodblock", 115),
  242. INSTRUMENT("Taiko Drum", 116),
  243. INSTRUMENT("Melodic Tom", 117),
  244. INSTRUMENT("Synth Drum", 118),
  245. INSTRUMENT("Reverse Cymbal", 119)]),
  246. FAMILY("Sound Effects",
  247. [INSTRUMENT("Guitar Fret Noise", 120),
  248. INSTRUMENT("Breath Noise", 121),
  249. INSTRUMENT("Seashore", 122),
  250. INSTRUMENT("Bird Tweet", 123),
  251. INSTRUMENT("Telephone Ring", 124),
  252. INSTRUMENT("Helicopter", 125),
  253. INSTRUMENT("Applause", 126),
  254. INSTRUMENT("Gunshot", 127)])
  255. ];
  256. // Data for translating scan codes to octaves and notes
  257. struct Key
  258. {
  259. int iOctave;
  260. int iNote;
  261. int yPos;
  262. int xPos;
  263. string szKey;
  264. }
  265. Key[] key =
  266. [
  267. // Scan Char Oct Note
  268. // ---- ---- --- ----
  269. Key(-1, -1, -1, -1, null), // 0 None
  270. Key(-1, -1, -1, -1, null), // 1 Esc
  271. Key(-1, -1, 0, 0, ""), // 2 1
  272. Key(5, 1, 0, 2, "C#"), // 3 2 5 C#
  273. Key(5, 3, 0, 4, "D#"), // 4 3 5 D#
  274. Key(-1, -1, 0, 6, ""), // 5 4
  275. Key(5, 6, 0, 8, "F#"), // 6 5 5 F#
  276. Key(5, 8, 0, 10, "G#"), // 7 6 5 G#
  277. Key(5, 10, 0, 12, "A#"), // 8 7 5 A#
  278. Key(-1, -1, 0, 14, ""), // 9 8
  279. Key(6, 1, 0, 16, "C#"), // 10 9 6 C#
  280. Key(6, 3, 0, 18, "D#"), // 11 0 6 D#
  281. Key(-1, -1, 0, 20, ""), // 12 -
  282. Key(6, 6, 0, 22, "F#"), // 13 = 6 F#
  283. Key(-1, -1, -1, -1, null), // 14 Back
  284. Key(-1, -1, -1, -1, null), // 15 Tab
  285. Key(5, 0, 1, 1, "C"), // 16 q 5 C
  286. Key(5, 2, 1, 3, "D"), // 17 w 5 D
  287. Key(5, 4, 1, 5, "E"), // 18 e 5 E
  288. Key(5, 5, 1, 7, "F"), // 19 r 5 F
  289. Key(5, 7, 1, 9, "G"), // 20 t 5 G
  290. Key(5, 9, 1, 11, "A"), // 21 y 5 A
  291. Key(5, 11, 1, 13, "B"), // 22 u 5 B
  292. Key(6, 0, 1, 15, "C"), // 23 i 6 C
  293. Key(6, 2, 1, 17, "D"), // 24 o 6 D
  294. Key(6, 4, 1, 19, "E"), // 25 p 6 E
  295. Key(6, 5, 1, 21, "F"), // 26 [ 6 F
  296. Key(6, 7, 1, 23, "G"), // 27 ] 6 G
  297. Key(-1, -1, -1, -1, null), // 28 Ent
  298. Key(-1, -1, -1, -1, null), // 29 Ctrl
  299. Key(3, 8, 2, 2, "G#"), // 30 a 3 G#
  300. Key(3, 10, 2, 4, "A#"), // 31 s 3 A#
  301. Key(-1, -1, 2, 6, ""), // 32 d
  302. Key(4, 1, 2, 8, "C#"), // 33 f 4 C#
  303. Key(4, 3, 2, 10, "D#"), // 34 g 4 D#
  304. Key(-1, -1, 2, 12, ""), // 35 h
  305. Key(4, 6, 2, 14, "F#"), // 36 j 4 F#
  306. Key(4, 8, 2, 16, "G#"), // 37 k 4 G#
  307. Key(4, 10, 2, 18, "A#"), // 38 l 4 A#
  308. Key(-1, -1, 2, 20, ""), // 39 ;
  309. Key(5, 1, 2, 22, "C#"), // 40 ' 5 C#
  310. Key(-1, -1, -1, -1, null), // 41 `
  311. Key(-1, -1, -1, -1, null), // 42 Shift
  312. Key(-1, -1, -1, -1, null), // 43 \ (not line continuation)
  313. Key(3, 9, 3, 3, "A"), // 44 z 3 A
  314. Key(3, 11, 3, 5, "B"), // 45 x 3 B
  315. Key(4, 0, 3, 7, "C"), // 46 c 4 C
  316. Key(4, 2, 3, 9, "D"), // 47 v 4 D
  317. Key(4, 4, 3, 11, "E"), // 48 b 4 E
  318. Key(4, 5, 3, 13, "F"), // 49 n 4 F
  319. Key(4, 7, 3, 15, "G"), // 50 m 4 G
  320. Key(4, 9, 3, 17, "A"), // 51 , 4 A
  321. Key(4, 11, 3, 19, "B"), // 52 . 4 B
  322. Key(5, 0, 3, 21, "C") // 53 / 5 C
  323. ];
  324. // Create the program's menu (called from WndProc, WM_CREATE)
  325. HMENU CreateTheMenu(int iNumDevs)
  326. {
  327. string szBuffer;
  328. HMENU hMenu, hMenuPopup, hMenuSubPopup;
  329. int i, iFam, iIns;
  330. MIDIOUTCAPS moc;
  331. hMenu = CreateMenu();
  332. // Create "On/Off" popup menu
  333. hMenuPopup = CreateMenu();
  334. AppendMenu(hMenuPopup, MF_STRING, IDM_OPEN, "&Open");
  335. AppendMenu(hMenuPopup, MF_STRING | MF_CHECKED, IDM_CLOSE, "&Closed");
  336. AppendMenu(hMenu, MF_STRING | MF_POPUP, cast(UINT)hMenuPopup, "&Status");
  337. // Create "Device" popup menu
  338. hMenuPopup = CreateMenu();
  339. // Put MIDI Mapper on menu if it's installed
  340. if (!midiOutGetDevCaps(MIDIMAPPER, &moc, moc.sizeof))
  341. AppendMenu(hMenuPopup, MF_STRING, IDM_DEVICE + cast(int)MIDIMAPPER, moc.szPname.ptr);
  342. else
  343. iDevice = 0;
  344. // Add the rest of the MIDI devices
  345. for (i = 0; i < iNumDevs; i++)
  346. {
  347. midiOutGetDevCaps(i, &moc, moc.sizeof);
  348. AppendMenu(hMenuPopup, MF_STRING, IDM_DEVICE + i, moc.szPname.ptr);
  349. }
  350. CheckMenuItem(hMenuPopup, 0, MF_BYPOSITION | MF_CHECKED);
  351. AppendMenu(hMenu, MF_STRING | MF_POPUP, cast(UINT)hMenuPopup, "&Device");
  352. // Create "Channel" popup menu
  353. hMenuPopup = CreateMenu();
  354. for (i = 0; i < 16; i++)
  355. {
  356. szBuffer = format("%s", i+1);
  357. AppendMenu(hMenuPopup, MF_STRING | (i ? MF_UNCHECKED : MF_CHECKED), IDM_CHANNEL + i, szBuffer.toUTF16z);
  358. }
  359. AppendMenu(hMenu, MF_STRING | MF_POPUP, cast(UINT)hMenuPopup, "&Channel");
  360. // Create "Voice" popup menu
  361. hMenuPopup = CreateMenu();
  362. for (iFam = 0; iFam < 16; iFam++)
  363. {
  364. hMenuSubPopup = CreateMenu();
  365. for (iIns = 0; iIns < 8; iIns++)
  366. {
  367. szBuffer = format("&%s,\t%s", iIns + 1, fam[iFam].inst[iIns].szInst);
  368. AppendMenu(hMenuSubPopup,
  369. MF_STRING | (fam[iFam].inst[iIns].iVoice ?
  370. MF_UNCHECKED : MF_CHECKED),
  371. fam[iFam].inst[iIns].iVoice + IDM_VOICE,
  372. szBuffer.toUTF16z);
  373. }
  374. szBuffer = format("&%s.\t%s", cast(char)('A' + iFam), fam[iFam].szFam);
  375. AppendMenu(hMenuPopup, MF_STRING | MF_POPUP, cast(UINT)hMenuSubPopup, szBuffer.toUTF16z);
  376. }
  377. AppendMenu(hMenu, MF_STRING | MF_POPUP, cast(UINT)hMenuPopup, "&Voice");
  378. return hMenu;
  379. }
  380. // Routines for simplifying MIDI output
  381. DWORD MidiOutMessage(HMIDIOUT hMidi, int iStatus, int iChannel, int iData1, int iData2)
  382. {
  383. DWORD dwMessage;
  384. dwMessage = iStatus | iChannel | (iData1 << 8) | (iData2 << 16);
  385. return midiOutShortMsg(hMidi, dwMessage);
  386. }
  387. DWORD MidiNoteOff(HMIDIOUT hMidi, int iChannel, int iOct, int iNote, int iVel)
  388. {
  389. return MidiOutMessage(hMidi, 0x080, iChannel, 12 * iOct + iNote, iVel);
  390. }
  391. DWORD MidiNoteOn(HMIDIOUT hMidi, int iChannel, int iOct, int iNote, int iVel)
  392. {
  393. return MidiOutMessage(hMidi, 0x090, iChannel, 12 * iOct + iNote, iVel);
  394. }
  395. DWORD MidiSetPatch(HMIDIOUT hMidi, int iChannel, int iVoice)
  396. {
  397. return MidiOutMessage(hMidi, 0x0C0, iChannel, iVoice, 0);
  398. }
  399. DWORD MidiPitchBend(HMIDIOUT hMidi, int iChannel, int iBend)
  400. {
  401. return MidiOutMessage(hMidi, 0x0E0, iChannel, iBend & 0x7F, iBend >> 7);
  402. }
  403. // Draw a single key on window
  404. VOID DrawKey(HDC hdc, int iScanCode, BOOL fInvert)
  405. {
  406. RECT rc;
  407. rc.left = 3 * cxCaps * key[iScanCode].xPos / 2 + xOffset;
  408. rc.top = 3 * cyChar * key[iScanCode].yPos / 2 + yOffset;
  409. rc.right = rc.left + 3 * cxCaps;
  410. rc.bottom = rc.top + 3 * cyChar / 2;
  411. SetTextColor(hdc, fInvert ? 0x00FF_FFFF : 0x0000_0000);
  412. SetBkColor(hdc, fInvert ? 0x0000_0000 : 0x00FF_FFFF);
  413. FillRect(hdc, &rc, GetStockObject(fInvert ? BLACK_BRUSH : WHITE_BRUSH));
  414. DrawText(hdc, key[iScanCode].szKey.toUTF16z, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  415. FrameRect(hdc, &rc, GetStockObject(BLACK_BRUSH));
  416. }
  417. // Process a Key Up or Key Down message
  418. VOID ProcessKey(HDC hdc, UINT message, LPARAM lParam)
  419. {
  420. int iScanCode, iOctave, iNote;
  421. iScanCode = 0x0FF & HIWORD(lParam);
  422. if (iScanCode >= key.length) // No scan codes over 53
  423. return;
  424. if ((iOctave = key[iScanCode].iOctave) == -1) // Non-music key
  425. return;
  426. if (GetKeyState(VK_SHIFT) < 0)
  427. iOctave += 0x20000000 & lParam ? 2 : 1;
  428. if (GetKeyState(VK_CONTROL) < 0)
  429. iOctave -= 0x20000000 & lParam ? 2 : 1;
  430. iNote = key[iScanCode].iNote;
  431. if (message == WM_KEYUP) // For key up
  432. {
  433. MidiNoteOff(hMidiOut, iChannel, iOctave, iNote, 0); // Note off
  434. DrawKey(hdc, iScanCode, FALSE);
  435. return;
  436. }
  437. if (0x40000000 & lParam) // ignore typematics
  438. return;
  439. MidiNoteOn(hMidiOut, iChannel, iOctave, iNote, iVelocity); // Note on
  440. DrawKey(hdc, iScanCode, TRUE); // Draw the inverted key
  441. }
  442. extern (Windows)
  443. LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) nothrow
  444. {
  445. scope (failure) assert(0);
  446. static BOOL bOpened = FALSE;
  447. HDC hdc;
  448. HMENU hMenu;
  449. int i, iNumDevs, iPitchBend, cxClient, cyClient;
  450. MIDIOUTCAPS moc;
  451. PAINTSTRUCT ps;
  452. SIZE size;
  453. string szBuffer;
  454. switch (message)
  455. {
  456. case WM_CREATE:
  457. // Get size of capital letters in system font
  458. hdc = GetDC(hwnd);
  459. GetTextExtentPoint(hdc, "M", 1, &size);
  460. cxCaps = size.cx;
  461. cyChar = size.cy;
  462. ReleaseDC(hwnd, hdc);
  463. // Initialize "Volume" scroll bar
  464. SetScrollRange(hwnd, SB_HORZ, 1, 127, FALSE);
  465. SetScrollPos(hwnd, SB_HORZ, iVelocity, TRUE);
  466. // Initialize "Pitch Bend" scroll bar
  467. SetScrollRange(hwnd, SB_VERT, 0, 16383, FALSE);
  468. SetScrollPos(hwnd, SB_VERT, 8192, TRUE);
  469. // Get number of MIDI output devices and set up menu
  470. iNumDevs = midiOutGetNumDevs();
  471. if (iNumDevs == 0)
  472. {
  473. MessageBeep(MB_ICONSTOP);
  474. MessageBox(hwnd, "No MIDI output devices!", appName.toUTF16z, MB_OK | MB_ICONSTOP);
  475. return -1;
  476. }
  477. SetMenu(hwnd, CreateTheMenu(iNumDevs));
  478. return 0;
  479. case WM_SIZE:
  480. cxClient = LOWORD(lParam);
  481. cyClient = HIWORD(lParam);
  482. xOffset = (cxClient - 25 * 3 * cxCaps / 2) / 2;
  483. yOffset = (cyClient - 11 * cyChar) / 2 + 5 * cyChar;
  484. return 0;
  485. case WM_COMMAND:
  486. hMenu = GetMenu(hwnd);
  487. // "Open" menu command
  488. if (LOWORD(wParam) == IDM_OPEN && !bOpened)
  489. {
  490. if (midiOutOpen(&hMidiOut, iDevice, 0, 0, 0))
  491. {
  492. MessageBeep(MB_ICONEXCLAMATION);
  493. MessageBox(hwnd, "Cannot open MIDI device",
  494. appName.toUTF16z, MB_OK | MB_ICONEXCLAMATION);
  495. }
  496. else
  497. {
  498. CheckMenuItem(hMenu, IDM_OPEN, MF_CHECKED);
  499. CheckMenuItem(hMenu, IDM_CLOSE, MF_UNCHECKED);
  500. MidiSetPatch(hMidiOut, iChannel, iVoice);
  501. bOpened = TRUE;
  502. }
  503. }
  504. // "Close" menu command
  505. else if (LOWORD(wParam) == IDM_CLOSE && bOpened)
  506. {
  507. CheckMenuItem(hMenu, IDM_OPEN, MF_UNCHECKED);
  508. CheckMenuItem(hMenu, IDM_CLOSE, MF_CHECKED);
  509. // Turn all keys off and close device
  510. for (i = 0; i < 16; i++)
  511. MidiOutMessage(hMidiOut, 0xB0, i, 123, 0);
  512. midiOutClose(hMidiOut);
  513. bOpened = FALSE;
  514. }
  515. // Change MIDI "Device" menu command
  516. else if (LOWORD(wParam) >= IDM_DEVICE - 1 &&
  517. LOWORD(wParam) < IDM_CHANNEL)
  518. {
  519. CheckMenuItem(hMenu, IDM_DEVICE + iDevice, MF_UNCHECKED);
  520. iDevice = LOWORD(wParam) - IDM_DEVICE;
  521. CheckMenuItem(hMenu, IDM_DEVICE + iDevice, MF_CHECKED);
  522. // Close and reopen MIDI device
  523. if (bOpened)
  524. {
  525. SendMessage(hwnd, WM_COMMAND, IDM_CLOSE, 0L);
  526. SendMessage(hwnd, WM_COMMAND, IDM_OPEN, 0L);
  527. }
  528. }
  529. // Change MIDI "Channel" menu command
  530. else if (LOWORD(wParam) >= IDM_CHANNEL &&
  531. LOWORD(wParam) < IDM_VOICE)
  532. {
  533. CheckMenuItem(hMenu, IDM_CHANNEL + iChannel, MF_UNCHECKED);
  534. iChannel = LOWORD(wParam) - IDM_CHANNEL;
  535. CheckMenuItem(hMenu, IDM_CHANNEL + iChannel, MF_CHECKED);
  536. if (bOpened)
  537. MidiSetPatch(hMidiOut, iChannel, iVoice);
  538. }
  539. // Change MIDI "Voice" menu command
  540. else if (LOWORD(wParam) >= IDM_VOICE)
  541. {
  542. CheckMenuItem(hMenu, IDM_VOICE + iVoice, MF_UNCHECKED);
  543. iVoice = LOWORD(wParam) - IDM_VOICE;
  544. CheckMenuItem(hMenu, IDM_VOICE + iVoice, MF_CHECKED);
  545. if (bOpened)
  546. MidiSetPatch(hMidiOut, iChannel, iVoice);
  547. }
  548. InvalidateRect(hwnd, NULL, TRUE);
  549. return 0;
  550. // Process a Key Up or Key Down message
  551. case WM_KEYUP:
  552. case WM_KEYDOWN:
  553. hdc = GetDC(hwnd);
  554. if (bOpened)
  555. ProcessKey(hdc, message, lParam);
  556. ReleaseDC(hwnd, hdc);
  557. return 0;
  558. // For Escape, turn off all notes and repaint
  559. case WM_CHAR:
  560. if (bOpened && wParam == 27)
  561. {
  562. for (i = 0; i < 16; i++)
  563. MidiOutMessage(hMidiOut, 0xB0, i, 123, 0);
  564. InvalidateRect(hwnd, NULL, TRUE);
  565. }
  566. return 0;
  567. // Horizontal scroll: Velocity
  568. case WM_HSCROLL:
  569. switch (LOWORD(wParam))
  570. {
  571. case SB_LINEUP:
  572. iVelocity -= 1; break;
  573. case SB_LINEDOWN:
  574. iVelocity += 1; break;
  575. case SB_PAGEUP:
  576. iVelocity -= 8; break;
  577. case SB_PAGEDOWN:
  578. iVelocity += 8; break;
  579. case SB_THUMBPOSITION:
  580. iVelocity = HIWORD(wParam); break;
  581. default:
  582. return 0;
  583. }
  584. iVelocity = max(1, min(iVelocity, 127));
  585. SetScrollPos(hwnd, SB_HORZ, iVelocity, TRUE);
  586. return 0;
  587. // Vertical scroll: Pitch Bend
  588. case WM_VSCROLL:
  589. switch (LOWORD(wParam))
  590. {
  591. case SB_THUMBTRACK:
  592. iPitchBend = 16383 - HIWORD(wParam); break;
  593. case SB_THUMBPOSITION:
  594. iPitchBend = 8191; break;
  595. default:
  596. return 0;
  597. }
  598. iPitchBend = max(0, min(iPitchBend, 16383));
  599. SetScrollPos(hwnd, SB_VERT, 16383 - iPitchBend, TRUE);
  600. if (bOpened)
  601. MidiPitchBend(hMidiOut, iChannel, iPitchBend);
  602. return 0;
  603. case WM_PAINT:
  604. hdc = BeginPaint(hwnd, &ps);
  605. for (i = 0; i < key.length; i++)
  606. if (key[i].xPos != -1)
  607. DrawKey(hdc, i, FALSE);
  608. midiOutGetDevCaps(iDevice, &moc, MIDIOUTCAPS.sizeof);
  609. szBuffer = format("Channel %s", iChannel + 1);
  610. TextOut(hdc, cxCaps, 1 * cyChar,
  611. bOpened ? ("Open\0"w.dup.ptr) : ("Closed\0"w.dup.ptr),
  612. bOpened ? 4 : 6);
  613. auto deviceName = to!string(fromWStringz(moc.szPname.ptr));
  614. TextOut(hdc, cxCaps, 2 * cyChar, deviceName.toUTF16z, deviceName.count);
  615. TextOut(hdc, cxCaps, 2 * cyChar, moc.szPname.ptr, 0);
  616. TextOut(hdc, cxCaps, 3 * cyChar, szBuffer.toUTF16z, szBuffer.count);
  617. TextOut(hdc, cxCaps, 4 * cyChar,
  618. fam[iVoice / 8].inst[iVoice % 8].szInst.toUTF16z,
  619. fam[iVoice / 8].inst[iVoice % 8].szInst.count);
  620. EndPaint(hwnd, &ps);
  621. return 0;
  622. case WM_DESTROY:
  623. SendMessage(hwnd, WM_COMMAND, IDM_CLOSE, 0L);
  624. PostQuitMessage(0);
  625. return 0;
  626. default:
  627. }
  628. return DefWindowProc(hwnd, message, wParam, lParam);
  629. }
  630. wstring fromWStringz(const wchar* s)
  631. {
  632. if (s is null) return null;
  633. wchar* ptr;
  634. for (ptr = cast(wchar*)s; *ptr; ++ptr) {}
  635. return to!wstring(s[0..ptr-s]);
  636. }