* Sprite Clock
*
*  This is a very simple clock that uses a sprite as it's display  medium.
* It displays the time every minute for a couple seconds and then the sprite
* disappears. The time is displayed in 12-hr. format; you'll have to figure
* out if it is AM or PM yourself. 
*  It is useful if you are working on different screens alot since a sprite
* will appear in front of any screen.
*  I had to design the numbers myself and I used alot of tricks to compress
* the space their data would have taken so that the clock will remain small.
* I would think that there is some way to take advantage of the fonts already
* in the system but I did not know enough about the form of the font in 
* memory and how to access that data so that it could be used.
*  Alot of different techniques are used in order to load the numerals into
* memory and I would think they would prove educational to someone.

* At this time this clock can not be terminated once it is started.
* I don't mind this since it takes up very little memory. This program
* could be given another port and a close message could be sent to it by
* another program or by something like the workbench menus if they where
* properly altered.

* Code size:		 820 bytes
* minumum stack:	1600 bytes
* Running size:		6312 bytes

* I do not think it will work from the workbench in it's current version.

* Darrel Schneider
************************************************************************

          NOPAGE
          NOLIST
          LLEN 132

;          INCLUDE "exec/types.i"
;          INCLUDE "exec/ables.i"
;          INCLUDE "exec/memory.i"
;          INCLUDE "devices/timer.i"

* the following equates take the place of the info in the above files

          MP_SIZE: EQU $22
          MP_SIGBIT: EQU $F
          MEMF_CLEAR: EQU $10000
          MEMF_PUBLIC: EQU $1
          MEMF_CHIP: EQU $2
          NT_MSGPORT: EQU $4
          NT_MESSAGE: EQU $5
          IOTV_SIZE: EQU $28
          LN_TYPE: EQU $8
          LN_NAME: EQU $A
          IO_DEVICE: EQU $14
          IO_UNIT: EQU $18
          IO_COMMAND: EQU $1C
          IO_SIZE: EQU $20
          MN_REPLYPORT: EQU $E
          TR_ADDREQUEST: EQU $9
          TR_GETSYSTIME: EQU $A
          UNIT_VBLANK: EQU $1
          TV_SECS: EQU $0
          TV_MICRO: EQU $4

          XREF _AbsExecBase

          XREF _LVOOpenLibrary
          XREF _LVOCloseLibrary

          XREF _LVOAllocMem
          XREF _LVOFreeMem

          XREF _LVOAllocSignal
          XREF _LVOFreeSignal

          XREF _LVOAddPort
          XREF _LVORemPort

          XREF _LVOOpenDevice
          XREF _LVOCloseDevice

          XREF _LVODoIO
          XREF _LVOSendIO
          XREF _LVOWaitIO

          XREF _LVOFindTask

          XREF _LVOGetSprite
          XREF _LVOChangeSprite
          XREF _LVOMoveSprite
          XREF _LVOFreeSprite

          LIST
          Temp:               EQUR d2
          LoopCount:          EQUR d2
          Secs:               EQUR d3
          Micros:             EQUR d4
          AndMask:            EQUR d5
          Timer_Port:         EQUR d6
          GraphicsBase:       EQUR d7

          BufferPTR:          EQUR a2
          SimpleSpritePTR:    EQUR a3
          NewDataPTR:         EQUR a4
          Time_ReqPTR:        EQUR a5

********** open libraries ***********

          movea.l   _AbsExecBase,a6
          lea       GraphicsName(pc),a1
          moveq.l   #0,d0
          jsr       _LVOOpenLibrary(a6)
          tst.l     d0
         bne.s      NOAbortOpenLibrary ;V
          rts       ;quit
         NOAbortOpenLibrary:
          move.l    d0,GraphicsBase

********** create timer port **********

          ;get a signal bit
          moveq.l   #-1,d0
          jsr       _LVOAllocSignal(a6)
          move.l    d0,d2
          bmi.l     AbortCreateTimer1    ; if -1 AllocSignal failed so abort

          ; alloc port structure
          moveq.l   #MP_SIZE,d0
          move.l    #(MEMF_CLEAR!MEMF_PUBLIC),d1
          jsr       _LVOAllocMem(a6)
          move.l   d0,Timer_Port
        bne.s   skipAbortCode ;V  it was allocated so skip the abort code
          ; AllocMem failed so clean up and then abort
          move.l    d2,d0
          jsr       _LVOFreeSignal(a6)
          bra.l     AbortCreateTimer2    ; if 0 AllocMem failed so abort

        skipAbortCode:
          ; fill port fields
          move.l    Timer_Port,a2
          addq.l    #8,a2                 ; a2 = &LN_TYPE
          move.w    #((NT_MSGPORT<<8)+Priority),(a2)+
          ; the last instruction does the next two
          ;   move.b    #NT_MSGPORT,(a2)+     ; LN_TYPE
          ;   move.b    #Priority,(a2)+       ; LN_PRI
          lea       TimerPortName(pc),a1
          move.l    a1,(a2)+                  ; LN_NAME gets &TimerPortName
          ; this instruction is done by the next one since it is a move.w 
          ;   clr.b     (a2)+                 ; #PA_SIGNAL -> MP_FLAGS
          move.w    d2,(a2)+                  ; MP_SIGBIT
          moveq.l   #0,d0
          movea.l   d0,a1
          jsr       _LVOFindTask(a6)
          move.l    d0,(a2)               ; MP_SIGTASK
          ; add the port
          movea.l   Timer_Port,a1
          jsr       _LVOAddPort(a6)

 bra.s   SkipAbortSection ;V

*******************************************************************************
**************************  TERMINATION SECTION *****************************

AbortNewDataAlloc:
          moveq.l   #0,d0
          move.w    10(SimpleSpritePTR),d0
          exg       a6,GraphicsBase
          jsr       _LVOFreeSprite(a6)
          exg       a6,GraphicsBase
AbortGetSprite:
          moveq.l   #IOTV_SIZE,d4
          adda.l    d4,Time_ReqPTR
          adda.l    d4,Time_ReqPTR
          moveq.l   #1,d3
       bra.s BEGINCloseDeviceLOOP ;V
AbortOpenDevice:
          subq.l    #1,d3
      beq.s SKIPCloseDeviceLOOP ;V
          moveq.l   #0,d3
       BEGINCloseDeviceLOOP:
          ; close timer device
          suba.l    d4,Time_ReqPTR
          lea       (Time_ReqPTR),a1
          jsr       _LVOCloseDevice(a6)
       dbra d3,BEGINCloseDeviceLOOP ;^

      SKIPCloseDeviceLOOP:
          ; deallocate Time_Req structure
          moveq.l   #-1,d3
          movea.l   Time_ReqPTR,a1
          moveq.l   #1,d0
         DeallocateLOOP:
          move.b    d3,LN_TYPE(Time_ReqPTR)
          move.l    d3,IO_DEVICE(Time_ReqPTR)
          move.l    d3,IO_UNIT(Time_ReqPTR)
          adda.l    d4,Time_ReqPTR
         dbra d0,DeallocateLOOP
          moveq.l   #IOTV_SIZE*2,d0
          jsr       _LVOFreeMem(a6)

AbortTime_ReqAlloc:
          ; close timer port
          movea.l   Timer_Port,a1
          jsr       _LVORemPort(a6)
          movea.l   Timer_Port,a2
            ; d3 = -1
          move.b    d3,LN_NAME(a2)
          move.l    d3,(a2)            ; LH_HEAD
          move.l    MP_SIGBIT(a2),d0
          jsr       _LVOFreeSignal(a6)
          movea.l   Timer_Port,a1
          moveq.l   #MP_SIZE,d0
          jsr       _LVOFreeMem(a6)

AbortCreateTimer2:
AbortCreateTimer1:
          move.l    GraphicsBase,a1
          jsr       _LVOCloseLibrary(a6)

          moveq.l   #0,d0
          rts

********************* END OF TERMINATION SECTION ***************************
****************************************************************************

 SkipAbortSection:

********** initialize timerequest struct ***********

          ;alloc timerequest structure
          moveq.l   #IOTV_SIZE*2,d0
          move.l    #(MEMF_CLEAR!MEMF_PUBLIC),d1
          jsr       _LVOAllocMem(a6)
          tst.l     d0
          beq.s     AbortTime_ReqAlloc
          movea.l   d0,Time_ReqPTR

          move.w    #((NT_MESSAGE<<8)+Priority),LN_TYPE(Time_ReqPTR)
          ; The previous instruction actually does the next two.
          ;    move.b    #NT_MESSAGE,LN_TYPE(Time_ReqPTR)
          ;    move.b    #Priority,LN_PRI(Time_ReqPTR)
          move.l    Timer_Port,MN_REPLYPORT(Time_ReqPTR)
          ; cleared in the AllocMem call
          ;         clr.w     IO_FLAGS(Time_ReqPTR)  ; this also zeros IO_ERROR
          move.w    #TR_ADDREQUEST,IO_COMMAND(Time_ReqPTR)

          moveq.l   #IOTV_SIZE,d0
          move.w    #((NT_MESSAGE<<8)+Priority),LN_TYPE(Time_ReqPTR,d0.w)
          move.w    #TR_GETSYSTIME,IO_COMMAND(Time_ReqPTR,d0.w)


********** open vblank timer device **************

          moveq.l   #1,d3
          moveq.l   #IOTV_SIZE,d4
        OpenDeviceLOOP:
          lea       TimerName(pc),a0
          lea       (Time_ReqPTR),a1
          moveq.l   #UNIT_VBLANK,d0
          moveq.l   #0,d1
          jsr       _LVOOpenDevice(a6)
          tst.l     d0
          bne.l     AbortOpenDevice
          adda.l    d4,Time_ReqPTR
        dbra    d3,OpenDeviceLOOP
          sub.l     d4,Time_ReqPTR
          sub.l     d4,Time_ReqPTR

********** initialize the Sprite *******************

          lea       SimpleSprite(pc),SimpleSpritePTR
          move.l    SimpleSpritePTR,a0
          moveq.l   #-1,d0
          exg       a6,GraphicsBase
          jsr       _LVOGetSprite(a6)
          exg       a6,GraphicsBase
          tst.l     d0
          bmi       AbortGetSprite

          move.w    #43,4(SimpleSpritePTR)
          clr.l     6(SimpleSpritePTR)

********** initialize newData structure used by the Sprite **********

          move.w    #newDataSIZE,d0
          move.l    #(MEMF_CLEAR!MEMF_CHIP),d1
          jsr       _LVOAllocMem(a6)
          tst.l     d0
          beq       AbortNewDataAlloc
          movea.l   d0,NewDataPTR

          moveq.l   #(newDataSIZE/4)-3,d0
          lea       4(NewDataPTR),a0
          move.l    #term,d1
        BEGINnewDataLOOP:
          move.l    d1,(a0)+
        dbra        d0,BEGINnewDataLOOP ;^
          move.w    #colon,88(NewDataPTR)

********** initialize program register variables *****************

          lea       buffer(pc),BufferPTR
          moveq.l   #AndMaskVALUE,AndMask

********************* main loop *******************

          bsr.s     GetSecsTillNextMin
          cmpi.w    #5,Secs
        bpl.s DisplayTime ;V
        bra.s WaitTillNextMin ;V
    RESTART:
          bsr.s     GetSecsTillNextMin
        DisplayTime:
          bsr.s     LoadBuffer
          bsr.s     GetSecsTillNextMin  ;only needs to get Secs & Micros
        WaitTillNextMin:
          move.w    Secs,2+IO_SIZE+TV_SECS(Time_ReqPTR)
          move.l    Micros,IO_SIZE+TV_MICRO(Time_ReqPTR)
          movea.l   Time_ReqPTR,a1
          jsr       _LVODoIO(a6)
    bra RESTART ;^

***************************** SUBROUTINES **********************************
GetSecsTillNextMin:
          moveq.l   #SEC_PER_MIN-1,Secs
          move.l    #MICRO_PER_SEC-1,Micros
          ; get the current time
          lea       IOTV_SIZE(Time_ReqPTR),a1
          jsr       _LVODoIO(a6)

          ; calculate # of Secs. till next minute and put it in d0
          move.l    IOTV_SIZE+IO_SIZE+TV_SECS(Time_ReqPTR),d0
          sub.l     IOTV_SIZE+IO_SIZE+TV_MICRO(Time_ReqPTR),micros
          ; moveq.l   #0,Temp  ; clear to zero meaning AM
          divu      #SEC_PER_HALF,d0
          ; lsr.b     #1,d0           ; test lsbit by shifting it into C bit
      ; bcc.s     AM ;V
          ; moveq.l   #12,Temp      ; make it 12 if PM
      ; AM:
          swap      d0              ; d0 = # of seconds in the half day
          exg       d0,d1
          moveq.l   #0,d0
          move.w    d1,d0
          divu      #SEC_PER_MIN,d0
          ; swap      Temp
          move.w    d0,Temp    ; save the minutes*hrs. in half day
          swap      d0
          sub.w     d0,Secs

          rts

**********************************************
LoadBuffer:
          ; BufferPTR = &buffer[0]
          clr.l     (BufferPTR)
          ; load buffer with the current time
          moveq.l   #0,d0
          move.w    Temp,d0        ; d0 has minutes*hrs. in half day
          ; clr.w     Temp
          ; swap      Temp
          divu      #MIN_PER_HOUR,d0
          moveq.l   #0,d1
          add.w     d0,d1           ; prepare # of hours in half day

          divu      #10,d1
          tst.l     d1
        bne.s WasNot12 ;V
          move.w    #$0102,(bufferPTR)+
       bra.s  Load2and3 ;V
        WasNot12:
          tst.w     d1
         bne.s WasNotZero ;V
          BlankOffset: EQU $a
          move.b    #BlankOffset,d1
         WasNotZero:
          move.b    d1,(BufferPTR)+ ; buffer[0]
          swap      d1
          move.b    d1,(BufferPTR)+ ; buffer[1]

       Load2and3:
          swap      d0
          ext.l     d0
          divu      #10,d0
          move.b    d0,(BufferPTR)+     ; buffer[2]
          swap      d0
          move.b    d0,(BufferPTR)+     ; buffer[3]

          ; BufferPTR= &buffer[3]+1

          ; Print Sprite
          lea       num4(NewDataPTR),a1
          bsr.s     LoadSpriteNumber
          ; BufferPTR= &buffer[3]

          lea       num3(NewDataPTR),a1
          bsr.s     LoadSpriteNumber
          ; BufferPTR= &buffer[2]

          lea       num2(NewDataPTR),a1
          bsr.s     LoadSpriteNumber
          ; BufferPTR= &buffer[1]

          lea       num1(NewDataPTR),a1
          bsr.s     LoadSpriteNumber
          ; BufferPTR= &buffer[0]

          moveq.l   #0,d3

          move.l    d3,a0
          move.l    SimpleSpritePTR,a1
          move.l    a2,-(sp)
          movea.l   NewDataPTR,a2
          exg       a6,GraphicsBase
          jsr       _LVOChangeSprite(a6)

          move.l    d3,a0
          move.l    SimpleSpritePTR,a1
          move.l    #300,d0
          moveq.l   #70,d1
          jsr       _LVOMoveSprite(a6)

          move.w    #3,2+IO_SIZE+TV_SECS(Time_ReqPTR)  ; wait 3 Secs
          ; clr.l     IO_SIZE+TV_MICRO(Time_ReqPTR)
          exg       a6,GraphicsBase
          lea       (Time_ReqPTR),a1
          jsr       _LVODoIO(a6)
          exg       a6,GraphicsBase

          moveq.l   #0,d0
          move.l    #244,d1
          move.l    d0,a0
          move.l    SimpleSpritePTR,a1
          jsr       _LVOMoveSprite(a6)
          exg       a6,GraphicsBase

          move.l    (sp)+,a2

          rts

******************** LoadSpriteNumber ************************
; loads the image of the number in -(BufferPTR)
; into the memory whose first long has the address in A1.

LoadSpriteNumber:
          moveq.l   #0,d0
          move.b    -(BufferPTR),d0
          asl.b     #2,d0   ; multiply by 4
          move.l    recipeTABLE(pc,d0.w),d1

          moveq.l   #7,LoopCount
        BEGINdecodeRecipeLOOP:
          move.l    d1,d0
          and.w     AndMask,d0
          move.b    imageTABLE(pc,d0.w),d0
          lsl.w     #4,d0
          move.w    d0,(a1)+
          addq.l    #2,a1
          lsr.l     #4,d1  ; get next 4-bit incoded image
        dbra      LoopCount,BEGINdecodeRecipeLOOP ;^

          rts

        imageTABLE:
          DC.B twoDots
          DC.B Dot1
          DC.B Dot2
          DC.B blank
          DC.B Dot4
          DC.B Dot5
          DC.B Dot6
          DC.B Dot7
          DC.B Dot8
          DC.B bar
          DC.B lBar
          DC.B rBar
          DC.B sBar
          DC.B triple
          DC.B fours2Dot
          DC.B onesBigDot

        recipeTABLE:          CNOP 0,2
          zero:     DC.L $c000000c
          one:      DC.L $d44444f4
          two:      DC.L $b111c88c
          three:    DC.L $a888c88a
          four:     DC.L $7779eee7
          five:     DC.L $c088a119
          six:      DC.L $c00a112f
          seven:    DC.L $5556788b
          eight:    DC.L $c000c00c
          nine:     DC.L $6788b00c
          space:    DC.L $33333333


***********************************************************************
*    SECTION "initialized data",DATA

          twoDots:    EQU $81
          Dot1:       EQU $80
          Dot2:       EQU $40
          blank:      EQU $00
          Dot4:       EQU $10
          Dot5:       EQU $08
          Dot6:       EQU $04
          Dot7:       EQU $02
          Dot8:       EQU $01
          bar:        EQU $ff
          lBar:       EQU $fe
          rBar:       EQU $7f
          sBar:       EQU $7e
          triple:     EQU $38
          fours2Dot:  EQU $82
          onesBigDot: EQU $30

          colon:    EQU $0440
          term:     EQU $7ffe
          line:     EQU $00


          AndMaskVALUE:     EQU $f

          TimerPortName:      CNOP 0,2
                    DC.B 'timer',0
          Priority: EQU 0
          TimerName:          CNOP 0,2
                    DC.B 'timer.device',0
          GraphicsName:       CNOP 0,2
                    DC.B 'graphics.library',0

          MICRO_PER_SEC:    EQU 1000000
          SEC_PER_MIN:      EQU 60
          MIN_PER_HOUR:     EQU 60
          SEC_PER_HOUR:     EQU (60*60)
          SEC_PER_HALF:     EQU (60*60*12)
          SEC_PER_DAY:      EQU (60*60*24)
          HOUR_PER_DAY:     EQU 24

          newDataSIZE:      EQU 196-16
          ; newData offsets
              num1:         EQU 12
              num2:         EQU 52
              num3:         EQU 96
              num4:         EQU 136

*     SECTION "uninitialized data",BSS
          buffer:             CNOP 0,2
                    DS.B 4
          SimpleSprite:       CNOP 0,2
                    DS.W 6
     END
