    ;; This example shows how to make a vertical split/rupture.
    ;;
    ;; A vertical split/rupture is used to divide the display (in the vertical dimension)
    ;; into more than one block. Each block spans the entire width of the display,
    ;; can have a programmable height and it's own start address.
    ;; Therefore parts of the screen can be scrolled while other parts remain static.
    ;; The split is another method to make overscan, but the split must be refreshed
    ;; every frame for the effect to be maintained. The split can also require exact
    ;; timing.
    ;;
    ;; When making a split, be aware of the differences in the CRTC models used by Amstrad.
    ;; (HD6845S/UM6845 (type 0), UM6845R (type 1), MC6845 (type 2), CPC+ CRTC inside ASIC
    ;; (type 3), CPC CRTC inside Pre-ASIC in cost-down CPCs (type 4)).
    ;; You will certainly notice the difference between these CRTCs when you program splits.
    ;;
    ;; The split is not a new effect, it has been used in some games (i.e. Mission Genocide, Octoplex
    ;; Prehistorik 2, Super Cauldron and Snowstrike, to name a few), and many demos.
    ;; This effect was made popular by demo groups such as Logon System.
    ;;
    ;; This method is technical and requires good understanding of the operation
    ;; of the CRTC.
    ;;
    ;; Extensive comments have been included to explain the operation of the split
    ;; and the reasons for every CRTC register write.
    ;;
    ;; Abreviations used:
    ;;
    ;; VADJC = internal Vertical Adjust Count register of the CRTC
    ;; VC = internal Vertical Count register of the CRTC
    ;; RC = internal Raster Count register of the CRTC
    ;; HC = internal Horizontal Count register of the CRTC
    ;; HTOT = Horizontal total register of CRTC (register 0)
    ;; HDISP = Horizontal displayed register of CRTC (register 1)
    ;; VTOT = Vertical total register of CRTC (register 4)
    ;; VSYNCPOS = Vertical sync position register of CRTC (register 7)
    ;; VSYNC = The vertical sync signal. This can be monitored through PPI port B.
    ;; HSYNCWIDTH = Horizontal sync width (programmed by register 3 of the CRTC)
    ;; HSYNCPOS = Horizontal sync position register of CRTC (register 2)
    ;; HDISP = Horizontal displayed register of CRTC (register 1)
    ;; VADJ = Vertical adjust register of CRTC (register 5)
    ;; MR = Maximum raster register of CRTC (register 9)
    ;;
    ;; (c) Kevin Thacker, 2002
    ;;
    ;; This code has been released under the GNU Public License V2.

    org &4000
    nolist

    ;;------------------------------------------------
    ;; install a interrupt handler

    di						;; disable interrupts
    im 1					;; interrupt mode 1 (CPU will jump to &0038 when a interrupt occrs)
    ld hl,&c9fb				;; C9 FB are the bytes for the Z80 opcodes EI:RET
    ld (&0038),hl			;; setup interrupt handler

    ;; the interrupt handler we have setup is minimal:
    ;;
    ;; EI
    ;; RET
    ;;
    ;; This will re-enable interrupts and then return to program control.
    ;;
    ;; We know the CPU time taken for the interrupt handler because we have defined
    ;; the interrupt handler and the timing of the instructions is known.
    ;;

    ei						;; re-enable interrupts
    ;;--------------------------------------------------------------------------

    ;;------------------------------------------------
    ;; define the horizontal and vertical sync widths
    ;;
    ;; the vertical sync width is programmable on CRTC type 0, type 3 and type 4.
    ;; the vertical sync width is fixed on CRTC type 1 and type 2.
    ;;
    ;; the horizontal sync width is programmable on all CRTC types.
    ;;

    ld bc,&bc03							;; select vertical and horizontal sync register of the CRTC
    out (c),c
    ld bc,&bd00+8						;; set vertical sync width = 16, horizontal sync width = 8
    out (c),c

    ;;------------------------------------------------
    ;; define the horizontal sync position
    ;;
    ;; With the vertical split, this is used to position the screen horizontally
    ;; within the display. Increasing this value will move the screen to the left
    ;; decreasing this value will move the screen to the right.
    ;;
    ;; Note that on CRTC type 2, (HSYNCPOS+HSYNCWIDTH)<HTOT otherwise interrupts are not
    ;; generated, and the split will not work!
    ;;
    ;; This is setup here once as the horizontal position will be the same
    ;; for all blocks.

    ld bc,&bc02							;; select horizontal sync position register of the CRTC
    out (c),c
    ld bc,&bd00+48						;; set horizontal sync to 48
    out (c),c

    ;;------------------------------------------------
    ;; define the horizontal displayed
    ;;
    ;; The horizontal displayed defines the number of CRTC characters to display
    ;; on each CRTC scan line. (Each CRTC character is 2 bytes)
    ;;
    ;; In this example this is setup here once as the horizontal displayed will
    ;; be the same for all blocks.

    ld bc,&bc01							;; select horizontal displayed register of the CRTC
    out (c),c
    ld bc,&bd00+48						;; set horizontal sync to 48
    out (c),c

    ;;------------------------------------------------
    ;; Setup the vertical displayed so that it is larger than
    ;; the vertical height of the tallest split block.
    ;;
    ;; In this example, this is setup here once as the horizontal displayed
    ;; will be the same for all blocks.
    ;;
    ;; The vertical and horizontal displayed signals define when
    ;; the border is shown:
    ;;
    ;; - If VDISP<VTOT of any block then border colour will be shown
    ;; between the block (where VDISP<VTOT) and the start of the next block.
    ;;
    ;; - If HDISP<HTOT of any block then border colour will be shown
    ;; on the sides of that block (where HDISP<HTOT).

    ld bc,&bc06
    out (c),c
    ld bc,&bdff
    out (c),c

    ;;------------------------------------------------
    ;; Setup the maximum raster so that it is the same for every split block.
    ;;
    ;; The following registers define the height of a split block:
    ;; - maximum raster register
    ;; - vertical total register
    ;; - vertical adjust register
    ;;
    ;; The height is computed as ((MR+1))*(VTOT+1))+VADJ  HTOT times.
    ;;
    ;; The time for a split block is defined as:
    ;; (((MR*HTOT)+1)*(VTOT+1))+(VADJ*HTOT)
    ;;
    ;; If HTOT is 64, then RC will increment once for every monitor scan-line.
    ;;
    ;; VERTICAL ADJUST:
    ;;
    ;; The vertical adjust, if defined to be a value other than 0, is activated
    ;; at the end of the split block it is defined for. The VADJC will increment
    ;; until it's value matches the programmed vertical adjust. Therefore the split block
    ;; is extended by (HTOT*VADJ) time.
    ;;
    ;; In this example where every HTOT is the duration of a monitor scan-line,
    ;; vertical adjust will count additional monitor scan-lines.
    ;;
    ;; MAXIMUM RASTER ADDRESS:
    ;;
    ;; The maximum raster adjust defines the number of times RC must increment
    ;; for each CRTC character row.
    ;;
    ;; In this example where every HTOT is the duration of a monitor scan-line,
    ;; the maximum raster address defines the height of the CRTC character row in
    ;; monitor scan-lines.

    ld bc,&bc05									;; select vertical adjust register of CRTC
    out (c),c
    ld bc,&bd00									;; 0 scan-lines of vertical adjust
    out (c),c

    ld bc,&bc09									;; select maximum raster register of CRTC
    out (c),c
    ld bc,&bd07									;; 8 scan-lines per CRTC character row
    out (c),c

    ;;----------------------------------------------------
    ;; this main loop is executed every frame to maintain
    ;; the split. The timing required to maintain this split is not
    ;; too important because the split is simple. If we are attempting
    ;; a more complex split, then the timing is often critical.

    .main_loop

    ;;----------------------------------------------------
    ;; wait for the start of the vsync
    ;;
    ;; the position of the vsync for the first time this code is executed,
    ;; and then subsequent executions will be different.
    ;;
    ;; The position of the first vsync is defined by the initial CRTC values
    ;; when this code is first entered.
    ;;
    ;; The position of subsequent vsyncs is determined by our vertical split code.
    ;;
    ;; (Note: This code will also recognise when a VSYNC is already in progress.
    ;; It will only see the start of a VSYNC if the VSYNC was inactive before this
    ;; checking loop is started.
    ;;
    ;; If the test is entered when the VSYNC has already started then our split
    ;; could be unstable as we may not always be synchronised to the CRTC at the same
    ;; point each frame. If the code requires the timing to be accurate, and we miss the
    ;; start of the VSYNC, then the CRTC registers we program (to create the split) could
    ;; be setup at the wrong time and the split will break).

    ld b,&f5				;; B = I/O address of PPI port B
    .vsync
    in a,(c)				;; read PPI port B input
    rra						;; transfer bit 0 into carry
    jr nc,vsync				;; if carry=0 then vsync= 0 (inactive),
    						;; if carry=1 then vsync=1 (active).

    ;;--------------------------------------------------
    ;; at this point we have seen the start of a vsync,
    ;; and are synchronised (but not exactly) to
    ;; the start of it.
    ;;
    ;;
    ;; (If we detected the start of the VSYNC, then we also know when the next interrupt
    ;; will occur.
    ;;
    ;; The interrupt counter is updated every HSYNC.
    ;; The interrupt counter reset is synchronised to the start of the VSYNC.
    ;; A interrupt request is issued when the interrupt counter reaches 52.
    ;;
    ;; The next interrupt could occur in two HSYNC times, assuming that
    ;; the previous interrupt was not serviced less than 32 lines ago.
    ;;
    ;; Otherwise the next interrupt will occur in 52+2 HSYNC times.
    ;;
    ;; A perfect split relies on predicting the position of the start of the VSYNC
    ;; and the position of the interrupts, as these are the signals we use to
    ;; synchronise with the display, and this means that we can setup the next split
    ;; block at the correct position).
    ;;
    ;; in our split example, the VSYNC is programmed to occur when
    ;; VC = 0, RC = 0. (This trigger point is setup at the end of the main loop).
    ;; We know that when we have seen the start of the VSYNC the CRTC
    ;; will be processing VC = 0, RC=0, and HC will be somewhere on this line.
    ;; (The value of HC will vary as the VSYNC testing check is accurate to about 8us).
    ;;

    ;;-----------------------------------------------------------------
    ;; To stop the VSYNC from triggering again, until we want it
    ;; to, program the vsync position so that it is larger than
    ;; the vertical height of the tallest split block.
    ;;
    ;; Now Vsync will not be triggered, because VC can never equal the
    ;; vertical sync position we have programmed.
    ;;
    ;; The CRTC will continue to display the split blocks until we set
    ;; a valid VSYNC position which can be reached by VC. (e.g. VSYNCPOS<VTOT)

    ld bc,&bc07				;; select vertical sync position register of the CRTC
    out (c),c
    ld bc,&bdff				;; set vertical sync position (255)
    out (c),c

    ;;--------------------------------------------------------------
    ;; The height of each split block is defined by:
    ;;
    ;; - the vertical total register of the CRTC. The register must be reprogrammed for each
    ;; block.
    ;; - the maximum raster register of the CRTC.
    ;; - the vertical adjust register of the CRTC.
    ;;
    ;;
    ;; In the case where HTOT is 64, the height of the split block is therefore
    ;; defined in complete monitor scan-lines.
    ;;
    ;; For compatibility reasons, the vertical total register should be written
    ;; when VC<new_VTOT, otherwise the CRTC may finish the current split block and
    ;; start a new one, and this may not be the effect you want.
    ;;
    ;; For a steady split, when HTOT is 64 the frame should use 312 scan-lines.
    ;; (64 microseconds * 312 crtc scan lines)=19968us.
    ;;
    ;; 1/50 second = 0.02 seconds
    ;; 1/1000000 second = 1 microsecond
    ;;
    ;; 1 microsecond*19968 = 0.019968 seconds = approx 0.02 seconds
    ;;
    ;; If more scan-lines are used then the whole display may roll, or the display
    ;; will flicker.

    ;;---------------------------------------------------------------
    ;; set height of first split block. At this point VC<new_VTOT, so first split
    ;; block will become 5 char lines tall. The split block will
    ;; be 40 (5*8) scanlines.

    ld bc,&bc04				;; select vertical total register of the CRTC
    out (c),c
    ld bc,&bd00+10-1		;; set height (height-1) of the first split block
    out (c),c

    ;;----------------------------------------------------------------------------
    ;; HALT = wait for next interrupt to trigger, execute the interrupt handler
    ;; and continue with program execution. Assumes interrupts are ENABLED!
    ;;
    ;; NOTE: we use the interrupts for two purposes:
    ;; - to sync exactly with the CRTC to an exact point on the screen
    ;; (This position is determined by the HSYNCPOS, the CPU time required to acknowledge
    ;; the interrupt and the CPU time required to execute the interrupt handler)
    ;;
    ;; - to waste some time, so that we can wait until the appropiate position
    ;; to setup the split. When using the standard Amstrad CPC interrupts
    ;; the HALT provides a coarse positioning method.
    ;;
    ;; A HALT instruction will execute the equivalent of NOP instructions until
    ;; a interrupt request occurs. A NOP is one of the fastest instructions taking
    ;; 1us for each cycle. Therefore, when a interrupt request occurs, there will
    ;; always be the same time between interrupt request occuring and interrupt
    ;; request being acknowledge and serviced. This instruction works the best
    ;; when there is no interrupt request outstanding when the HALT is first executed.)

    halt
    ;; At this point we are synchronised exactly to a predictable position on the screen.
    ;; We can now use software loops to delay to the exact position we want.

    ;;-------------------------------------------------------
    ;; In this example, the previous interrupt was processed greater than 32 lines
    ;; ago.
    ;;
    ;; If IRQ_ACK is the time between the interrupt being triggered and then acknowledged
    ;; by the CPU, and IRQ_FUNC is the time for the interrupt handler to execute and
    ;; return control back to the program, then the interrupt will occur at time:
    ;;
    ;; HTOT+HSYNCPOS+IRQ_ACK+IRQ_FUNC
    ;;
    ;; (This assumes that the HSYNCPOS and HTOT remain constant).
    ;;
    ;; When VSYNCPOS=0 then the CRTC should be processing VC=0, RC=1 at the time the
    ;; interrupt is requested, and by the time we receive control the CRTC should
    ;; be processing VC=0, RC=2.

    ;; delay until VC=1.
    ;; Delay required = 8 scanlines = 8 * 64 us = 512 us
    ;;
    ;; for DJNZ: if b-1==0, instruction will execute in 3us
    ;; if b-1<>0, instruction will execute in 4us.
    ;;

    ld b,127						;; [2]
    .wait1
    djnz wait1						;; [3/4]

    ;; for this loop:
    ;;
    ;; (14*4)+3+2 = 513 us

    ;;-----------------------------------------------------------------------------------
    ;; set the start address of the *next* split block
    ;;
    ;; - for CRTC type 0,2,3 and 4, this start address will take effect at the start
    ;; of the next split block.
    ;; - for CRTC type 1, it is possible to reprogram the start address of the current
    ;; split block if VC=0, otherwise this start address will take effect at the start
    ;; of the next split block.
    ;;
    ;; For compatibility with other CRTC types, attempt to change the start position
    ;; when VC>0 and VC<(VTOT-1).
    ;;
    ;; NOTE: the start address of the first split block is defined at the end of the loop

    ld hl,&1000							;; start address in CRTC form (&4000 - &7fff in RAM)
    ld bc,&bc0c							;; start address high register of CRTC
    out (c),c							;; select start address high register of CRTC
    inc b								;; B = &BD
    out (c),h							;; write data to start address low register

    ld bc,&bc0d							;; select start address low register of CRTC
    out (c),c							;; select start address low register of CRTC
    inc b								;; B = &BD
    out (c),l							;; write data to start address low register

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

    halt
    ;; in this example, the CRTC has completed 2 + 52 monitor scanlines
    ;; since the start of the VSYNC.
    ;;
    ;; In this example HTOT and HSYNC remain constant, therefore the amount of time
    ;; that has passed is:
    ;;
    ;; (HTOT+HSYNCPOS)+(HTOT-HSYNCPOS)+(51*HTOT)+HSYNCPOS
    ;;
    ;; where (HTOT+HSYNCPOS) is the time to the first interrupt request,
    ;; (HTOT-HSYNCPOS) is the time from the first interrupt request to the first HTOT
    ;; after the interrupt request,
    ;; (51*HTOT)+HSYNCPOS is the time to the second interrupt request.
    ;;
    ;; approx 6.75 scanlines

    ld b,15
    .wait2 djnz wait2

    ld bc,&bc04
    out (c),c
    ld bc,&bd00+24
    out (c),c

    ;------------------------------------------------------------------------------
    ;blk3
    halt

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

    halt
    ld b,15
    .wait4 djnz wait4
    ld bc,&bc0c
    out (c),c
    ld bc,&bd00
    out (c),c
    ld bc,&bc0d
    out (c),c
    ld bc,&bd00
    out (c),c

    halt

    halt
    ld b,15
    .wait6 djnz wait6

    ld bc,&bc04
    out (c),c
    ld bc,&bd00+5 ;5+25+6=36 (nearly 39!)
    out (c),c

    ld bc,&bc0c
    out (c),c
    ld bc,&bd00+%00010000 ;top section of screen
    out (c),c
    ld bc,&bc0d
    out (c),c
    ld bc,&bd00
    out (c),c

    ;;-------------------------------------------------------------
    ;; To maintain a steady split we must do the following:
    ;; - ensure the register writes to the CRTC occur at the same position every frame
    ;; - force a VSYNC to be generated once per frame, once per 312 complete (64us) scan-lines
    ;;  (once every 19968us)
    ;;
    ;; If we want a steady split that will work on every CRTC type:
    ;; - ensure the register writes to the CRTC occur at a time that is compatible with
    ;; every CRTC

    ;; reprogram a new vertical sync position to force a VSYNC to be triggered.
    ;;
    ;; We want our VSYNC to start on VC=0, RC=0, HC=0.
    ;;
    ;; - If VC<>0 then the VSYNC will be triggered at the start of the next split (VC=0, RC=0, HC=0).
    ;; - If VC==0, then on some CRTC's a VSYNC will be triggered immediatly (VC=0, RC!=0, HC=??), this
    ;; could cause a bad split if our split requires that the VSYNC must occur when VC=0, RC=0, HC=0)!
    ;;

    ld bc,&bc07				;; vertical sync position register of the CRTC
    out (c),c				;; select vertical sync position of the CRTC
    ld bc,&bd00				;; vertical sync position = 0
    out (c),c				;; set vertical sync position

    ;;----------------------------------------
    ;; continue to loop so that the split is maintained

    jp