;; This example shows how to have an accurate mode split with the firmware.
;;
;; Things to note:
;; - We must use express asynchronous fast ticker events, these are executed first after frame flyback interrupts.

;; - Frame flyback interrupts are executed every 50Hz of a second. express asynchronous fast ticker events are executed every 300th of a second
;; and immediately after frame flyback interrupts.
;; 
;;
;; - if we used other fast ticker events, we could end up being called much later and subjected to delays from other interrupts
;; and keyboard scanning.
;;
;;
;; - The keyboard is read once every 6th interrupt but is not synchronised with a specific point on the screen. 
;; On 6128/664/Plus we can call KL SCAN NEEDED after a VSYNC to synchronise the keyboard with the vsync, but this doesn't work on 
;; 464.
;;
;; - When our function is called, we are already effectively working in the alternative register set.
;;
;; - We can't call MC SET MODE because this switches to alternative register set and also disables and enables interrupts.
;; Not good inside our function.
;;
;;
;; Using default CRTC register settings:
;; 
;; Reg 0 = 63 (64 chars per line)
;; Reg 9 = 7 (8 lines per char)
;; Reg 4 = 38 (39 char lines per screen)
;; Reg 7 = 30 (vsync triggered on line 30)
;; 
;; Also first interrupt occurs 2 lines into vsync and 52 lines after (assuming interrupt is not delayed by user, or disabling interrupt
;; for too long)
;;
;; Then first number is number of lines after vsync start, 2nd is number of lines relative to top-line of screen
;; at which the interrupt will trigger. It triggers at hsync pos defined by Reg 2 so generally happens in the middle of a line
;;
;;first at line 2 	-70
;;next at line 54	-18
;;next at line 106	34 
;;next at line 158	86
;;next at line 210	138
;;next at line 262	190
;;next at line 314	242		
;;
;; What this means is that the interrupt may not occur exactly on the line you want. You can 
;; use CRTC register 7 to move the screen up/down relative and compensate a little, otherwise you will need
;; to include a delay before you change mode to allow for the position you want.

;; firmware functions we use in this example
.kl_new_fast_ticker equ &bce0
.mc_wait_flyback    equ &bd19
.kl_del_fast_ticker equ &bce6

org &8000

jp split_on
jp split_off

split_on:
;; synchronise our ticker interrupt with the vsync       
call mc_wait_flyback
halt
halt
call mc_wait_flyback

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

;; install interrupt
ld hl,ticker_event_block
ld b,%11000001				;; near address, express asynchronous event
ld c,&80					;; rom select 
ld de,ticker_function
call kl_new_fast_ticker

;; return to BASIC
ret

split_off:
ld hl,ticker_event_block
call kl_del_fast_ticker
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
;;push bc
;; 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. We need to ensure we are synced with vsync in
;; order that this works correctly.
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

;; already in alternative register set

;; set mode now to ensure we set it early, so that other interrupt delay is not so important.

;; fetch current C register value stored in A'
ex af,af'
ld c,a
ex af,af'

;; now read mode from table
ld a,(hl)
;; clear off mode bits
res 1,c
res 0,c
;; combine with our wanted mode
or c
;; write to hardware
ld c,a
res 2,a			;; ensure lower rom is active, but we don't want to modify C register ;)
ld b,&7f
out (c),a

;; now store back to A'
;; later firmware will also use this to set mode/rom once more, but now it is updated
;; with our new values we will get the mode we wanted.
ex af,af'
ld a,c			;; remember it otherwise firmware sets it back
ex af,af'

;; update pointer
inc hl
;; store pointer
ld (current_mode_pointer),hl

;; ensure express asynchronous event is retriggered correctly
;; see SOFT968
ld hl,ticker_event_block+2
ld (hl),#00
;;pop bc
pop hl
pop af
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 0
defb 0
defb 0
defb 1
defb 1