PageRenderTime 52ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/Samples/Chap22/Drum/Drum.d

http://github.com/AndrejMitrovic/DWinProgramming
D | 596 lines | 440 code | 133 blank | 23 comment | 58 complexity | cb26744988ca205c4f9fb17b469874a2 MD5 | raw file
  1. /+
  2. + Copyright (c) Charles Petzold, 1998.
  3. + Ported to the D Programming Language by Andrej Mitrovic, 2011.
  4. +/
  5. module Drum;
  6. import core.memory;
  7. import core.runtime;
  8. import core.thread;
  9. import std.algorithm : min, max;
  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. import resource;
  29. string appName = "Drum";
  30. string description = "MIDI Drum Machine";
  31. HINSTANCE hinst;
  32. extern (Windows)
  33. int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  34. {
  35. int result;
  36. try
  37. {
  38. Runtime.initialize();
  39. result = myWinMain(hInstance, hPrevInstance, lpCmdLine, iCmdShow);
  40. Runtime.terminate();
  41. }
  42. catch (Throwable o)
  43. {
  44. MessageBox(null, o.toString().toUTF16z, "Error", MB_OK | MB_ICONEXCLAMATION);
  45. result = 0;
  46. }
  47. return result;
  48. }
  49. int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
  50. {
  51. hinst = hInstance;
  52. HACCEL hAccel;
  53. HWND hwnd;
  54. MSG msg;
  55. WNDCLASS wndclass;
  56. wndclass.style = CS_HREDRAW | CS_VREDRAW;
  57. wndclass.lpfnWndProc = &WndProc;
  58. wndclass.cbClsExtra = 0;
  59. wndclass.cbWndExtra = 0;
  60. wndclass.hInstance = hInstance;
  61. wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  62. wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
  63. wndclass.hbrBackground = cast(HBRUSH)GetStockObject(WHITE_BRUSH);
  64. wndclass.lpszMenuName = appName.toUTF16z;
  65. wndclass.lpszClassName = appName.toUTF16z;
  66. if (!RegisterClass(&wndclass))
  67. {
  68. MessageBox(NULL, "This program requires Windows NT!", appName.toUTF16z, MB_ICONERROR);
  69. return 0;
  70. }
  71. hwnd = CreateWindow(appName.toUTF16z, // window class name
  72. description.toUTF16z, // window caption
  73. WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_SYSMENU |
  74. WS_MINIMIZEBOX | WS_HSCROLL | WS_VSCROLL, // window style
  75. CW_USEDEFAULT, // initial x position
  76. CW_USEDEFAULT, // initial y position
  77. CW_USEDEFAULT, // initial x size
  78. CW_USEDEFAULT, // initial y size
  79. NULL, // parent window handle
  80. NULL, // window menu handle
  81. hInstance, // program instance handle
  82. lpCmdLine); // creation parameters
  83. ShowWindow(hwnd, iCmdShow);
  84. UpdateWindow(hwnd);
  85. while (GetMessage(&msg, NULL, 0, 0))
  86. {
  87. TranslateMessage(&msg);
  88. DispatchMessage(&msg);
  89. }
  90. return msg.wParam;
  91. }
  92. import DrumTime;
  93. import DrumFile;
  94. string[NUM_PERC] szPerc =
  95. [
  96. "Acoustic Bass Drum", "Bass Drum 1",
  97. "Side Stick", "Acoustic Snare",
  98. "Hand Clap", "Electric Snare",
  99. "Low Floor Tom", "Closed High Hat",
  100. "High Floor Tom", "Pedal High Hat",
  101. "Low Tom", "Open High Hat",
  102. "Low-Mid Tom", "High-Mid Tom",
  103. "Crash Cymbal 1", "High Tom",
  104. "Ride Cymbal 1", "Chinese Cymbal",
  105. "Ride Bell", "Tambourine",
  106. "Splash Cymbal", "Cowbell",
  107. "Crash Cymbal 2", "Vibraslap",
  108. "Ride Cymbal 2", "High Bongo",
  109. "Low Bongo", "Mute High Conga",
  110. "Open High Conga", "Low Conga",
  111. "High Timbale", "Low Timbale",
  112. "High Agogo", "Low Agogo",
  113. "Cabasa", "Maracas",
  114. "Short Whistle", "Long Whistle",
  115. "Short Guiro", "Long Guiro",
  116. "Claves", "High Wood Block",
  117. "Low Wood Block", "Mute Cuica",
  118. "Open Cuica", "Mute Triangle",
  119. "Open Triangle"
  120. ];
  121. string szUntitled = "(Untitled)";
  122. string szBuffer;
  123. HANDLE hInst;
  124. int cxChar, cyChar;
  125. __gshared wchar[MAX_PATH] szFileName = 0;
  126. __gshared wchar[MAX_PATH] szTitleName = 0;
  127. __gshared BOOL bNeedSave;
  128. __gshared DRUM drum;
  129. __gshared HMENU hMenu;
  130. extern (Windows)
  131. LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) nothrow
  132. {
  133. scope (failure) assert(0);
  134. static int iTempo = 50, iIndexLast;
  135. HDC hdc;
  136. int i, x, y;
  137. PAINTSTRUCT ps;
  138. POINT point;
  139. RECT rect;
  140. TCHAR* szError;
  141. switch (message)
  142. {
  143. case WM_CREATE:
  144. // Initialize DRUM structure
  145. drum.iMsecPerBeat = 100;
  146. drum.iVelocity = 64;
  147. drum.iNumBeats = 32;
  148. DrumSetParams(&drum);
  149. // Other initialization
  150. cxChar = LOWORD(GetDialogBaseUnits());
  151. cyChar = HIWORD(GetDialogBaseUnits());
  152. GetWindowRect(hwnd, &rect);
  153. MoveWindow(hwnd, rect.left, rect.top, 77 * cxChar, 29 * cyChar, FALSE);
  154. hMenu = GetMenu(hwnd);
  155. // Initialize "Volume" scroll bar
  156. SetScrollRange(hwnd, SB_HORZ, 1, 127, FALSE);
  157. SetScrollPos(hwnd, SB_HORZ, drum.iVelocity, TRUE);
  158. // Initialize "Tempo" scroll bar
  159. SetScrollRange(hwnd, SB_VERT, 0, 100, FALSE);
  160. SetScrollPos(hwnd, SB_VERT, iTempo, TRUE);
  161. DoCaption(hwnd, to!string(fromWStringz(szTitleName.ptr)));
  162. return 0;
  163. case WM_COMMAND:
  164. switch (LOWORD(wParam))
  165. {
  166. case IDM_FILE_NEW:
  167. if (bNeedSave && IDCANCEL == AskAboutSave(hwnd, to!string(fromWStringz(szTitleName.ptr))))
  168. return 0;
  169. // Clear drum pattern
  170. for (i = 0; i < NUM_PERC; i++)
  171. {
  172. drum.dwSeqPerc [i] = 0;
  173. drum.dwSeqPian [i] = 0;
  174. }
  175. InvalidateRect(hwnd, NULL, FALSE);
  176. DrumSetParams(&drum);
  177. bNeedSave = FALSE;
  178. return 0;
  179. case IDM_FILE_OPEN:
  180. // Save previous file
  181. if (bNeedSave && IDCANCEL == AskAboutSave(hwnd, to!string(fromWStringz(szTitleName.ptr))))
  182. return 0;
  183. szFileName = 0;
  184. szTitleName = 0;
  185. // Open a drm file
  186. if (DrumFileOpenDlg(hwnd, szFileName.ptr, szTitleName.ptr))
  187. {
  188. szError = DrumFileRead(&drum, szFileName.ptr);
  189. if (szError != NULL)
  190. {
  191. ErrorMessage(hwnd, to!string(fromWStringz(szError)), to!string(fromWStringz(szTitleName.ptr)));
  192. szTitleName[0] = 0;
  193. }
  194. else
  195. {
  196. // Set new parameters
  197. iTempo = cast(int)(50 * (log10(drum.iMsecPerBeat) - 1));
  198. SetScrollPos(hwnd, SB_VERT, iTempo, TRUE);
  199. SetScrollPos(hwnd, SB_HORZ, drum.iVelocity, TRUE);
  200. DrumSetParams(&drum);
  201. InvalidateRect(hwnd, NULL, FALSE);
  202. bNeedSave = FALSE;
  203. }
  204. DoCaption(hwnd, to!string(fromWStringz(szTitleName.ptr)));
  205. }
  206. return 0;
  207. case IDM_FILE_SAVE:
  208. case IDM_FILE_SAVE_AS:
  209. // Save the selected file
  210. if ((LOWORD(wParam) == IDM_FILE_SAVE && szTitleName[0]) ||
  211. DrumFileSaveDlg(hwnd, szFileName.ptr, szTitleName.ptr))
  212. {
  213. szError = DrumFileWrite(&drum, szFileName.ptr);
  214. if (szError != NULL)
  215. {
  216. ErrorMessage(hwnd, to!string(fromWStringz(szError)), to!string(fromWStringz(szTitleName.ptr)));
  217. szTitleName[0] = 0;
  218. }
  219. else
  220. bNeedSave = FALSE;
  221. DoCaption(hwnd, to!string(fromWStringz(szTitleName.ptr)));
  222. }
  223. return 0;
  224. case IDM_APP_EXIT:
  225. SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
  226. return 0;
  227. case IDM_SEQUENCE_RUNNING:
  228. // Begin sequence
  229. if (!DrumBeginSequence(hwnd))
  230. {
  231. ErrorMessage(hwnd,
  232. "Could not start MIDI sequence -- MIDI Mapper device is unavailable!",
  233. to!string(fromWStringz(szTitleName.ptr)));
  234. }
  235. else
  236. {
  237. CheckMenuItem(hMenu, IDM_SEQUENCE_RUNNING, MF_CHECKED);
  238. CheckMenuItem(hMenu, IDM_SEQUENCE_STOPPED, MF_UNCHECKED);
  239. }
  240. return 0;
  241. case IDM_SEQUENCE_STOPPED:
  242. // Finish at end of sequence
  243. DrumEndSequence(FALSE);
  244. return 0;
  245. case IDM_APP_ABOUT:
  246. DialogBox(hInst, "AboutBox", hwnd, &AboutProc);
  247. return 0;
  248. default:
  249. }
  250. return 0;
  251. case WM_LBUTTONDOWN:
  252. case WM_RBUTTONDOWN:
  253. hdc = GetDC(hwnd);
  254. // Convert mouse coordinates to grid coordinates
  255. x = LOWORD(lParam) / cxChar - 40;
  256. y = 2 * HIWORD(lParam) / cyChar - 2;
  257. // Set a new number of beats of sequence
  258. if (x > 0 && x <= 32 && y < 0)
  259. {
  260. SetTextColor(hdc, RGB(255, 255, 255));
  261. TextOut(hdc, (40 + drum.iNumBeats) * cxChar, 0, ":|", 2);
  262. SetTextColor(hdc, RGB(0, 0, 0));
  263. if (drum.iNumBeats % 4 == 0)
  264. TextOut(hdc, (40 + drum.iNumBeats) * cxChar, 0, ".", 1);
  265. drum.iNumBeats = cast(short)x;
  266. TextOut(hdc, (40 + drum.iNumBeats) * cxChar, 0, ":|", 2);
  267. bNeedSave = TRUE;
  268. }
  269. // Set or reset a percussion instrument beat
  270. if (x >= 0 && x < 32 && y >= 0 && y < NUM_PERC)
  271. {
  272. if (message == WM_LBUTTONDOWN)
  273. drum.dwSeqPerc[y] ^= (1 << x);
  274. else
  275. drum.dwSeqPian[y] ^= (1 << x);
  276. DrawRectangle(hdc, x, y, drum.dwSeqPerc.ptr, drum.dwSeqPian.ptr);
  277. bNeedSave = TRUE;
  278. }
  279. ReleaseDC(hwnd, hdc);
  280. DrumSetParams(&drum);
  281. return 0;
  282. case WM_HSCROLL:
  283. // Change the note velocity
  284. switch (LOWORD(wParam))
  285. {
  286. case SB_LINEUP:
  287. drum.iVelocity -= 1; break;
  288. case SB_LINEDOWN:
  289. drum.iVelocity += 1; break;
  290. case SB_PAGEUP:
  291. drum.iVelocity -= 8; break;
  292. case SB_PAGEDOWN:
  293. drum.iVelocity += 8; break;
  294. case SB_THUMBPOSITION:
  295. drum.iVelocity = HIWORD(wParam);
  296. break;
  297. default:
  298. return 0;
  299. }
  300. drum.iVelocity = cast(short)max(1, min(drum.iVelocity, 127));
  301. SetScrollPos(hwnd, SB_HORZ, drum.iVelocity, TRUE);
  302. DrumSetParams(&drum);
  303. bNeedSave = TRUE;
  304. return 0;
  305. case WM_VSCROLL:
  306. // Change the tempo
  307. switch (LOWORD(wParam))
  308. {
  309. case SB_LINEUP:
  310. iTempo -= 1; break;
  311. case SB_LINEDOWN:
  312. iTempo += 1; break;
  313. case SB_PAGEUP:
  314. iTempo -= 10; break;
  315. case SB_PAGEDOWN:
  316. iTempo += 10; break;
  317. case SB_THUMBPOSITION:
  318. iTempo = HIWORD(wParam);
  319. break;
  320. default:
  321. return 0;
  322. }
  323. iTempo = max(0, min(iTempo, 100));
  324. SetScrollPos(hwnd, SB_VERT, iTempo, TRUE);
  325. drum.iMsecPerBeat = cast(WORD)(10 * pow(100, iTempo / 100.0));
  326. DrumSetParams(&drum);
  327. bNeedSave = TRUE;
  328. return 0;
  329. case WM_PAINT:
  330. hdc = BeginPaint(hwnd, &ps);
  331. SetTextAlign(hdc, TA_UPDATECP);
  332. SetBkMode(hdc, TRANSPARENT);
  333. // Draw the text strings and horizontal lines
  334. for (i = 0; i < NUM_PERC; i++)
  335. {
  336. MoveToEx(hdc, i & 1 ? 20 * cxChar : cxChar,
  337. (2 * i + 3) * cyChar / 4, NULL);
  338. TextOut(hdc, 0, 0, szPerc[i].toUTF16z, szPerc[i].count);
  339. GetCurrentPositionEx(hdc, &point);
  340. MoveToEx(hdc, point.x + cxChar, point.y + cyChar / 2, NULL);
  341. LineTo(hdc, 39 * cxChar, point.y + cyChar / 2);
  342. }
  343. SetTextAlign(hdc, 0);
  344. // Draw rectangular grid, repeat mark, and beat marks
  345. for (x = 0; x < 32; x++)
  346. {
  347. for (y = 0; y < NUM_PERC; y++)
  348. DrawRectangle(hdc, x, y, drum.dwSeqPerc.ptr, drum.dwSeqPian.ptr);
  349. SetTextColor(hdc, x == drum.iNumBeats - 1 ?
  350. RGB(0, 0, 0) : RGB(255, 255, 255));
  351. TextOut(hdc, (41 + x) * cxChar, 0, ":|", 2);
  352. SetTextColor(hdc, RGB(0, 0, 0));
  353. if (x % 4 == 0)
  354. TextOut(hdc, (40 + x) * cxChar, 0, ".", 1);
  355. }
  356. EndPaint(hwnd, &ps);
  357. return 0;
  358. case WM_USER_NOTIFY:
  359. // Draw the "bouncing ball"
  360. hdc = GetDC(hwnd);
  361. SelectObject(hdc, GetStockObject(NULL_PEN));
  362. SelectObject(hdc, GetStockObject(WHITE_BRUSH));
  363. for (i = 0; i < 2; i++)
  364. {
  365. x = iIndexLast;
  366. y = NUM_PERC + 1;
  367. Ellipse(hdc, (x + 40) * cxChar, (2 * y + 3) * cyChar / 4,
  368. (x + 41) * cxChar, (2 * y + 5) * cyChar / 4);
  369. iIndexLast = wParam;
  370. SelectObject(hdc, GetStockObject(BLACK_BRUSH));
  371. }
  372. ReleaseDC(hwnd, hdc);
  373. return 0;
  374. case WM_USER_ERROR:
  375. ErrorMessage(hwnd, "Can't set timer event for tempo",
  376. to!string(fromWStringz(szTitleName.ptr)));
  377. goto case;
  378. case WM_USER_FINISHED:
  379. DrumEndSequence(TRUE);
  380. CheckMenuItem(hMenu, IDM_SEQUENCE_RUNNING, MF_UNCHECKED);
  381. CheckMenuItem(hMenu, IDM_SEQUENCE_STOPPED, MF_CHECKED);
  382. return 0;
  383. case WM_CLOSE:
  384. if (!bNeedSave || IDCANCEL != AskAboutSave(hwnd, to!string(fromWStringz(szTitleName.ptr))))
  385. DestroyWindow(hwnd);
  386. return 0;
  387. case WM_QUERYENDSESSION:
  388. if (!bNeedSave || IDCANCEL != AskAboutSave(hwnd, to!string(fromWStringz(szTitleName.ptr))))
  389. return 1L;
  390. return 0;
  391. case WM_DESTROY:
  392. DrumEndSequence(TRUE);
  393. PostQuitMessage(0);
  394. return 0;
  395. default:
  396. }
  397. return DefWindowProc(hwnd, message, wParam, lParam);
  398. }
  399. extern (Windows)
  400. BOOL AboutProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  401. {
  402. switch (message)
  403. {
  404. case WM_INITDIALOG:
  405. return TRUE;
  406. case WM_COMMAND:
  407. switch (LOWORD(wParam))
  408. {
  409. case IDOK:
  410. EndDialog(hDlg, 0);
  411. return TRUE;
  412. default:
  413. }
  414. break;
  415. default:
  416. }
  417. return FALSE;
  418. }
  419. void DrawRectangle(HDC hdc, int x, int y, DWORD* dwSeqPerc, DWORD* dwSeqPian)
  420. {
  421. int iBrush;
  422. if (dwSeqPerc[y] & dwSeqPian[y] & (1L << x))
  423. iBrush = BLACK_BRUSH;
  424. else if (dwSeqPerc [y] & (1L << x))
  425. iBrush = DKGRAY_BRUSH;
  426. else if (dwSeqPian [y] & (1L << x))
  427. iBrush = LTGRAY_BRUSH;
  428. else
  429. iBrush = WHITE_BRUSH;
  430. SelectObject(hdc, GetStockObject(iBrush));
  431. Rectangle(hdc, (x + 40) * cxChar, (2 * y + 4) * cyChar / 4,
  432. (x + 41) * cxChar + 1, (2 * y + 6) * cyChar / 4 + 1);
  433. }
  434. wstring fromWStringz(const wchar* s)
  435. {
  436. if (s is null) return null;
  437. wchar* ptr;
  438. for (ptr = cast(wchar*)s; *ptr; ++ptr) {}
  439. return to!wstring(s[0..ptr-s]);
  440. }
  441. void ErrorMessage(HWND hwnd, string szError, string szTitleName)
  442. {
  443. szBuffer = format(szError, (szTitleName.length ? szTitleName : szUntitled));
  444. MessageBeep(MB_ICONEXCLAMATION);
  445. MessageBox(hwnd, szBuffer.toUTF16z, appName.toUTF16z, MB_OK | MB_ICONEXCLAMATION);
  446. }
  447. void DoCaption(HWND hwnd, string szTitleName)
  448. {
  449. szBuffer = format("MIDI Drum Machine - %s", (szTitleName.length ? szTitleName : szUntitled));
  450. SetWindowText(hwnd, szBuffer.toUTF16z);
  451. }
  452. int AskAboutSave(HWND hwnd, string szTitleName)
  453. {
  454. int iReturn;
  455. szBuffer = format("Save current changes in %s?", (szTitleName.length ? szTitleName : szUntitled));
  456. iReturn = MessageBox(hwnd, szBuffer.toUTF16z, appName.toUTF16z, MB_YESNOCANCEL | MB_ICONQUESTION);
  457. if (iReturn == IDYES)
  458. {
  459. if (!SendMessage(hwnd, WM_COMMAND, IDM_FILE_SAVE, 0))
  460. iReturn = IDCANCEL;
  461. }
  462. return iReturn;
  463. }