Created
December 6, 2020 16:25
-
-
Save kajott/2613c7792aa96025a3bd83bbe3a3521e to your computer and use it in GitHub Desktop.
clock screensaver for DOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; clock screensaver in 670 bytes (DOS, 186+) | |
; | |
; features: | |
; - VGA mode 13h | |
; - 24-hour clock in pixellated LED clock style | |
; - animated screen position | |
; - smooth alpha-blended transition between seconds | |
; - fade-in on startup, fade-out on exit | |
; - smoothly changing colors | |
; | |
; assemble and test with: | |
; yasm -fbin -oclock.com clock.asm && dosbox clock.com | |
cpu 186 | |
bits 16 | |
org 100h | |
section .text | |
; set video mode | |
mov ax, 13h | |
int 10h | |
; get "random" start position | |
call @getticks ; ax = timer | |
mov cx, ax ; save into cx | |
and ax, 127 ; only use 7 bits for Y position | |
mov bx, 320 ; multiply with 320 | |
mul bx | |
xchg cl, ch ; use other bits for X position | |
and cx, 127 ; use 7 bits there too | |
add ax, cx ; add X position to Y position | |
mov [@pos], ax ; store position | |
@mainloop: | |
; ----- ONCE-PER-SECOND TIME UPDATE AND BITMAP PREPARATION ----- | |
; get absolute system time | |
mov ah, 2Ch | |
int 21h | |
; new second? | |
cmp dh, [@lastsec] | |
je @nonewtime | |
mov [@lastsec], dh | |
; toggle phase and set di to appropriate text address | |
mov al, [@phase] | |
xor al, 1 | |
mov [@phase], al | |
xor ah, ah | |
mov di, @text | |
shl ax, 4 ; translate phase to text offset | |
add di, ax ; add phase | |
; decode system time into digits | |
mov bl, 10 ; set divisor | |
mov al, ch ; convert hour | |
xor ah, ah | |
div bl | |
stosb | |
shr ax, 8 ; also resets ah to zero | |
stosb | |
mov al, bl ; store separator | |
stosb | |
mov al, cl ; convert minute | |
div bl | |
stosb | |
shr ax, 8 ; also resets ah to zero | |
stosb | |
mov al, bl ; store separator | |
stosb | |
mov al, dh ; convert second | |
div bl | |
stosb | |
mov al, ah | |
stosb | |
not al ; store terminator | |
stosb | |
; ensure that there are two copies of the text | |
; (because in the first frame, text0 is still not valid) | |
cmp byte [@text], 128 | |
jne @textok | |
mov di, @text | |
mov si, @text+16 | |
mov cx, 9 | |
rep movsb | |
@textok: | |
; clear the bitmap | |
mov di, @bitmap | |
mov cx, 32*7 | |
xor al, al | |
rep stosb | |
; render the bitmap | |
mov si, @text ; render text0 into bit 0 | |
mov cl, 0 | |
call @render | |
mov si, @text+16 ; render text1 into bit 1 | |
mov cl, 1 | |
call @render | |
; reset alpha | |
mov al, 32 | |
mov byte [@alpha], al | |
@nonewtime: | |
; ----- PALETTE UPDATE AND SCREEN SCALING ----- | |
; set colors using the sine table | |
mov di, @color | |
call @getticks ; load time (only MSBs are relevant) | |
mov dl, al ; save time | |
call @sin ; R = sin(t) | |
stosb | |
add dl, 55h ; t += (1/3) * 256 | |
mov al, dl | |
call @sin ; G = sin(t) | |
stosb | |
add dl, 55h ; t += (1/3) * 256 | |
mov al, dl | |
call @sin ; B = sin(t) | |
stosb | |
; load alphas (bl = alpha, bh = inverse alpha) | |
mov bl, byte [@alpha] | |
mov bh, 32 | |
sub bh, bl | |
; swap alphas depending on phase | |
mov al, byte [@phase] | |
or al, al | |
jnz @noswap | |
xchg bl, bh | |
@noswap: | |
; wait for VSync | |
mov dx, 3DAh | |
@vsync1: ; wait until we *leave* VSync | |
in al, dx ; (required if everything else runs too quickly) | |
test al, 8 | |
jnz @vsync1 | |
@vsync0: ; wait until we enter VSync again | |
in al, dx | |
test al, 8 | |
jz @vsync0 | |
; configure palette | |
mov dx, 3C8h ; port 3C8h | |
mov al, 1 ; start with index 1 (0 stays black) | |
out dx, al | |
inc dx ; port 3C9h | |
call @setcolor ; set color 1 | |
mov bl, bh ; set color 2 | |
call @setcolor | |
mov bl, 32 ; set "always-on" base color (3) | |
call @setcolor | |
; scale to screen | |
push es ; save data segment | |
mov ax, 0A000h ; load screen segment | |
mov es, ax | |
mov di, word [@pos] ; load screen position | |
sub di, 320*5+6 ; adjust screen position | |
mov si, @bitmap | |
mov dh, 7 ; dh = outer line counter | |
@scalelinea: | |
mov dl, 4 ; dl = inner line counter | |
@scalelineb: | |
push si ; save source pointer | |
mov cx, 29 ; cx = number of pixels | |
@scalepixel: | |
lodsb ; load pixel | |
times 5 stosb ; scale x5 | |
xor al, al ; add another black pixel | |
stosb | |
loop @scalepixel | |
; end of scaler loops | |
add di, 320-29*6 ; advance to next screen line | |
pop si ; restore source pointer | |
dec dl ; repeat inner line loop | |
jnz @scalelineb | |
add si, 32 ; advance to next bitmap line | |
mov cx, 320 ; clear the following line | |
rep stosb | |
dec dh ; repeat outer line loop | |
jnz @scalelinea | |
pop es ; restore data segment | |
; ----- PER-FRAME STATE UPDATES ----- | |
; adjust alpha | |
mov al, byte [@alpha] ; alpha -= 1 | |
dec al | |
jns @alphaok ; if (alpha < 0) alpha = 0 | |
xor al, al | |
@alphaok: | |
mov [@alpha], al | |
; adjust position | |
mov ax, word [@pos] ; load old position and apply delta | |
mov cx, word [@delta] | |
add ax, cx | |
mov word [@pos], ax | |
mov bx, 320 ; split position into X/Y pair (ax = Y, dx = X) | |
xor dx, dx | |
div bx | |
or dx, dx ; if X=0, set direction right | |
jnz @x0ok | |
add cx, 2 | |
@x0ok: | |
cmp dx, 320-27*6+1 ; if X=max, set direction left | |
jne @x1ok | |
sub cx, 2 | |
@x1ok: | |
or ax, ax ; if Y=0, set direction down | |
jnz @y0ok | |
add cx, 640 | |
@y0ok: | |
cmp ax, 200-5*5+1 ; if Y=max, set direction up | |
jne @y1ok | |
sub cx, 640 | |
@y1ok: | |
mov word [@delta], cx ; store new direction | |
; update fading | |
mov al, byte [@fade] ; fade += fdelta | |
add al, byte [@fdelta] | |
or al, al ; if fade < 0 then quit | |
js @exit | |
mov dl, 64 ; fade = min(fade, 64) | |
cmp al, dl | |
jl @fadeok | |
mov al, dl | |
@fadeok: | |
mov byte [@fade], al ; store new fade value | |
; exit handling | |
mov ah, 1 ; check for keystroke | |
int 16h | |
jz @mainloop ; if no key pressed, resume with frame loop | |
xor ah, ah ; consume keystroke | |
int 16h | |
mov al, byte [@fdelta] ; fdelta -= 2 (if it was +1, makes it -1; | |
sub al, 2 ; if it already was negative, this speeds up | |
mov byte [@fdelta], al ; the fade-out operation) | |
jmp @mainloop ; new loop | |
@exit: | |
mov ax, 3 | |
int 10h | |
ret | |
; -------- FUNCTIONS -------- | |
; FUNCTION @render(si = text pointer, cl = output bit select) | |
@render: | |
xor ch,ch ; ch = current line number | |
@renderline: | |
push si ; create a backup of the text pointer | |
; set DI to bitmap pointer: @bitmap + 33 + ch * 32 | |
mov di, @bitmap+33 | |
xor bh, bh | |
mov bl, ch | |
shl bl, 5 | |
add di, bx | |
; set glyph base pointer: @glyphs + ch * 12 | |
mov dl, bl ; backup ch*32 | |
shr bl, 2 ; bl = ch*8 | |
shr dl, 3 ; dl = ch*4 | |
add bl, dl ; bl = ch*8 + ch*4 = ch*12 | |
add bx, @glyphs ; bx = @glyphs + ch*12 | |
@renderglyph: | |
lodsb ; load glyph index | |
or al, al ; check for end marker | |
js @renderendline | |
xlatb ; translate glyph index into bit pattern | |
mov ah, al ; save bit pattern | |
@renderpixel: | |
mov al, ah ; restore bit pattern | |
and al, 1 ; mask LSB | |
shl al, cl ; move to appropriate bit position | |
or al, [es:di] | |
stosb ; OR into bitmap | |
shr ah, 1 ; go to next bit | |
cmp ah, 1 ; end marker reached? if not, render next pixel | |
jne @renderpixel | |
jmp @renderglyph ; render next glyph | |
@renderendline: | |
pop si ; restore the text pointer backup | |
inc ch ; move to next line | |
cmp ch, 5 ; go to next line | |
jne @renderline | |
ret | |
; FUNCTION @setcolor(bl = alpha) | |
@setcolor: | |
mov al, bl | |
mul byte [@fade] ; multiply with fade factor to get final alpha | |
shr ax, 5 | |
mov bl, al ; save final alpha | |
mul byte [@color+0] ; multiply with R and set palette | |
shr ax, 6 | |
out dx, al | |
mov al, bl ; restore final alpha | |
mul byte [@color+1] ; multiply with G and set palette | |
shr ax, 6 | |
out dx, al | |
mov al, bl ; restore final alpha | |
mul byte [@color+2] ; multiply with B and set palette | |
shr ax, 6 | |
out dx, al | |
ret | |
; FUNCTION @sin(al) -> al | |
@sin: | |
mov ah, al ; save argument | |
and al, 63 ; clip sub-quadrant index | |
test ah, 64 ; check for quadrants 1 and 3 | |
jz @quadxok | |
xor al, 63 ; reverse table in affected quadrants | |
@quadxok: | |
mov bx, @sintab ; lookup base sin | |
xlatb | |
test ah, 128 ; check for quadrants 2 and 3 | |
jz @quadyok | |
xor al, 63 ; reverse signal in affected quadrants | |
@quadyok: | |
ret ; done | |
; FUNCTION @getticks -> ax | |
; get 18.2 Hz tick counter from 0:046Ch | |
@getticks: | |
push ds | |
xor ax, ax | |
mov ds, ax | |
mov ax, [046Ch] | |
pop ds | |
ret | |
;------- DATA ------- | |
section .data ; constants and initialized variables | |
@delta: dw 321 ; position increment | |
; python -c 'import math;print([int((math.sin((i+.5)/128*math.pi)/2+.5)*63+.5) for i in range(64)])' | |
@sintab: | |
db 32, 33, 33, 34, 35, 36, 37, 37, 38, 39, 40, 40, 41, 42, 42, 43 | |
db 44, 45, 45, 46, 47, 47, 48, 49, 49, 50, 51, 51, 52, 52, 53, 53 | |
db 54, 55, 55, 56, 56, 57, 57, 57, 58, 58, 59, 59, 59, 60, 60, 60 | |
db 61, 61, 61, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63 | |
@glyphs: ; glyph bitmaps, intermixed with initialized variables | |
; [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [:] | |
db 10111b, 10100b, 10111b, 10111b, 10101b, 10111b, 10111b, 10111b, 10111b, 10111b, 100b | |
@lastsec: db 44h ; second where the last phase change occurred | |
db 10101b, 10100b, 10100b, 10100b, 10101b, 10001b, 10001b, 10100b, 10101b, 10101b, 101b | |
@phase: db 0 ; current phase: 0/1 | |
db 10101b, 10100b, 10111b, 10111b, 10111b, 10111b, 10111b, 10100b, 10111b, 10111b, 100b | |
@fade: db 0 ; current fade-in/-out state | |
db 10101b, 10100b, 10001b, 10100b, 10100b, 10100b, 10101b, 10100b, 10101b, 10100b, 101b | |
@fdelta: db 1 ; current fade direction | |
db 10111b, 10100b, 10111b, 10111b, 10100b, 10111b, 10111b, 10100b, 10111b, 10111b, 100b | |
@text: db 128 ; first text line (starts with an "invalid" marker) | |
section .bss ; uninitialized variables follow | |
resb 31 ; remainder of the text lines | |
@alpha: resb 1 ; alpha blending factor | |
@pos: resw 1 ; screen position (linear offset in video memory) | |
@color: resb 3 ; current RGB color | |
@bitmap: resb 32*7 ; unscaled bitmap (with some borders) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment