Metronome in asm
I built this initially out of a lack of a real metronome, but then I thought it was probably about time I actually did a proper step by step asm tutorial. Comments will eventually come into the code, but for now all I can tell you is that it's assembled using masm version 8.

Have fun
NoFriLLz

.586
.model flat,stdcall
option casemapnone

include windows.inc
include user32.inc
include kernel32.inc ;here we include all the dlls we intend to use
include gdi32.inc

includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib


WinMain proto DWORD,DWORD,DWORD,DWORD ;prototype for WinMain()

WNDX equ 240 ;set size of window as constants so resizing takes
WNDY equ 85 ;only one step

.data
ClassName db MainWinClass,0
AppName db Metronoma,0
ButtonClassName db BUTTON,0
EditClassName db EDIT,0
StaticClassName db STATIC,0 ;set all our data here
lpDoMetro db Set,0
lpStop db Stop,0
GotButton db W,0
lpLu db %lu,0
bignum dd 60000
lpCrt db crtdll,0
lpAtoi db atoi,0
lpHtClient db HTCLIENT,0
lpQuit db Quit,0
lpBadRect db Couldn't get rectangular region,0

.data
hInstance HINSTANCE
CommandLine LPSTR
hwndEdit DWORD
hwndDoMetro DWORD
hwndStop DWORD ;space for handles & buffers
hwndQuit DWORD
roomforstuff db 512 dup()
speed DWORD
timerId DWORD
ddCrt DWORD
ddAtoi DWORD
MousePos POINT

.code


; ---------------------------------------------------------------------------


start
invoke GetModuleHandle, NULL
mov hInstance,eax

invoke GetCommandLine
mov CommandLine,eax

invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInstHINSTANCE,hPrevInstHINSTANCE,CmdLineLPSTR,CmdShowDWORD
LOCAL wcWNDCLASSEX
LOCAL msgMSG
LOCAL hwndHWND

mov wc.cbSize,SIZEOF WNDCLASSEX ;This is all standard window creation stuff
mov wc.style, CS_HREDRAW or CS_VREDRAW ; - it usually comes with a win32 window template
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName

invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax

invoke RegisterClassEx, addr wc


;Here we create our window to our specifications WS_POPUP means no border, WS_EX_TOPMOST means
;always on top, ClassName is the class defined above, AppName is the title of the window.
;Notice for window size we use the constants WNDX and WNDY from the start


INVOKE CreateWindowEx,WS_EX_TOPMOST,ADDR ClassName,ADDR AppName,
WS_POPUP,100,
100,WNDX,WNDY,NULL,NULL,
hInst,NULL
mov hwnd,eax

;This simply displays the window for us

invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd

;Message processing loop, sends it onto WndProc()

.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWndHWND, uMsgUINT, wParamWPARAM, lParamLPARAM
LOCAL hdcHDC
LOCAL psPAINTSTRUCT
LOCAL hrgnHRGN

.IF uMsg==WM_DESTROY
;This is space for anything that needs cleaning up at close-time

invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE

;This message is sent to the window when first created (duh)
;It is useful for initialising anything the window may need

;Here we create our buttons and edit box

invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or
ES_AUTOHSCROLL,
15,15,210,25,hWnd,1,hInstance,NULL
mov hwndEdit,eax
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR lpDoMetro,
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,
15,50,50,25,hWnd,2,hInstance,NULL
mov hwndDoMetro,eax

invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR lpStop,
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,
75,50,50,25,hWnd,3,hInstance,NULL
mov hwndStop,eax

invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR lpQuit,
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,
175,50,50,25,hWnd,4,hInstance,NULL
mov hwndQuit,eax

;This is to load crtdll.dll which has the text-to-number function atoi()
;because masm32 has no libs or includes for it

invoke LoadLibrary,offset lpCrt
mov ddCrt,eax
invoke GetProcAddress,ddCrt,offset lpAtoi
mov ddAtoi,eax

.ELSEIF uMsg==WM_COMMAND

;This message is for when a button is clicked, or the enter button pressed

mov eax,wParam
shr eax,16
.if ax==BN_CLICKED
;If a button is clicked

mov edx,lParam

;Compare the window handle of each control with the one sent to us

.if edx==hwndDoMetro
invoke SendMessage,hwndEdit,WM_GETTEXT,4,offset roomforstuff
push offset roomforstuff
mov eax,offset ddAtoi
call dword ptr [eax]
mov speed,eax
invoke KillTimer,hWnd,1 ;This resets any previous timer
fild dword ptr [bignum] ;and calculates bpm601000
fidiv dword ptr [speed] ;to give us an interval in milliseconds
fistp dword ptr [speed] ;using the FPU integer functions
invoke SetTimer,hWnd,1,speed,0

;This will send the message WM_TIMER when we need a metronome beat

.elseif edx==hwndStop
invoke KillTimer,hWnd,1 ;Stops any previous timer
.elseif edx==hwndQuit
invoke ExitProcess,0 ;Quits the window if clicked, because there
;is no X button
.endif
.endif

.elseif uMsg==WM_TIMER
invoke MessageBeep,MB_OK ;This beeps when the timer goes off

.elseif uMsg==WM_PAINT

invoke BeginPaint,hWnd,addr ps ;Gets the device context
mov hdc,eax ;and saves it in hdc
invoke CreatePen,PS_SOLID,4,00ffffffh ;The pen is white (RGB #FFFFFF) and 4 pixels thick
invoke SelectObject,hdc,eax ;Set pen to our hdc
invoke CreateSolidBrush,00a00000h ;Brush is blue (RGB #0000A0)
invoke SelectObject,hdc,eax ;Set brush to our hdc
invoke Rectangle,hdc,0,0,WNDX,WNDY

;The Rectangle() function uses the current brush to paint the background
;and the current pen to paint the border.
;Before calling the function we set these to values we wanted,
;so the background and border are fully adjustable

invoke EndPaint,hWnd,addr ps ;Finish our painting session
xor eax,eax ;Clear eax because we are supposed to

.ELSEIF uMsg==WM_NCHITTEST
; This handler is a little trick to make moving the window easy.

mov eax, HTCAPTION
ret

;What this does is when the window is clicked on, it needs to find out what it is selecting.
;By returning HTCAPTION regardless of where the window is clicked, Windows
;treats the whole window as the caption bar, so is fully moveable.


.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ;pass to default proc
ret
.ENDIF

xor eax,eax
noclear
ret
WndProc endp


end start