        title   SNDDVR -- Epoc/Os Sound Device Driver Example
        subttl  Copyright (c) Psion PLC 1992
        name    SNDDVR
;
;  VER    DATE    BY  DESCRIPTION
; ----- -------- ---- -----------
; 1.00A 21/04/92 JH   Alpha release
;
TURBOC	equ	1
;
        include epocdef.inc
        include epocmac.inc
        include epoclib.inc
        include epocser.inc
        include ossibo.inc

HandlerDisable         equ     000h     ; the hnadler should not be called
HandlerEnable          equ     001h     ; the handler should be called

        CodeSeg

        ProcBegin@ SnddvrLDD
;                 ==========
; The externally loadable device table
        dw      LDDSignature
        db      'MUS',0,0,0,0,0
        dw      (VectorEnd-Vector)/2
Vector:
        dw      SnddvrInstall
        dw      SnddvrRemove
        dw      SnddvrHold
        dw      SnddvrResume
        dw      SnddvrReset
        dw      SnddvrUnits
        dw      SnddvrOpen
        dw      SnddvrStrategy
VectorHandler:
        dw      SnddvrHandler
VectorEnd:
        ProcEnd noret

SoundChanOpen   db      ?

	ProcBegin@ CancelWrite
;		   ===========
; Cancel any outstanding I/O write request.
; Set the completion status word to E_FILE_CANCEL and signal the I/O 
; semaphore.
; In:
;       BX - sound control block ptr
; Out:
;       NONE
;
        mov     dl, CancelErr
        IoSerSignalUserWrite    ; complete any write with E_FILE_CANCEL
        IoSerTimerCancel        ; harmless if nothing queued
        mov     al, HandlerDisable
        IoSerSetHandler         ; make SnddvrHandler non callable
; FALL THROUGH to StopSound
        ProcEnd noret
;

	ProcBegin@ StopSound
;		   =========
; Stop the sound by turning off the sound chip and shuting down the
; amplifier.
; In:
;       BX - sound control block ptr
; Out:
;       NONE
;
	mov	dx, PSoundControl
	mov	al, 1
	out	dx, al                  ; stop sound chip
	HwComboOff                      ; stop amplifier
	ret
	ProcEnd noret
;

        ProcBegin@ PlaySounds
;                  ==========
; Play any more sounds available.
; Set the note volume from bits 6 and 7 of the first byte.
; Write the note to the chip from bits 0-5 of the first byte.
; Queue the timer to expire on timeout specified in 2nd byte.
; In:
;       BX      - channel control blk
; Out:
;       AL as required by SnddvrHandler routine
;
        cmp     [bx].IoRequestRqWrite.RqA2Ptr, 0
        je      finishedAllNotes
        cld
        dec     [bx].IoRequestRqWrite.RqA2Ptr   ; one less note to play
        mov     si, [bx].IoRequestRqWrite.RqA1Ptr
        lodsw                   ; note+volume to AL, duration to AH
        mov     [bx].IoRequestRqWrite.RqA1Ptr, si
        xor     cx, cx
        mov     cl, ah                  ; the duration (in 1/10ths)
        push    ax
        shr     ax, 1                   ; factor out the volume bits
        and     al, 60h                 ; into bits 5+6 for the hardware
        mov     ah, al
	mov	al, A2IWrite
	out	A2Index, al
	mov	al, ah
	out	A2Control, al           ; set the volume for that note
        pop     ax
	mov	dx, PSoundControl
        and     al, 3fh                 ; bottom 6 bits are note info
	out	dx, al                  ; and play the note
        xor     dx, dx
        IoSerQueueTimer                 ; DX:CX is timeout in 1/10ths
        mov     al, 1                   ; enable handler
        ret
finishedAllNotes:
        call    StopSound               ; stop sound activity
        IoSerSignalUserWriteOk          ; write request completed ok
        xor     ax, ax                  ; disable handler
        ret
        ProcEnd noret
;

        ProcBegin@ SnddvrInstall,far
;                  =============
; Install vector called when application calls p_loadldd()
; In:
;       NONE
; Out:
;       Carry clear if installed ok
;       Carry set if failed to install
;
        and     SoundChanOpen, 0        ; no channel open yet
        clc                             ; installed OK
        ret
        ProcEnd noret
;

        ProcBegin@ SnddvrRemove,far
;                  ============
; Remove vector called when application calls p_devdel()
; In:
;       NONE
; Out:
;       Carry clear if removed ok
;       Carry set if failed to remove
;
        cmp     SoundChanOpen, 0
        je      soundRemoved            ; dont do anything if not open
        mov     al, InUseErr
        stc                             ; channel open, fail to remove
        ret
        ProcEnd noret
; 

        ProcBegin@ SnddvrReset,far
;                  ===========
; Reset vector called if the client terminated.
; We can only get a reset if weve got channel open and client died
; since we cancel the reset request when the channel is closed.
; NOTE: The clients data space no longer exists thus all data required to
; close down the channel must be avaliable in the drivers space.
; In:
;       NONE
; Out:
;       NONE
;
        call    StopSound
        HwFreeCombo             ; allow other sound components to work.
        and     SoundChanOpen, 0        ; no channel open now
soundRemoved:
        clc                     ; removed OK.
        ret
        ProcEnd noret


        ProcBegin@ SnddvrHold,far
;                  ==========
; Hold called when another driver loaded or switched off/power fail.
; In:
;       AH - reason.
; Out:
;       NONE

        cmp     SoundChanOpen, 0
        je      soundHeld       ; dont do anything if not open
        call    StopSound       ; stop any current sounds.
soundHeld:
        ret
        ProcEnd noret

        ProcBegin@ SnddvrResume,far
;                  ============
; Resume called after a Hold to allow the driver to continue.
; If we were running the outstanding timer request will complete at some
; in the future at which point we will play the next note.
; Note - we dont immediatly start playing the note that was being played
; when the hold was called - this may be a bug for some applications.
; In:
;       NONE
; Out:
;       NONE

        cmp     SoundChanOpen, 0
        je      soundResumed    ; dont do anything if not open
	HwComboOn               ; waithandler will run eventually
soundResumed:
        ret                     ; when the timer expires if we were active.
        ProcEnd noret

        ProcBegin@ SnddvrUnits,far
;                  ===========
; We can only handle 1 sound channel at a time.
;
        mov     ax, 1                  ; support 1 unit at a time
        ret
        ProcEnd noret

        ProcBegin@ SnddvrOpen,far
;                  ==========
; Open the sound device driver.
; This runs is the context of the process that has called p_open("MUS:").
;
; In:
;       DS=ES=SS - Process data space.
;       DX      - OS device handle
;       SI      - ptr to OpenEnt struct
; Out:
;       DX      - OS device handle
;   Carry Clear
;       BX      - channel, DX is as passed (device handle)
;   Carry Set
;       AL      - error number
;
; All device drivers MUST have a ChanEnt as first item at returned BX
;
	HwGetCombo              ; see if already in use, if not grab it
	jc	sndAlreadyInUse

; allocate in clients heap the I/O control block, add vector 9 as a
; waithandler and open a timer channel. Tidies up all resources if there
; are any errors.
        mov     cx, (size IoRequestEnt)
        IoSerOpenTimerHandler
        jc      endOpenNoMemory ; sets up ChanEnt, returns BX

; request reset vector be called if client terminates before closing channel
        xchg    bx, dx
        xor     cx, cx
        IoRequestReset          ; BX=device handle, CX= channel indicator
        xchg    bx, dx
        mov     SoundChanOpen, 1
        clc                     ; Opened Ok, BX=channel, DX=device
        ret
;
endOpenNoMemory:
        push    ax
        HwFreeCombo             ; free up as failed
        pop     ax
        stc
sndAlreadyInUse:
        ret
        ProcEnd noret

        ProcBegin@ SnddvrHandler,far
;                  ==============
; Called when an I/O signal is generated, it may be our timer to indicate
; that we should play the next note if any.
; In:
;       DS,ES,SS == Process data space
;       BX = the open sound channel
; Out:
;       Carry clear
;           Did not consume signal
;       Carry set
;           al = 0 - Leave handler disabled
;           al = non 0 - Enable handler
;

; check that were not currently in any StategyVector code, (anti nesting)
        test    [bx].IoRequestFlags, RQ_DISABLE_HANDLER
        jnz     signalNotForUs

; see if we currently have a timer request outstanding
        test    [bx].IoRequestFlags, RQ_TIMER
        jz      signalNotForUs  ; timer not queued

; see if the outstanding timer has completed
        cmp     [bx].IoRequestTimerStat, PendingErr
        je      signalNotForUs  ; timer still pending

; timer completed, play next sound if any and re-queue timer
        and     [bx].IoRequestFlags, NOT RQ_TIMER
        call    PlaySounds      ; returns AL as required
        stc
        ret
signalNotForUs:
        clc                     ; we didnt use the signal
        ret
        ProcEnd noret


        ProcBegin@ SnddvrStrategy,far
;                  ==============
; Strategy vector called by an I/O request on the opened sound device driver
; channel.
; The wait handler may be called (when enabled ) unless we block it when we 
; call a sync I/O request. Allowing the waithandler to run whilst in the 
; strategy vector is generally not a good thing to do.
; In this particular example there are synchronous calls to the I/O system
; to cancel the outstanding timer (if queued) and use the signal generated
; by the cancel.
;
; In:
;       DS,ES,SS == Process data space
;       BX == channel control block from open request
;       DX == device handle
;       SI == RqEnt pointer
; Out:
;       Carry clear
;           - request queued sucessfully (may have completed)
;       Carry set
;           - error queuing the I/O request
;
	cld

; Stop the WaitHandler from being called temporarily
        or      [bx].IoRequestFlags, RQ_DISABLE_HANDLER

	mov	al, byte ptr [si].RqFunction
	cmp	al, IoFuncWrite
	jne	tryCancel

; P_FWRITE I/O function request
        mov     di, [si].RqA2Ptr
        mov     cx, [di]                ; length to write
        mov     di, [si].RqA1Ptr        ; ptr to data to write
        mov     si, [si].RqStatusPtr
        IoSerCheckWriteSI               ; set *SI to Pending, PendingPanic's
        mov     [bx].IoRequestRqWrite.RqA2Ptr, cx
        mov     [bx].IoRequestRqWrite.RqA1Ptr, di
        mov     al, HandlerEnable
        IoSerSetHandler         ; make SnddvrHandler callable
	HwComboOn               ; start the amplifier
        call    PlaySounds      ; start the sound 
        and     [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER
        ret
tryCancel:
	cmp	al, IoFuncCancel
        jne     tryClose

; P_FCANCEL I/O function request
        call    CancelWrite     ; cancel any outstanding requests
        and     [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER
        jmp     short signalOk  ; cancel completed ok.
tryClose:
	cmp	al, IoFuncClose
        jne     notForUs

; P_FCLOSE I/O function request
        push    bx
        push    dx
        call    CancelWrite     ; cancel any outstanding requests

; cancel the request to call the Reset vector 
        pop     bx              ; our I/O device handle
	xor	cx, cx          ; MUST be same as passed to IoRequestReset
	IoRequestResetCancel    ; dont call reset vector now

; close timer, remove handler, free alloc
        pop     bx
        IoSerCloseTimerHandler
        HwFreeCombo             ; not in use any more
        and     SoundChanOpen, 0        ; no channel open now
signalOk:
	xor	ax, ax
	mov	di, [si].RqStatusPtr
	stosw                   ; set completion status word to zero
	IoSignal                ; complete the I/O request
	xor	ax, ax          ; also clears carry
	ret
notForUs:
        and     [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER
        IoRoot                  ; pass on request as not for us
        ret
        ProcEnd noret

        EndCodeSeg
;
stack   segment stack para 'data'
;
stack   ends
;
        end     SnddvrLDD

