;; The previous multi-mode example would not always be accurate.
;; Sometimes, a mode change could be delayed by up to 8 scanlines.
;;
;; The reason:
;; 1. The keyboard is scanned by the firmware 1 time out of every 6 interrupts.
;; However, the position within the frame is not synchronised to any specific event.
;; i.e. it is not synchronised to the vsync.
;; 
;; Sometimes the keyboard interrupt can get delayed, or shifted from it's original
;; position defined when the computer starts up.
;;
;; When it moves position, it is scanned before our interrupt is executed.
;;
;; Vsync (frame flyback) interrupts are always executed before the keyboard is scanned,
;; so these do not suffer from the same problem.
;;
;; So to solve this problem we need to synchronise and reset when the keyboard
;; should be read. KL SCAN NEEDED does this for us. It signals the keyboard should
;; be scanned at the next interrupt AND the counter used to indicate when it should next
;; be scanned is also reset.
;;
;; 2. The keyboard interrupt may go out of sync when the disc drive is used or the tape
;; is used, because these disable interrupts.
;;
;; This example only works on 664/6128.

;; firmware functions we use in this example
.kl_new_fast_ticker equ &bce0
.mc_set_mode        equ &bd1c
.mc_wait_flyback    equ &bd19
.kl_init_event equ &bcef
.kl_scan_needed equ &b92a


;; this code must be in the range &4000-&bfff
;; to work correctly.
;;
;; assemble, then from BASIC:
;; 
;; call &8000
org &8000

;; wait for a screen refresh
;; we do this to synchronise our effect
;; and so that the first ticker interrupt is in a predictable place


;; The following waits for the *start* of the vsync. But will also return
;; if the vsync has already started.
call mc_wait_flyback

;; two interrupts so we are outside of the vsync (one interrupt could trigger
;; within the vsync)
halt
halt
;; this should now wait until the start
call mc_wait_flyback

;; now reset key scan to ensure it happens at each vsync
;; NOTE: This will get upset if the disc/tape are used, but so will
;; the position of our interrupts and modes.
;; So in this case the interrupt must be disabled and re-enable in the same
;; way to ensure the accurate timing.
call kl_scan_needed


ld a,6
ld (ticker_counter),a
ld hl,modes
ld (current_mode_pointer),hl

;; install interrupt
ld hl,ticker_event_block
ld b,%10000010				;; asynchronous event, priority 1
ld c,&80					;; rom select 
ld de,ticker_function
call kl_new_fast_ticker

;; return to BASIC
ret

;; this is initialised by
;; the firmware; holds runtime state of ticker interrupt
.ticker_event_block
defs 10

;; this is the function called each 1/300th of a second
.ticker_function
push af
push hl

;; The 1/300th of a second interrupt effectively splits
;; the screen into 6 sections of equal height. Each section
;; spans the entire width of the screen.
;;
;; We want to ensure that the effect is stationary so we reset
;; every 6 calls of this function.
ld a,(ticker_counter)
dec a
ld (ticker_counter),a
or a
jr nz,ticker_function2
ld a,6
ld (ticker_counter),a
ld hl,modes
ld (current_mode_pointer),hl

.ticker_function2

;; get pointer to current mode
ld hl,(current_mode_pointer)
;; get the mode
ld a,(hl)
;; update pointer
inc hl
;; store pointer
ld (current_mode_pointer),hl

;; set mode. This will take effect at the start of the next horizontal
;; scan-line. This firmware function talks direct to the hardware.
;; The screen is not cleared and the other firmware functions for
;; plotting to the screen have no knowledge of the new mode.
call mc_set_mode

pop af
pop hl
ret

.ticker_counter defb 0

.current_mode_pointer defw modes

;; The 1/300th of a second interrupt effectively splits
;; the screen into 6 sections of equal height. Each section
;; spans the entire width of the screen.
;;
;; video mode for each of the 6 sections of the screen.

.modes
defb 0
defb 1
defb 2
defb 0
defb 1
defb 2