Source file: /~heha/hs/dos/dosmisc.zip/SRC/CDP.ASM

;
; CDP - CD-Player for DOS
;
; (C) 1994 by Henrik Drewelow
; (c) 1996,97 haftmann#software

	include prolog.asm
	P186
	jumps

	include	"instchk.asm"	;Installations-Check über Int 2F
;Das Programm ist dadurch prinzipiell mehrfach installierbar

BiosKey	equ	1

macro CallRet lbl
	call	lbl
	ret
endm

ComEntry:	jmp	Main		;JMP to Main at bottom of seg
;****************************************
;** Residente Daten für TSR-Verwaltung **
;****************************************

;Festlegungen für INSTCHK.ASM
MUXI		db	0	;Autodetect
AnsCode		equ	'#P'	;Antwortcode für Int2F
VerCode		equ	101h	;Versionsnummer
IntTab:	IntEntry	08	;Interrupttabelle für instchk
	IntEntry	09	;Definiert Nummer, OldIntXX und NewIntXX,
	IntEntry	13	;dadurch problemlose Deinstallation
	IntEntry	28	;auch bei verschobenen Routinen,
	IntEntry	2F	;nur die Nummern müssen stimmen
		db	0	;Ende der Liste

; Residenter Code ******************************************************

InDosClr	=	bit 0		;InDos=0 at popup time
InInt2F		=	bit 1		;MSCDEX wurde gerufen
InInt13		=	bit 2		;BIOS disk I/O
InInt28		=	bit 3		;INT28 handler
In28Call	=	bit 4		;we have issued INT28
iPopupRequest	=	bit 5		;inverse popup-Anforderung
PoppedUp	=	bit 6		;popup routine activated
BPActive	=	bit 7		;Hintergrundprozeß aktiv
BitsUnsafe	=	InInt13 or In28Call or InInt2F or PoppedUp or BPActive
TsrMode		db	iPopupRequest	;bits for various modes

KeyMode		db	0                       ;bits for hotkey status
HotKeyOn	=	bit 2			;full hotkey pressed
;Bit 3: CDP-Hauptschleife aktiv, Timer darf keine Treiberfunktionen rufen
;Bit 4: Wird vom Int09 bei Break-Codes gelöscht, um Loslassen der
;	Spultaste zu erfassen (temporär?)
;Bit 6: BIOS-Tastenauswertung lahmlegen, stattdessen LastScancode benutzen
LastScancode	db	0		;1-Byte-Puffer reicht für CDP!

InDosPtr	dd	?		;seg:off of InDos flag
CritErrPtr	dd	?		;seg:off of CritErr flag

;==============================================================================
; DATA FOR INT09H HANDLER TO CHECK FOR HOTKEY COMBINATION
;==============================================================================

HotKeyShift	db	bit 2 + bit 3	;shift state for popup
HotKey		db	19h		;hot key for popup ('P')

;==============================================================================
; DATA FOR INT08H HANDLER TO CHECK FOR POPUP
;==============================================================================

SafeWait	db	0			;count-down for safe popup
MaxWait		equ	8			;wait no more 8/18 sec

;==============================================================================
; PROCESS & SYSTEM DATA
;==============================================================================

OldSP		dw	0		;old stack off
OldSS		dw	0		;old stack seg

;************************************
;** Residente Daten für CD-Spieler **
;************************************

; CD-Device-Driver Request Header Struct

struc tReqHdr
 hdrlen		db	13	; Length of request header
 subunit	db	0	; Subunit code for minor devices
 command	db	?	; Command code field
 status		dw	?	; Status
 dum		db	8 dup (?) ; Reserved
 data		db	15 dup (?) ; additional data
ends tReqHdr


; CD-Device-Driver IOCRTL Read/Write Request Header Struct

struc tIOCTRL
 hdrlen		db	13	; Length of request header
 subunit	db	0	; Subunit code for minor devices
 command	db	3	; Command code field
 status		dw	?	; Status
 dum		db	8 dup (?) ; Reserved
 media		db	0	; Media descriptor byte from BPB
 bufptr		dd	?	; Pointer to data buffer
 buflen		dw	16	; Length of data buffer
 dum1		db	6 dup (?) ; Reserved
ends tIOCTRL

; CD-Device IOCRTL Read Request Buffer Struct

struc tIOBuf
 Request	db	?	; Request code
 Data		db	15 dup (?) ; additional data
ends            tIOBuf

struc tMSF	;Minuten-Sekunden-Frame
 f	db	?
 union
  time	dw	?
  struc
   s	db	?
   m	db	?
  ends
 ends
ends tMSF


; CD-Track Buffer

struc           tTrackBuf
 nr		db	?	; Track number on CD
 start		tMSF	<>	; Start sector
 len		tMSF	<>	; Track sectors
 ctrladr	db	?	;CTRL+ADR-Byte (auf 8-Byte-Grenze bringen)
ends            tTrackBuf


; variables ==========================================================


cddrive		dw	0		; CDROM Drive letter
reqhdr		tReqHdr	<>		; Request Header
IOCtrl		tIOCTRL	<>		; IOCRTL Buffer
IOBuf		tIOBuf	<>		;IOCTRL Data Buffer
tracks          tTrackBuf 27 dup (<>)	; CD Audio-Track Buffer
nPlaylist	db	27 dup (?)	;normale Playliste
sPlaylist       db	27 dup (?)	;gesicherte Originalliste bei Random

ST_WaitDisk	equ	0	;Keine CD im Laufwerk
ST_WaitChange	equ	1	;CD im Laufwerk, aber keine Audiospuren
ST_Ready	equ	2	;CD im Laufwerk mit Audiospuren, eingelesen
ST_Playing	equ	3		;  3 -> playing
ST_NextTrack	equ	4		;  4 -> next track
ST_Pausing	equ	5		;  5 -> pausing
CDPStat		db	ST_WaitDisk	; CDP Status
QTrack		db	?	;Q-Tracknummer
QTimeTrack	TMSF	<>	;Q-Zeit bezogen auf Spuranfang
QTimeDisk	TMSF	<>	;Q-Zeit bezogen auf Disk-Anfang
maxdtime	TMSF	<>	;Zeit aller Titel in der nPlaylist
CurTnoIdx	db	0	;Tracknummer-Index in nPlaylist (0=nichts)
keymodus	db	0	;Bedien- (0) oder Spurtaster (1) fokussiert
KeyFocused	db	0	;Fokussierter Bedientaster
TrackFocused	db	1	;Fokussierte Spur 1-basiert!
k_Play		equ	0
k_Pause		equ	1
k_Stop		equ	2
k_Prev		equ	3
k_Succ		equ	4
k_Eject		equ	5
k_PlayMode	equ	6
k_Random	equ	7
k_TimeMode	equ	8
;LED-Zustände usw. für die einzelnen Tasten (für SetXxxFocus)
KeyLights	dw	0	;Bedientasten-LED's
playmode        db           0          ; Mode of playing
;Zufallsgenerator (ist nicht gerade mein Liebling)
RandSeed        dd           ?          ; Random Number
Factor		dw	8405h          ; Factor for random
;Bildschirm
label CdpScr	dword		;Zeiger
		dw	ofs SCR_MAIN
		dw	0	;Segment (DS) wird vom Hauptprogramm gesetzt
LineIncr	db	SCR_MAIN_WIDTH ;Zeichen pro Zeile (im Puffer oder auf Schirm)
vid_CurPage	db	0	;aktuelle Bildschirmseite
vid_LeftTop	dw	5*256+5	;Cursorposition linke obere Ecke des Fensters
vid_WidthHeight	dw	?	;Größe Bildschirm (für Dragging)
vid_CursS	dw	1*256+0	;Cursorform (shape)
vid_CursP	dw	0	;Cursorposition
vid_graph	db	0	;Grafikmode-Flag sowie Maus
;Bit 0: 1=Grafikmodus, 0=Textmodus
;Bit 1: 0=16 Farben, 1=256 und mehr Farben im Grafikmodus
;Bit 4: 1=Maus vorhanden (bei Installation)

;Sonstiges
timercnt        db           0          ; Counter for timer int
wasplaying      db           0          ; Key pressed during playing
canedit         db           1          ; Set if can add to edit list
locked          db           0          ; Flag for locked tray

Strat	dd	?
Inter	dd	?	;Strategie- und Interrupt-Eintrittspunkt des Treibers


; constants ==========================================================


include "cdpscr.asm"

scr_nodisk:	dz 'no disk inserted'
scr_noaudiodisk: dz 'no audio tracks found'
scr_locked:	dz 'disk tray is locked'
scr_clear:	dz '                         '
scr_mode:	dz 'cont  '
		dz 'edit  '
		dz 'scan  '
		dz 'single'
scr_help:	dz 'start play '
		dz 'pause play '
		dz 'stop play  '
		dz 'prev. track'
		dz 'next track '
		dz 'eject/close'
		dz 'play mode  '
		dz 'random play'
		dz 'time format'
		dz 'resume play'
		dz 'cont. play '
		dz 'play track '
		dz 'sel. track '

;Farbtabelle (12 verwendete Farben)
Colors	db      01h	;Rand blau auf schwarz
	db	1Bh	;Rahmen helltürkis auf blau
	db	00h	;Unsichtbare Zahlen schwarz auf schwarz
	db	0Bh	;Betriebsart, aktuelle Spur und Zeitangaben
;---
	db	01h	;Sichtbare Zahlen
	db	10h	;Fokussierte Zahlen
	db	03h	;Hervorgehobene (ausgewählte) Zahlen (normal!)
	db	30h	;Hervorgehoben und fokussiert
;---
	db	70h	;Taste schwarz auf weiß
	db	30h	;Fokussierte Taste
	db	7Ah	;Hervorgehobene Taste
	db	3Ah	;Hervorgehobene und fokussierte Taste

;***************************************
;** TSR-Verwaltungsroutinen, resident **
;***************************************

;==============================================================================
; TestSafe - CHECK IF THIS IS A SAFE TIME TO DO A POP UP
;
; RETURN CLC IF SAFE TO POP UP, CY IF NOT SAFE.
;
; CHECK IF WE ARE IN AN OUR OWN INT28 CALL (In28Call)
; CHECK 8259A PIC ISR REGISTER FOR MISSING EOIs
; CHECK IF DOS IS STABLE FOR POP UP
; CHECK IF A PRINT SCREEN IS IN PROGRESS
;==============================================================================
; DS muß aufs Datensegment zeigen!
;VR: AX,BX,Flags
proc TestSafe near
; ------------	CHECK THE 8259A PIC ISR REGISTER FOR NON-EOIed HW INTs
	mov	al,00001011b		;tell 8259A we want the ISR
	out	20h,al			;8259A command reg
	jmp	$+2			;now, ISR should be ready
	in	al,20h			;AL=mask of active INTs
	or	al,al			;test all (IRQ0 *did* EOI)
	jnz	@@NotSafe		;jump if active INTs
; ------------	NOW, ENSURE THAT DOS WAS NOT INTERRUPTED
	push	ds
	 lds	bx,[InDosPtr]		;now, DS:BX=InDos
	 mov	al,[by bx]		;get InDos to AL
	 lds	bx,[cs:CritErrPtr]	;now, DS:BX=CritErr
	 or	al,[by bx]		;both flags zero?
	pop	ds
	jz	@@Safe?			;YES - DOS is really idle
	BTST	[TsrMode],InInt28	;is this an INT28h
	jz	@@NotSafe		;NO - not safe, should be idle
	cmp	al,1			;YES - one InDos entry only?
	ja	@@NotSafe		;NO - jump if more than one
@@Safe?:
; ------------	CHECK TO SEE IF A PRINT SCREEN IS IN PROGRESS
	push	ds
	 LD	ds,50h
	 cmp	[by 0],1		;print screen in progress?
	pop	ds
	je	@@NotSafe		;YES - jump if prtsc
; ------------	SEEMS TO BE A SAFE TIME FOR POPUP
	clc				;CLC=safe to popup
	jr	@@e			;end this then
; ------------	APPARENTLY THIS IS JUST NOT THE TIME TO DO A POPUP
@@NotSafe: stc				;CY=don't popup now
; ------------	RETURN TO CALLER WITH CARRY SET/CLEAR
@@e:	ret
endp TestSafe


;Testet, ob Fenster erscheinen soll, und läßt es ggf. erscheinen.
;Testet dabei das Bit PopupRequest, und setzt bei Stackumschaltung PoppedUp
;VR: Flags
proc CheckRequest
	BTST	[cs:TSRMode],iPopupRequest or BitsUnsafe
	jnz	@@e		;weder Anforderung noch Erlaubnis
	sti
	push	ax bx ds
	 LD	ds,cs
	 call	TestSafe	;InDOS usw. sagt OK?
	 jc	@@e1		;nein, nicht aufpoppen!
	 BSET	[TSRMode],PoppedUp or iPopupRequest
	 mov	bx,ofs Popup	;Popup-Routine soll angesprungen werden
	 call	InitPopup
	 mov	[TimerCnt],0	;Nicht gleich anschließend Hintergrundprozeß
	 BRES	[TSRMode],PoppedUp
@@e1:	pop	ds bx ax
@@e:	ret
endp


;Testet, ob Hintergrundarbeit gestartet werden kann. Das erfolgt
;etwa halbsekündlich, um Rechenleistung zu schonen
;VR: Flags
proc CheckBackground
	inc	[cs:TimerCnt]
	cmp	[cs:TimerCnt],9
	jc	@@e		;raus, zu wenig Zeit ist vergangen
	BTST	[cs:TSRMode],BitsUnsafe
	jnz	@@e		;keine Erlaubnis
	sti
	push	ax bx ds
	 LD	ds,cs
	 call	TestSafe	;InDOS usw. sagt OK?
	 jc	@@e1		;nein, nicht aufpoppen!
	 BSET	[TSRMode],BPActive
	 mov	bx,ofs CDPTimer	;Timer-Routine soll angesprungen werden
	 call	InitPopup
	 mov	[TimerCnt],0	;hier erst zurücksetzen
	 BRES	[TSRMode],BPActive
@@e1:	pop	ds bx ax
@@e:	ret
endp
	ret


;==============================================================================
; OurInt08 - TSR INT08H HANDLER TO WATCH FOR HOTKEY AND SAFE POPUP TIMES
;
; CALL OldInt08
; CHECK FOR RE-ENTRANCE INTO CRITICAL INT08 CODE
; SET InInt08 FLAG
; CHECK IF HOTKEY WAS PRESSED
; CHECK IF ALREADY InPopup OR InInt28
; CHECK IF SAFE TIME FOR SYSTEM TO POPUP
; UPDATE FLAGS AND CALL POPUP IF SAFE
; GIVE ERROR BEEP IF POPUP WAS UNSAFE FOR A LONG TIME
; RESET InInt08 FLAG
; DO IRET
;==============================================================================

proc NewInt08 far
	pushf				;simulate INT08
	 call	[cs:OldInt08]		;call TSRs loaded before us
	call	CheckRequest	;Popup-Fenster aufrufen, wenn erforderlich
	call	CheckBackground	;Hintergrund-Routine starten, wenn Zeit um
	iret
endp

;==============================================================================
; OurInt09 - TSR INT09H HANDLER TO WATCH FOR HOTKEY
;
; COMPARE FOR KEY MATCH
; SET HotKeyOn IF HOTKEY PRESSED
; Tastendruck selbst verschlucken
; DO IRET
; else JMP OldInt09
;==============================================================================

proc ClearKey
	in	al,61h		;Zeichen abholen (!!)
;	mov	ah,al
	BSET	al,bit 7
	out	61h,al		;Bit 7[61h] setzen
;	xchg	al,ah
	BRES	al,bit 7
	out	61h,al		;auf alten Wert zurück
	mov	al,61h		;EOI spezifisch IRQ1
	out	20h,al
	ret
endp

proc NewInt09 far
	push	ds ax		;save regs used
	 LD	ds,40h		;BIOS-Datensegment
	 mov	al,[17h]	;Shift-Byte
	 LD	ds,cs
	 BTST	[KeyMode],bit 6	;Kein BIOS-Zugriff?
	 jnz	@@nobios
	 BTST	[TSRMode],PoppedUp	;Sichtbar oder im Aufbau?
	 jnz	@@ToOld		;ja, nicht noch einmal aufpoppen!
	 BTST	al,bit 0	;RightShift gedrückt?
	 jz	@@1
	 BSET	al,bit 1	;LeftShift drücken (keine Unterschiede machen)
@@1:	 not	al		;Bits drehen
	 and	al,[HotKeyShift]
	 jnz	@@ToOld		;und wegspringen
	 in	al,60h
	 cmp	al,[HotKey]	;Unsere heiße Taste?
	 jnz	@@ToOld
	 call	ClearKey	;Tastaturpuffer leeren
	 BRES	[TSRMode],iPopupRequest	;aufpoppen?
	pop	ax ds
	call	CheckRequest	;sollte kein Register ändern...
	iret			;zurück
@@nobios:
	 in	al,60h		;Al=key, preserved by BIOS
	 cmp	al,0E0h		;Präfix-Code erweiterte Taste?
	 jz	@@3		;gleich "wegwerfen"
	 BTST	al,bit 7	;Loslaß-Code?
	 jz	@@2
	 BRES	[KeyMode],bit 4	;Flag löschen für Spulknöpfe
	 jr	@@3		;und Loslaß-Code nicht speichern
@@2:	 mov	[LastScancode],al
@@3:
ifndef BIOSKEY
	 call	ClearKey
	pop	ax ds
	iret
endif
@@ToOld:
	pop	ax ds
	jmp	[cs:OldInt09]	;weiter mit altem Handler
endp NewInt09

proc NewInt13 far
	pushf				;save flags we use
	 BSET	[cs:TsrMode],InInt13	;remember we are in BIOS now
	popf				;restore flags
	pushf				;simulate INT13
	call	[cs:OldInt13]		;let BIOS handle it all
	pushf				;BIOS uses flag return
	 BRES	[cs:TsrMode],InInt13	;tell people we left INT13h
	 call	CheckRequest		;aufpoppen, wenn möglich
	popf
	sti
	retf	2			;throw flags off stack
endp NewInt13

proc NewInt28 far
	BSET	[cs:TSRMode],In28Call or InInt28;tell'em we are here
	pushf
	 call	[cs:OldInt28]		;call TSRs loaded before this
	BRES	[cs:TSRMode],In28Call
	call	CheckRequest
	BRES	[cs:TSRMode],InInt28
	iret				;we have nothing more to say
endp

proc NewInt2F far
	pushf
	 cmp	ah,15h		;MSCDEX/NWCDEX-Funktion wird gerufen
	 je	@@a		;ja, Semaphor aufbauen
	 cmp	ah,[cs:MUXI]	;Installationstest-Funktion?
	 je	@@t		;ja
	popf
	jmp	[cs:OldInt2F]	;Nur anspringen, da einige andere Int2F-
			;Funktionen Stack-Parameterübergabe praktizieren
@@t:
	popf
	or	al,al
	jnz	@@e
	MuxResponse		;Makro aus INSTCHK.ASM, Antwort-Code erzeugen
@@e:	iret
@@a:
	 BSET	[cs:TSRMode],InInt2F
	popf
	pushf
	 call	[cs:OldInt2F]	;MSCDEX rufen
	pushf
	 BRES	[cs:TSRMode],InInt2F
	 call	CheckRequest	;Prüfen, ob nicht etwas zu tun ist
	popf
	sti
	retf	2
endp

;Aufruf mit DS=CS; AX und Flags gerettet, BX=Ansprungadresse, Interrupt frei
;Routine darf niemals verschachtelt gerufen werden, da Benutzung desselben
;Stacks. Die Prüfung geschieht außerhalb.
proc InitPopup
	mov	[OldSS],ss	;save current stack frame
	mov	[OldSP],sp
	LD	ss,ds
	mov	sp,102h		;Kommandozeile muß als Stack herhalten
; ------------	SAVE ALL REGS
	pusha
	push	es
; ------------	TAG VALUE OF InDos FLAG AT TIME OF POPUP
	 les	si,[InDosPtr]	;now, ES:[SI]=InDos
	 cmp	[by es:si],1	;InDos set? (>2 impossible)
	 jnc	@@1		;NO - jump if all clear DOS
	 BSET	[TsrMode],InDosClr	;InDos war 0
@@1:
	 cld			;!! Klare Verhältnisse !!
	 LD	es,ds
	 call	bx		;Aufruf!
	 BRES	[TsrMode],InDosClr
; ------------	RESTORE USER REGS
	pop	es
	popa
	mov	ss,[OldSS]	;restore SS
	mov	sp,[OldSP]	;restore SP
	ret
endp InitPopup

;***********************************
;***********************************
;** Residente CD-Spieler-Routinen **
;***********************************
;***********************************

; library procedures -------------------------------------------------

_ANUM
_OCHR
_ALDEZ
_CASE

macro PrintXY x:req,y:req,str:rest
 ifnb <str>
	lea	si,[str]
 endif
	mov	dx,y*256+x
	call	PrintScr
endm

tick  dw 0

proc  ErrBeep

	    push    es
	    ld      es,40h
	    mov     ax,[es:6Ch]
S1:         cmp     [es:6Ch],ax
	    je      S1              ;wait till clock tick changes
	    inc     ax
	    mov     [tick],ax

	    mov     al,0B6h
	    out     43h,al          ;get timer ready

	    mov     ax,0C00h

	    out     42h,al
	    mov     al,ah
	    out     42h,al          ;set freq

	    in      al,61h
	    or      al,3
	    out     61h,al          ;turn speaker on

	    mov     ax,[tick]
S2:         cmp     ax,[es:6Ch]
	    je      S2              ;do for one clock tick

	    in      al,61h          ;turn off speaker
	    and     al,11111100b
	    out     61h,al

	    pop     es
	    ret

endp  ErrBeep

;*******************************
;** Aufruf des Gerätetreibers **
;*******************************


RequestLux:	;Luxusvariante mit AH=Code und AL=Kopflänge
	mov	[ReqHdr.hdrlen],al
	mov	[ReqHdr.command],ah
; Performs a driver request
;
; Return : al -> 0 - ok
;                1 - busy
;                2 - error   ah -> error code
;Puffer ist fest auf ReqHdr gestellt!
proc  Request
	push	es bx	;Verwendete Register retten
	 lea	bx,[ReqHdr]
DoRequest:
	 LD	es,ds
	 call	[Strat]
	 call	[Inter]
;	 push	cx
;	  mov	cx,[cddrive]
;	  MUX	1510h	; Pass to mscdex
;	 pop	cx
	pop	bx es
	;läuft durch zu CheckReturn!
endp  Request

; Checks Driver request return code
;
; Return : al -> 0 - ok CY=0
;                1 - busy CY=0
;                2 - error   ah -> error code CY=1
proc  CheckReturn
	mov	ax,[IOCTRL.status]
	BTST	ax,bit 15
	jnz	@@error
	BTST	ax,bit 9
	jnz	@@busy
	xor	ax,ax
	ret
@@busy:
	mov	ax,1
	ret
@@error:
	mov	ah,al
	BRES	ah,bit 7
	mov	al,2
	stc
	ret
endp  CheckReturn


IOCTRLReadLux:	;Luxusvariante mit AH=Code und AL=Pufferlänge
	mov	[by LOW IOCTRL.buflen],al	;Hi-Word ist sowieso immer 0
	mov	[IOBuf.request],ah

; Performs a IOCTRL Read request
;
; Return : al -> 0 - ok
;                1 - busy
;                2 - error   ah -> error code
;Puffer sind fest auf IOCtrl und IOBuf gestellt
proc  IOCTRLRead
	push	es bx
	 lea	bx,[IOCTRL]
	 mov	[(TIOCTRL bx).command],3	; Set up for read
	 jmp	DoRequest
endp  IOCTRLRead


IOCTRLWriteLux:	;Luxusvariante mit AH=Code und AL=Pufferlänge
	mov	[by LOW IOCTRL.buflen],al	;Hi-Word ist sowieso immer 0
	mov	[IOBuf.request],ah
; Performs a IOCTRL Write request
;
; Return : al -> 0 - ok
;                1 - busy
;                2 - error   ah -> error code
;Puffer sind fest auf IOCtrl und IOBuf gestellt
proc  IOCTRLWrite
	push	es bx
	 lea	bx,[IOCTRL]
	 mov	[(TIOCTRL bx).command],12	;Schreiben
	 jmp	DoRequest
endp  IOCTRLWrite


;******************************************
;** Einfache CDROM-Gerätetreiberzugriffe **
;** (ohne Zugriff auf CD-Tabellen)       **
;******************************************


; Gets the device status
;
; Return : dx:cx -> Status
proc  getdevicestatus
	mov	ax,0605h
	call	IOCTRLReadLux
	lea	si,[IOBuf.data]
	mov	cx,[si]
	mov	dx,[si+2]
	ret
endp  getdevicestatus

; Eject tray
;
proc Eject
	mov	ax,0*256+1
	CallRet	IOCTRLWriteLux
endp Eject

; Close tray
;
proc CloseTray
	mov	ax,5*256+1
	CallRet	IOCTRLWriteLux
endp  CloseTray


; Checks if media was changed  ( not if drive busy !!! )
;
; Return : carry set     -> media changed
;          carry not set -> media not changed
proc  mediachanged
	mov	ax,0902h
	call	IOCTRLReadLux
	mov	al,[IOBuf.Data]
	add	al,1
	ret
endp  mediachanged


; Checks if a CD is inserted
;CY=1: keine CD im Laufwerk, dann CDPStat:=0
proc  checkdisk
	call	getdevicestatus		; Get drive status
	BTST	cx,bit 11
	jz	@@DiskInserted
	mov	[CDPStat],0
	stc
@@DiskInserted:
	ret
endp  checkdisk


; Checks if the tray is locked
;
; Return : carry set     -> tray locked
;          carry not set -> tray not locked
proc  checktray
	call	getdevicestatus		; Get drive status
	BTST	cx,bit 1
	jnz	@@notl
	stc
@@notl:
	ret
endp  checktray

; Stop playing
;
proc  Stop
	mov	ax,133*256+13
	CallRet	RequestLux
endp  Stop


; Continue playing
;
proc  Continue
	mov	ax,136*256+13
	CallRet	RequestLux
endp  Continue

;Ein "Stückchen" CD abspielen (zum CUE-Spulen wichtig)
;PE: AH:AL:DL=Startposition
;    CH:CL:DH=Länge
;VR: ziemlich alle
;Vorher wird immer ein Stop-Kommando abgesetzt.
proc PlayPiece
	LD	es,ds
	push	cx ax		;CX bewußt ganz unten
	  call	Stop		;erforderlich, falls CD gerade spielt
	  lea	di,[reqhdr.data]
	  xor	al,al
	  stosb			;Adressiermodus LBN (High Sierra)
	 pop	ax
	 push	dx		;DH retten
	  call	lbn		;in LBN (High Sierra) umrechnen
	  sub	ax,150		;2 Sekunden abziehen
	  stosw			;LBN Lo-Word
	  xchg	ax,dx
	  sbb	ax,0		;abziehen lt. Gleichung
	  stosw			;LBN Hi-Word
	 pop	dx
	 xchg	dl,dh
	pop	ax		;...was vorher CX war
	call	lbn		;in LBN (High Sierra) umrechnen
	stosw			;LBN Lo-Word
	xchg	ax,dx
	stosw			;LBN Hi-Word
	mov	ax,132*256+22
	call	RequestLux	;PLAY-Request
	ret
endp

;***************************
;** (Um)Rechnungsroutinen **
;***************************

;Zeitdifferenz berechnen
;PE: AH:AL:DL: MSF Minuend, CH:CL:DH: MSF Subtrahend
;PA: AH:AL:DL: MSF Differenz, CY=1: Negatives Ergebnis
proc Diff
	sub	dl,dh
	jnc	@@1
	add	dl,75	;Rahmen pro Sekunde
@@1:	sbb	al,cl
	jnc	@@2
	add	al,60	;Sekunden pro Minute
@@2:	sbb	ah,ch
	jnc	@@3
	add	ah,60	;Carry bleibt i.d.R.
@@3:	ret
endp

;Zeitsumme berechnen
;PE: AH:AL:DL: MSF Akku, CH:CL:DH: MSF Summand
;PA: AH:AL:DL: MSF Akku
proc Sum
	add	dl,dh
	cmp	dl,75	;Rahmen pro Sekunde
	jc	@@1	;Wenn kleiner, dann KEIN Übertrag
	sub	dl,75	;(Carry unverändert)
@@1:	cmc		;Carry ist immer verkehrtherum, daher drehen!
	adc	al,cl
	cmp	al,60
	jc	@@2
	sub	al,60	;Sekunden pro Minute
@@2:	cmc
	adc	ah,ch
	ret
endp

;Zeitangaben von MSF (Red Book) nach LBN (High Sierra) umrechnen
;PE: AH:AL:DL: Angabe in MSF
;PA: DX:AX: Angabe als LBN (Futter für PlayAudio-Funktion)
;VR: AX,DX,Flags
;Es erfolgt hier keine Korrektur auf Diskanfang (sub 150)!
proc lbn
	push	cx
	 mov	cl,dl	;Rahmen retten
	 xor	ch,ch	;auf 16 bit erweitern, später verwenden
	 mov	dh,al	;Low-Teil retten
	 mov	al,60	;Multiplikation vorbereiten
	 mul	ah	;AX=Sekunden der Minutenangabe
	 add	al,dh	;Sekunden dazu
	 adc	ah,0	;AX=volle Sekunden
	 mov	dx,75	;Rahmen pro Sekunde
	 mul	dx	;DX:AX=Rahmen der Minuten- und Sekundenangabe
	 add	ax,cx	;Rahmen dazu
	 adc	dx,0	;LBN ist fertig.
	pop	cx
	ret
endp

;Luxusvariante - bei EDIT-Modus erfolgt nur eine Indirektion!
;(wichtig für die korrekte Anzeige)
proc GetAdrOfIdxLux
	cmp	[PlayMode],1
	je	@@1
;PE: AL=Track-Index
;PA: AX=Zeiger in tracks-Liste (doppelte Indirektion!)
;Kein Gültigkeitstest, außer AL=0 liefert [tracks]
GetAdrOfIdx:
	or	al,al
	jz	@@1	;Nicht umrechnen (das Längenbyte ist kein Index)
	push	bx
	 lea	bx,[nPlaylist]
	 xlatb		;Index verrechnen
	pop	bx
@@1:	mov	ah,size TTrackBuf	;Einzelelement
	mul	ah	;AX=Differenz zum Anfang
	add	ax,ofs tracks
	ret
endp

;PA: AL=Höchste angezeigte Display-Tracknummer (je nach Edit-Modus)
proc GetMaxDisplayTrack
	mov	al,[nPlaylist]
	cmp	[PlayMode],1
	jne	@@e
	mov	al,[tracks.nr]
@@e:	ret
endp

;Zeit von Trackindex BL bis (einschließlich) BH berechnen
;BL und BH müssen gültig sein, es erfolgt kein Test!
;(jedoch bei BL>BH kommt die Zeit 0 heraus)
;PE: BL: Trackindex "von", BH: Trackindex "bis"
;PA: AH:AL:DL: Zeitsumme
;VR: AX,DL,BL,CX,SI
;Bem.: AH:AL:DL enthält in der Schleife die zu summierende Zeit
proc CalcTimeFromTo
	xor	ax,ax
	mov	dl,al		;Zeit-Akku nullsetzen
	jr	@@foot
@@l:
	xchg	si,ax		;AX retten
	mov	al,bl
	call	GetAdrOfIdx
	xchg	ax,si		;AX holen und Indexregister benutzen
	mov	dh,[(TTrackBuf si).len.f]
	mov	cx,[(TTrackBuf si).len.time]
	call	Sum
	inc	bl
@@foot:
	cmp	bh,bl		;BL größer?
	jnc	@@l		;nein, Schleifenrunde drehen
	ret
endp

; Calculates time of all tracks in play list
;
; * Setzt MaxDTime auf die Zeitsumme
proc  CalcTLTime
	mov	bl,1		;von Spur 1
	mov	bh,[nPlaylist]	;bis zur letzten
	call	CalcTimeFromTo
	mov	[maxdtime.f],dl
	mov	[maxdtime.time],ax
	ret
endp  CalcTLTime

;********************************
;** High-Level-Treiberzugriffe **
;** (mit Zugriff auf Tabellen) **
;********************************

; Get the q-channel info
;PA: AX: Returncode von IOCTLRead (also 1 für "busy", sonst andere Werte)
proc GetQChannel
	mov	[IOBuf.data],1		;Binär bitte!
	mov	ax,12*256+11
	call	IOCTRLReadLux
	push	ax
	 mov	si,ofs IOBuf.data+1
	 lodsb			;Achtung! - immer BCD!
	 mov	ah,al
	 shr	ah,4
	 and	al,0fh
	 aad			;deshalb binär wandeln (mit AAD!)
	 mov	[QTrack],al
	 lodsw			;al verwerfen
	 mov	[QTimeTrack.m],ah	;Minute
	 lodsw
	 mov	[QTimeTrack.s],al	;Sekunde
	 mov	[QTimeTrack.f],ah	;Rahmen
	 lodsw
	 mov	[QTimeDisk.m],ah
	 lodsw
	 mov	[QTimeDisk.s],al
	 mov	[QTimeDisk.f],ah	;Alles einzeln, weil Bytes verdreht
	pop   ax
	ret
endp GetQChannel


; Read in track data
;PA: CY=1: IOCTL-Fehler

;Das Feld "tacks" beinhaltet im Element 0 Startzeit und Länge der CD
;sowie die Anzahl der vorhandenen Audiospuren;
;die übrigen Elemente enthalten die tatsächliche Spurnummmer sowie
;Startzeit und Länge der Spur
;Alle Angaben im binärkodierten MSF (Red Book) Format

;Besseres Vorgehen:
;* Disk-Info einlesen, Disklänge merken
;* Datenspur-Flag:=TRUE
;a Spur-Info lesen
;* Datenspur-Flag=TRUE ? goto b
;* Differenz zum Anfang der vorhergehenden Spur berechnen
;* Errechnete Differenz bei vorhergehender Spur speichern
;b aktuell Datenspur ? Datenspur-Flag:=TRUE; goto c
;* Aktuellen Spur-Anfang eintragen
;* Datenspur-Flag:=FALSE
;c Noch nicht letzte Spur ? goto a  (Schleife)
;* Datenspur-Flag=TRUE ? goto d
;* Differenz Disklänge zu Anfang vorhergehender Spur berechnen
;d
;Also keine ganz einfache Sache
; (deswegen war das Ursprungsprogramm hier verwirrend programmiert)
proc ReadInTrackData
	mov	ax,10*256+7		;Disk-Info einlesen
	call	IOCTRLReadLux
	jc	@@e1			;Fehler!
	lea	si,[IOBuf.data]
	lodsb				;Kleinste Tracknummer
	mov	bl,al
	lodsb				;Größte Tracknummer
	mov	[tracks.start.f],al	;als Puffer mißbrauchen!
	mov	[tracks.nr],0		;Anzahl Audiospuren mitzählen
	lodsb			;Lead-Out-Track (Erster Nicht-mehr-Track) LOW
	mov	[tracks.len.f],al
	lodsw				;-"- HIGH
	mov	[tracks.len.time],ax
	mov	bh,40h			;Datenspur-Flag in BH setzen
	lea	di,[tracks+size TTrackBuf]
	jr	@@foot			;WHILE-Schleife realisieren
@@everytrack:
	mov	[IOBuf.data],bl		;aktuelle Spur-Nummer
	mov	ax,11*256+7		;Spur-Info einlesen
	call	IOCTRLReadLux
@@e1:	jc	@@e			;Fehler!
	test	bh,40h			;Letzte Spur war Datenspur?
	jnz	@@1			;ja, Sprung
	cmp	[tracks.nr],26		;26 Audiospuren bereits gefunden?
	jae	@@4			;Nur noch Länge zum Ende berechnen
;Schluß machen, die übrigen Spuren werden auf Nr. 26 zusammengefaßt
;in der Hoffnung, daß es alles (abspielbare) Audiospuren sind.
	mov	dl,[(by IOBuf.data)+1]	;Zeitdifferenz berechnen
	mov	ax,[(wo IOBuf.data)+2]	;AH:AL:DL = Minuend
	call	Diff			;AH:AL:DL := AH:AL:DL - CH:CL:DH
	mov	[(TTrackBuf di).len.f],dl
	mov	[(TTrackBuf di).len.time],ax
	add	di,size TTrackBuf	;müßten 7 Bytes sein
@@1:
	mov	dh,[(by IOBuf.data)+1]
	mov	cx,[(wo IOBuf.data)+2]	;CH:CL:DH = Startpukt neue Spur
	mov	bh,[(by IOBuf.data)+5]	;neues CTRL+ADR-Byte
	test	bh,40h
	jnz	@@2
	inc	[tracks.nr]		;Noch eine Audiospur!
	mov	[(TTrackBuf di).nr],bl	;Wahre Tracknummer aufheben
	mov	[(TTrackBuf di).start.f],dh
	mov	[(TTrackBuf di).start.time],cx
	mov	[(TTrackBuf di).ctrladr],bh ;für später zur Anzeige aufheben
@@2:
	inc	bl
@@foot:
	cmp	bl,[tracks.start.f]	;Merker für letzte Spur
	jbe	@@everytrack		;bis kleiner/gleich
;Ende der Schleife
	test	bh,40h			;Letzte Spur war Datenspur?
	jnz	@@3			;ja, Sprung
@@4:
	mov	dl,[tracks.len.f]	;nein: Zeitdifferenz berechnen!
	mov	ax,[tracks.len.time]	;AH:AL:DL = Minuend
	call	Diff			;AH:AL:DL := AH:AL:DL - CH:CL:DH
	mov	[(TTrackBuf di).len.f],dl
	mov	[(TTrackBuf di).len.time],ax
@@3:
	mov	dl,[tracks.len.f]
	mov	ax,[tracks.len.time]
	mov	cx,2			;die übliche CD-Startzeitangabe
	mov	dh,ch			;(immer 00:02:00)
	call	Diff			;AH:AL:DL := AH:AL:DL - CH:CL:DH
	mov	[tracks.start.f],dh
	mov	[tracks.start.time],cx
	mov	[tracks.len.f],dl
	mov	[tracks.len.time],ax
@@e:	ret
endp

;Universelle Routine "PlayPieceOfCurTrack" muß her!
;PE: AH:AL:DL=Startzeit relativ zum aktuellen Track
;    CH:CL:DH=Länge höchstens zu spielen
;PA: CY=1: Startzeit zu hoch

; Play a track
;
; Input  : bl -> index in tracks
;BL=0 spielt die ganze CD ab (nur OK, wenn keine Datenspuren drauf sind)
;BH=1: Nur "scannen" (die ersten 10 Sekunden anspielen)

proc PlayScan
	mov	bh,1
	jr	PlayScan1
endp
proc Play
	xor	bh,bh
PlayScan1:
	lea	si,[tracks]
	mov	al,size tTrackBuf
	mul	bl
	add	si,ax		;Adresse im Feld ermittelt
	mov	dl,[(TTrackBuf si).start.f]
	mov	ax,[(TTrackBuf si).start.time]
	mov	dh,[(TTrackBuf si).len.f]
	mov	cx,[(TTrackBuf si).len.time]
	or	bh,bh
	jz	@@1		;Ganz spielen
	cmp	cx,10		;10 Sekunden (und keine Minuten)?
	jc	@@1		;Titel ist kürzer, nichts tun
	mov	cx,10
	mov	dh,ch		;nur 10 Sekunden ansetzen
@@1:
	call	PlayPiece
	mov	[CDPStat],3
	ret
endp  Play

;***************************
;** Bildschirm-Funktionen **
;***************************

proc SwapCursor
	mov	bh,[vid_CurPage]
	VID	3		;Cursor ermitteln
	xchg	[vid_CursS],cx
	xchg	[vid_CursP],dx
	VID	1		;Cursorform setzen
	VID	2		;Cursorposition setzen
	ret
endp
; Swap the main screen of CDP and the old data
;

proc  SwapMainScreen
	push	es
	 lea	si,[SCR_MAIN]
	 mov	dx,0*256+0
@@l1:
	 call	CalcAdr
	 mov	cx,SCR_MAIN_WIDTH	; Cols
@@l2:
	 lodsw
	 xchg	[es:di],ax
	 mov	[si-2],ax
	 add	di,2
	 loop	@@l2
	 inc	dh		;nächste Zeile
	 cmp	dh,SCR_MAIN_DEPTH
	 jnz	@@l1
	pop	es
	ret
endp  SwapMainScreen


proc CalcAdr	;Adresse ES:DI aus Cursorposition DX berechnen
;PE: DX=Cursorposition
;PA: ES:DI=Adresse
; zeigt entweder im das Programm-Image (wenn nicht aufgepopt oder
; Grafikmodus, also ES=DS und DI=offset TheDrawImage)
; oder in den Video-Speicher (aufgepopt im Textmodus)
;VR: ES,DI,Flags
	push	ax
	 mov	al,dh
	 mul	[LineIncr]	;AX=Zeile mal Zeichen/Zeile
	 add	al,dl		;Spalte dazu
	 adc	ah,0
	 add	ax,ax		;Verdoppeln
	 les	di,[CdpScr]	;Anfangsadresse
	 add	di,ax		;Jetzt wirkliche Adresse
	pop	ax
	ret
endp CalcAdr

proc PrintScr	;Zeichenkette ausgeben, Farbbytes bleiben
;PE: SI: (Nullterminierter) String
;    DX: Cursorposition DH=Zeile, DL=Spalte
	push	es di cx
	 call	CalcAdr
	 xor	cl,cl
	 jr	@@1
@@l:	 stosb
	 inc	cl
	 inc	di
@@1:	 lodsb
	 or	al,al
	 jnz	@@l	;Nächstes Zeichen
	 call	Redraw
	pop	cx di es
	ret
endp

;Ab Cursorposition vorhandene Zeichen mit neuer Farbe versehen
;PE: DX: Cursorposition
;    CL: Anzahl Zeichen
;    CH: neues Farbbyte
proc TouchColor
	push	es di ax cx
	 mov	al,ch
	 xor	ch,ch
	 jcxz	@@e
	 call	CalcAdr
	 mov	ah,cl
@@l:
	 inc	di
	 stosb
	 loop	@@l
	 mov	cl,ah		;Anzahl herstellen
	 call	Redraw
@@e:	pop	cx ax di es
	ret
endp

;So simpel diese Routine auch klingt, so kompliziert ist sie.
;PE: DX: Cursorposition Zeile (DH) und Spalte (DL)
;    AX: Zeichen (AL) und Attribut (AH)
;Es erfolgt keine Ausgabe, wenn das Zeichen außerhalb liegt
proc PutChar
	cmp	dl,[by LOW vid_WidthHeight]
	jnc	@@e
	cmp	dh,[by HIGH vid_WidthHeight]
	jnc	@@e
	push	cx bx
	 mov	bh,[vid_CurPage]
	 mov	bl,ah	;Farbbyte retten
	 VID	2	;Cursor positionieren
	 mov	cx,1	;Nur 1 Zeichen!
	 BTST	[vid_Graph],bit 1	;256-Farben-Flag?
	 jnz	@@256
	 BTST	[vid_Graph],bit 2	;40-Spalten-Text-Flag?
	 jnz	@@16	;Einfach Zeichen ausgeben
	 mov	ah,bl	;Farbbyte zurück
	 push	ax
	  shr	bl,4	;Hintergrundfarbe als Vordergrundfarbe anbieten
	  VID	9,'█'	;Vollcursor in Hintergrundfarbe (Codepage 437 und 850)
	 pop	ax
	 xor	bl,ah	;Vordergrundfarbe als XOR-Wert
	 and	bl,0fh	;Hintergrndfarbe ausmaskieren
	 or	bl,80h	;XOR-Bit setzen
	 jr	@@16
@@256:	 mov	bh,bl
	 shr	bh,4	;Hintergrundfarbe nach BH
	 and	bl,0fh
@@16:	 VID	9	;1 Zeichen mit V-Farbe (und Anzahl) schreiben
	pop	bx cx
@@e:	ret
endp

;Grafikbildschirm aktualisieren (ab CGA)
;Diese Routine tut bei gelöschtem Grafikflag nichts
;PE: DX: Cursorposition
;    CL: Zeichenzahl
;VR: ES(=DS),DI
proc Redraw
	push	cx
	 BTST	[vid_Graph],bit 0
	 jz	@@e
	 xor	ch,ch
	 jcxz	@@e
	 push	bx dx si
	  call	CalcAdr		;haben wir ES:DI als Quelladresse(!)
	  xchg	si,di
	  add	dl,[by LOW vid_LeftTop]	;Linke obere Ecke des Fensters
	  add	dh,[by HIGH vid_LeftTop]
@@l:
	  lodsw
	  call	PutChar
	  inc	dl	;Nächste Spalte
	  loop	@@l
	 pop	si dx bx
@@e:	pop	cx
	ret
endp Redraw

;Grafikfenster löschen (beim Drücken von ESC schwarzes Loch hinterlassen)
;VR: ES,DI,si
proc RemoveWindow
	push	bx cx dx
	 mov	bh,[vid_CurPage]
	 xor	bl,bl	;Farbe schwarz
	 mov	dx,[vid_LeftTop] ;globale Koordinaten!
	 mov	cx,SCR_MAIN_DEPTH ;Zeilenzahl
	 mov	si,SCR_MAIN_WIDTH
	 cmp	dl,-3	;Sonderfall, Extrawurst
	 jnz	@@l
	 mov	dl,0
	 mov	si,[vid_WidthHeight]	;wirkliche Breite nehmen
	 and	si,0ffh	;nur Low-Teil
@@l:	 push	cx
	  VID	2	;Cursorposition setzen
	  mov	cx,si	;selten: sinnvolle Verwendung für CX
	  VID	9,' '	;Zeile löschen
	 pop	cx
	 inc	dh
	 loop	@@l
	pop	dx cx bx
	ret
endp RemoveWindow

;Anzeige des CDP-Fensters. LineIncr sowie der CdpScr-Zeiger müssen stimmen.
;(Ausnahme Textmodus: Offsetanteil von CdpScr wird anhand der Cursorposition
;(vid_LeftTop) neu berechnet). Fürs Dragging vorgesehen.
proc ShowWindow
	BTST	[vid_Graph],bit 0
	jnz	@@noswap1
	mov	dx,[vid_LeftTop]
	mov	al,[LineIncr]
	mul	dh		;AX=Zeile mal Zeichen/Zeile
	xor	dh,dh
	add	ax,dx		;Spalte dazu
	add	ax,ax		;Verdoppeln wegen Attributbyte
	push	es
	 LD	es,40h
	 add	ax,[es:4eh]	;Startoffset aktive Seite noch dazu
	pop	es
	mov	[wo LOW CdpScr],ax	;wirklicher bzw. neuer Anfang
	jmp	swapmainscreen	;PopUp CDP !!!
@@noswap1:
	xor	dx,dx
@@g4:	mov	cl,SCR_MAIN_WIDTH
	call	Redraw		;Jede Zeile einzeln auf Schirm bringen
	inc	dh
	cmp	dh,SCR_MAIN_DEPTH
	jnz	@@g4
	ret
endp

;Verschwindenlassen des CDP-Fensters. LineIncr und CdpScr werden NICHT
;zurückgesetzt - das muß nachgeholt werden, wenn Bildschirmzugriffe
;erwünscht sind. Für Dragging, ShowWindow kann unmittelbar folgen
;(nach Änderung von vid_LeftTop)
proc HideWindow
	BTST	[vid_Graph],bit 0
	jz	swapmainscreen	;Textmodus-Lösung
	jmp	RemoveWindow	;Grafikmodus-Lösung
endp

;********************************************
;** Routinen für die Oberflächengestaltung **
;********************************************

;Track-(Slot)-Nummer zu Cursorposition wandeln
;PE: CL: Track-Nummer 0=aktuell 1..26: in Liste
;PA: DX: Cursorposition linke Ziffer
;VR: DX,Flags
proc Tno2Cursor
	push	cx
	 mov	dx,3*256+24	;Position 3/24
	 or	cl,cl
	 jz	@@e
	 mov	dx,1*256+1	;Position 1/1
	 cmp	cl,13
	 jbe	@@s1
	 sub	cl,13
	 inc	dh		;Zeile 2
@@s1:	 add	dl,cl
	 add	dl,cl
	 add	dl,cl		;Spalte cl*3+1
@@e:	pop	cx
	ret
endp

;Aus Tastennummer Cursorposition (Links) ermitteln
;PE: BL=Tastennummer
;PA: DX=Cursorposition
;VR: DX, Flags

proc Key2Cursor
	push	bx
	 mov	dx,5*256+3
	 cmp	bl,2
	 jbe	@@n
	 inc	dl
@@n:	 cmp	bl,4
	 jbe	@@n2
	 inc	dl
	 inc	dl
@@n2:	 cmp	bl,5
	 jbe	@@n3
	 inc	dl
	 inc	dl
@@n3:	 shl	bl,2
	 add	dl,bl
	pop	bx
	ret
endp Key2Cursor


;Anzahl Tracks in nPlaylist anzeigen
ShowTrackCount:
	mov	al,[nPlayList]
ShowTrackNumber0:
	mov	bl,[Colors+3]
	xor	cl,cl
; Show a track number
;
; Input  : al -> Track number
;          bl -> Screen attribute
;          cl -> Slot position
;                0      = Track number at status line
;                1 - 26 = Track number at track info display
;VR: AX,DX,ES,DI,Flags
proc  ShowTrackNumber
	push	es di
	 call	Tno2Cursor	;Cursorposition aus CL ermittlen
	 call	CalcAdr
	 call	ALDEZ
	 stosb
	 mov	al,bl		;Attribut
	 stosb
	 mov	al,ah		;2. Zeichen
	 stosb
	 mov	al,bl		;Noch ein Attribut
	 stosb
	 push	cx
	  mov	cl,2		;2 Zeichen
	  call	Redraw
	 pop	cx
	pop	di es
	ret
endp  ShowTrackNumber


; Show a time
;
; Input  : bx    -> time BH: Minuten, BL: Sekunden
;          carry -> position
;                   0 = Time at current counter
;                   1 = Time at overall counter
;neu:
;PE: AX=Zeit	(auch bei dem alten ShowTime)

proc  ShowTime
	push	es
	 mov	dx,3*256+28
	 jnc	@@1	;Zeitangabe links
	 add	dl,8	;8 Zeichen weiter rechts
@@1:	 call	CalcAdr
	 xchg	bx,ax

	 mov	al,'-'
	 cmp	[KeyLights],bit k_Play
	 jz	@@pos	 ;Nur während Abspiels negativ!
@@check:
	 BTST	[KeyLights],bit k_TimeMode
	 jnz	@@n
@@pos:	 mov	al,' '
@@n:	 call	@@outc
	 mov	al,bh
	 call	@@outd
	 mov	al,':'
	 call	@@outc
	 mov	al,bl
	 call	@@outd
	 mov	cl,6	;6 Zeichen aktualisieren
	pop	es
	Callret	Redraw

@@outd:	call	ALDEZ
	stosb
	inc   di
	mov	al,ah
@@outc:	stosb
	inc	di
	ret
endp  ShowTime


; Clear the display
;

proc  ClearDisplay	; Clear track display
	push	es di dx
	 mov	dx,1*256+4	;ab Position 1/4
	 mov	ch,[Colors+2]	;Anzeigetext verborgen (00)
	 mov	cl,38
	 call	TouchColor
	 inc	dh		;Zeile 2
	 call	TouchColor

	 mov	dx,3*256+16	;Zeile 3
	 call	CalcAdr		;ES:DI ermitteln
	 mov	al,' '
	 mov	cx,27
	 rep	stosw
	 mov	ch,[Colors+3]	;Zeit-Anzeige (0B)
	 mov	cl,27
	 call	TouchColor
	pop	dx di es
	ret
endp  ClearDisplay


; Show the play list
;Es werden alle Positionen der Playlist angezeigt, in der dort vorliegenden
;Reihenfolge und unter Beachtung der "wahren" Spur-Nummer auf der CD
proc ShowPlayList
	call	ClearDisplay
	lea	si,[nPlayList]
	lodsb
	mov	ch,al		;Anzahl
	xor	cl,cl
	jr	@@foot
@@l:
	inc	cl		;mit Platz 1 beginnen
	lodsb			;Track-Nummer
	mov	ah,size TTrackBuf
	mul	ah
	xchg	bx,ax
	mov	al,[(tracks+bx).nr]	;wahre Tracknummer
	mov	bl,[Colors+6]	;Farbe (03)
	call	ShowTrackNumber
@@foot:	cmp	cl,ch
	jc	@@l
	ret
endp ShowPlayList

; Show the edit list
;Es werden alle Spuren des "tracks"-Inhaltsverzeichnisses angezeigt.
;Spurnummern, die auch in der Playlist enthalten sind, werden hervorgehoben
proc ShowEditList
	lea	si,[tracks]
	mov	ch,[(TTrackBuf si).nr]	;Anzahl Spuren
	xor	cl,cl
	jr	@@foot
@@l:
	inc	cl		;Mit Slot 1 beginnen
	add	si,size TTrackBuf
	mov	al,cl
	call	InPlaylist	;In Spiel-Liste enthalten?
	mov	bl,[Colors+4]	;Farbe (01)
	jnz	@@1		;Nicht in Liste
	mov	bl,[Colors+6]	;Farbe (01)
@@1:	mov	al,[(TTrackBuf si).nr]	;wahre Nummer (meist gleich!)
	call	ShowTrackNumber
@@foot:
	cmp	cl,ch
	jc	@@l
	ret
endp ShowEditList


proc  readtrackdata
	mov	[CurTnoIdx],0	;Auf jeden Fall aktuelle Spur zurücksetzen
	call	ReadInTrackData
	jc	@@e
	cmp	[tracks.nr],0
	jz	@@noaudiodisk
	mov	[CDPStat],2
  call  setcontinuous                   ; Set continous mode
	mov	ah,1
	call	DrawCurPKey	;??
	mov	al,[tracks.nr]		; Show track count at status line
	call	showtracknumber0	;an Position 0
	mov	ax,[tracks.len.time] ; Show disk time at status line
	stc
	call	showtime
	clc				; Ok
	ret
 @@noaudiodisk:
	call	cleardisplay
	PRINTXY 17,3,scr_noaudiodisk
	mov	[CDPStat],1		; Wait for disk change
	mov	ah,1
	call	DrawCurPKey	;??
	stc				; No audio disk
@@e:	ret
endp  readtrackdata


;*****************************
;** Verwaltung der PlayList **
;*****************************

InPlaylist:
	xor	ah,ah		;Nur testen
proc AppendRemovePlaylist
;PE: AL: einzufügende oder (falls vorhanden) zu löschende Nummer
;    AH: Modus (was zu tun ist)
;    Bit 0 =1: Anhängen erlaubt (z.B. AH=1: anhängen, falls nicht vorhanden)
;    Bit 1 =1: Löschen erlaubt  (z.B. AH=0: nur testen)
;PA: Z=1: War enthalten (also ggf. NICHT MEHR enthalten)
;    Z=0: War nicht enthalten (also ggf. JETZT ENTHALTEN)
;VR: Flags
	push	es di cx
	 LD	es,ds
	 lea	di,[nPlayList+1]
	 mov	cl,[di-1]
	 xor	ch,ch
	 or	di,di		;Z-Flag löschen (wichtig für CX=0!)
	 repne	scasb
;Z=1: Enthalten, dann: DI zeigt auf Folgenummer, CX=Anzahl Folgezeichen
;Z=0: Nicht enthalten, dann DI zeigt hinter das Listenende, CX=0
	 pushf			;Flags in jedem Falle dem Anwenderprogramm!
	  jz	@@remove
	  BTST	ah,bit 0	;Anhängen erlaubt?
	  jz	@@e
	  mov	[di],al
	  inc	[nPlayList]	;Stringlänge erhöhen
	  jr	@@calc
@@remove:
	  BTST	ah,bit 1	;Löschen erlaubt?
	  jz	@@e
	  push	si
	   mov	si,di
	   dec	di		;Zeichenkettenrest nach vorn schaufeln
	   rep	movsb
	  pop	si
	  dec	[nPlayList]	;Stringlänge erniedrigen
@@calc:	 pusha
	  call	CalcTlTime	;Zeitänderung neu berechnen
	 popa
@@e:	 popf
	pop	cx di es
	ret
endp

;nPlaylist nach sPlaylist kopieren
proc CopyPlaylist
	LD	es,ds		;da war noch ein Problem!
	lea	si,[nPlaylist]
	lea	di,[sPlaylist]
	mov	cx,27
	rep	movsb		;Kopieren
	ret
endp

;* Kopie von nPlayList in sPlayList anlegen (zum Beenden von RANDOM)
;* nPlaylist leeren
;* zufälliges Element aud sPlaylist holen und - wenn nicht bereits vor-
;  handen, nach nPlaylist anhängen
;* Wiederholen, bis alle Elemente erwischt worden.
proc MakeRandomPlaylist
	call	CopyPlaylist
	mov	[nPlaylist],0	;kein Element zunächst
	jmp	@@foot		;WHILE-Schleife...
@@l:

  MOV	AX,[wo LOW RandSeed]
  MOV	BX,[wo HIGH RandSeed]
  MOV	CX,AX
  MUL	[FACTOR]
  SHL	CX,3
  ADD	CH,CL
  ADD	DX,CX
  ADD	DX,BX
  SHL	BX,2
  ADD	DX,BX
  ADD	DH,BL
  MOV	CL,5
  SHL	BX,CL
  ADD	DH,BL
  ADD	AX,1
  ADC	DX,0
  MOV	[wo LOW RandSeed],AX
  MOV	[wo HIGH RandSeed],DX

	mov	ax,[wo RandSeed+1]
	xor	dx,dx
	mov	bx,26	;Listen-Länge
	div	bx	;Zufall bleibt Zufall
	mov	al,dl	;Auf den Rest bin ich scharf
	lea	bx,[sPlaylist+1]	;Vorbereiten für starkes XLAT
	xlatb		;Zugriff (XLAT ist immer wieder schön!)
	mov	ah,1	;Einfügen, nie löschen
	call	AppendRemovePlaylist	;einsortieren!?
	jz	@@l		;war schon einsortiert, neue Zufallszahl
@@foot:
	mov	al,[sPlaylist]
	cmp	[nPlaylist],al	;Liste voll?
	jne	@@l		;nein, neue Runde
	ret
endp

;sPlaylist (Sicherungskopie) zurückspulen
proc ClearRandomPlaylist
	LD	es,ds		;da war noch ein Problem!
	lea	si,[sPlaylist]
	lea	di,[nPlaylist]
	mov	cx,27
	rep	movsb		;Rückschaufeln
	ret
endp

;nPlayList wird mit allen verfügbaren Spuren in normaler Reihenfolge geladen
proc FillPlaylist
	mov	cl,[tracks.nr]	;Vorhandene Track-Anzahl
	LD	es,ds
	lea	di,[nPlayList]
	xor	ch,ch
	mov	al,cl
	stosb			;Anzahl speichern
	mov	al,0
	jcxz	@@skip		;Anzahl 0 (ist sowieso fatal)
@@listloop:
	inc	al		;mit Nummer 1 beginnen
	stosb
	loop	@@listloop
@@skip:	CallRet	CalcTlTime	;Zeit berechnen
endp

;nPlayList wird gelöscht (kein Titel mehr in der Liste)
proc ClearPlaylist
	mov	[nPlayList],0
	CallRet	CalcTlTime	;Zeit "berechnen"
endp

;********************************
;** Tasten-Wiedergabe-Routinen **
;********************************

DrawCurTKey:	;hier nur noch AH=0 oder AH=1 übergeben!
	mov	al,[TrackFocused]
;wie unten, mit automatischem Setzen des Bit 1 von AH
proc MarkTKeyLux
	cmp	[PlayMode],1	;edit?
	jne	@@set
	push	ax		;Fokussierungs-Flag
	 xor	ah,ah
	 call	InPlaylist
	pop	ax
	jnz	@@w
@@set:	BSET	ah,bit 1	;Hervorgehoben darstellen
@@w:
;Track-Anzeige aktualisieren
;PE: al: Positions-Nummer 1-basiert
;    Bit0(ah): Schalter fokussiert
;    Bit1(ah): Schalter aktiviert (leuchtend)
;    Alle anderen Bits müssen 0 sein!
MarkTKey:
	push	dx cx bx
	 mov	cl,al
	 call	Tno2Cursor
	 mov	al,ah
	 lea	bx,[Colors+4]
	 xlatb			;passendes Farbbyte
	 mov	ch,al		;nach CH
	 mov	cl,2		;2 Zeichen
	 call	TouchColor	;Farbe setzen!
	pop	bx cx dx
	ret
endp  MarkTKeyLux


DrawCurPKey:
	mov	al,[KeyFocused]
proc MarkPKeyLux
	push	cx dx
	 mov	cl,al
	 mov	dx,1
	 shl	dx,cl
	 BTST	[KeyLights],dx
	 jz	@@w
	 BSET	ah,bit 1
@@w:	pop	dx cx
;Knopf neu zeichnen (neue Farbe)
;PE: AL=Knopf-Nummer (0-basiert)
;    Bit0(AH): Knopf ist fokussiert
;    Bit1(AH): Knopf-LED leuchtet
MarkPKey:
	push	bx cx dx
	 mov	bl,al		;AL-->DX
	 call	Key2Cursor
	 mov	al,ah
	 lea	bx,[Colors+8]
	 xlatb
	 mov	ch,al		;Farbbyte
	 mov	cl,3
	 call	TouchColor	;Taste mit neuer Farbe
	pop	dx cx bx
	ret
endp  MarkPKeyLux

;Nichts fokussieren!
proc RemoveCurFocus
	push	ax
	 xor	ah,ah
	 BTST	[keymodus],bit 0	;Bedientaste oder Spur?
	 jnz	@@1		;Spur!
	 call	DrawCurPKey
	 jr	@@e
@@1:
	 call	DrawCurTKey
@@e:	pop	ax
	ret
endp

proc SetKeyFocus
;PE: BL: Neue fokusierte Taste
;PA: -
;VR: ??
;Falls gerade ein Track fokussiert ist, wird die Fokkusierung dort entfernt
	call	RemoveCurFocus
	BRES	[keymodus],bit 0
	mov	[KeyFocused],bl
	mov	ah,1
	call	DrawCurPKey
	callret	showhelp	;Gleich noch Hilfe zeigen
endp

proc SetTrackFocus
;PE: BL: Neue fokusierte Spur (1-basiert)
;PA: -
;VR: ??
	call	RemoveCurFocus
	BSET	[keymodus],bit 0
	call	GetMaxDisplayTrack
	MIN	bl,al		;begrenzen !!!
	mov	[TrackFocused],bl
	mov	ah,1
	call	DrawCurTKey
	callret	ShowTrackLen
endp

; Shows the current modus display
;

proc  ShowModus
	mov	al,7		;Länge der Einzelstrings
	mul	[playmode]
	lea	si,[scr_mode]
	add	si,ax
	mov	dx,3*256+17
	CallRet	PrintScr
endp  ShowModus


; Shows the help for a key
; BL=Tastennummer, BH=Fokussierungs-Flag

proc  ShowHelp
	cmp	[locked],1
	jne	@@n4
	PRINTXY	17,3,scr_clear
	call	showmodus
	call	ShowTrackCount
	mov	ax,[maxdtime.time]
	stc
	call	showtime
	mov	[locked],0
	ret
@@n4:
	mov	si,offset scr_help
	BTST	[KeyModus],bit 0
	jne	@@nkeys		;Track-Auswahl aktiv!
	mov	al,12		;Länge der Hilfestrings
	mul	bl		;mal Tastennummer
	add	si,ax		;SI=Adresse Hilfestring
	cmp	bl,1		;Pausentaste?
	jne	@@n3		;nein
	BTST	[KeyLights],bit k_Pause
	jz	@@n3
	add	si,8*12		;8 Strings weiter (brrr!)
	jr	@@n1
@@n3:
	cmp	bl,7		;Taste "R"?
	jne	@@n1
	BTST	[KeyLights],bit k_Random
	jz	@@n1
	add	si,3*12
	jr	@@n1
@@nkeys:
	cmp	[playmode],1
	je	@@n2
	BTST	[KeyLights],bit k_Play
	jz	@@n2
	sub	si,1*12
@@n2:
	add	si,12*12
@@n1:
	mov	dx,3*256+4
	call	PrintScr
	ret

endp  ShowHelp


;Taste hervorheben oder zurückstellen (Status aktiv oder inaktiv)
;
; Input : bl -> Key number
;NEU (temp) BL=Nummer, Status aus KeyLights (nicht BH)
HiliteCurKey:		;auf aktuell fokussierte Taste bezogen
	mov	bl,[KeyFocused]
proc  HilitePKey
	xor	ah,ah
	mov	al,bl
	BTST	[keymodus],bit 0
	jnz	@@w
	cmp	[KeyFocused],al
	jnz	@@w
	BSET	ah,bit 0	;Fokussiert darstellen
@@w:	jmp	MarkPKeyLux
endp  HilitePKey


;*****************************************
;** Funktionalität der Bedienoberfläche **
;*****************************************

; Shuffles the playlist
proc  SetRandom
	call	MakeRandomPlaylist
	jr	ResetRandom1
endp  SetRandom


; Restores playlist
;
proc  ResetRandom
	call	ClearRandomPlaylist
	jr	ResetRandom1
endp  ResetRandom

; Fills playlist with all tracks
;
proc  SetContinuous
	mov	[playmode],0	;"cont"
	call	FillPlaylist	;berechnet auch Zeitsumme
	BTST	[KeyLights],bit k_Random
	jnz	SetRandom	;Zufällig machen, falls Random-Knopf leuchtet
ResetRandom1:
	call	showplaylist	;Normale Anzeige
	callret	showmodus
endp  SetContinuous


; Fills playlist with all tracks
;
proc  SetScan
	mov	[playmode],2	;"scan"
	call	FillPlayList
	call	CopyPlaylist
	jr	ResetRandom1	;Random-Flag hierbei ignorieren
endp  SetScan

;************************************
;** Bedientasten-Aktionsfunktionen **
;************************************

; Procedure for play key
;CY=1 wenn CurTnoIdx mit Bereichsüberlauf
;Setzt CurTnoIdx auf 1, wenn es 0 enthielt
proc  KeyPlay
	mov	[QTimeTrack.time],0
	mov	al,[CurTnoIdx]
	or	al,al
	jnz	@@n
	inc	al
	mov	[CurTnoIdx],al
@@n:
	lea	bx,[nPlayList]
	cmp	[bx],al		;Kein Überlauf?
	jc	@@e
	xlatb			;Indizierter Zugriff
	mov	bl,al
	cmp	[playmode],2
	jne	@@play
	call	playscan
	jr	@@cont
@@play:
	call	play
@@cont:
	BSET	[KeyLights],bit k_Play
	mov	bl,k_Play
	call	HilitePKey
	BRES	[KeyLights],bit k_Pause
	mov	bl,k_Pause
	call	HilitePKey
	clc
@@e:	ret
endp  KeyPlay


; Procedure for stop key
;

proc  KeyStop
	cmp	[playmode],3
	jne	@@norm
	call	setcontinuous
@@norm:
	mov	[CDPStat],2
	BSET	[KeyLights],bit k_Stop
	mov	bl,k_Stop
	call	HilitePKey
	call	stop
	call	stop
	BRES	[KeyLights],bit k_Stop + bit k_Pause + bit k_Play
	mov	bl,k_Stop
	call	HilitePKey
	mov	bx,k_Pause
	call	HilitePKey
	mov	bx,k_Play
	call	HilitePKey
	call	showplaylist
	call	showmodus
	call	ShowTrackCount
	mov	ax,[maxdtime.time]
	stc
	call	showtime
	mov	ah,1
	call	DrawCurPKey	;??
	ret
endp  KeyStop


; Procedure for eject key
;(sie schließt den Laden gegebenfalls wieder)
proc  KeyEject
	call	checktray
	jnc	@@n1
	PRINTXY	17,3,scr_clear
	PRINTXY	20,3,scr_locked
	mov	[locked],1
	ret
@@n1:
	push	cx		;Statusbit von checktray
	 BSET	[KeyLights],bit k_Eject
	 call	HiliteCurKey
	pop	cx
	BTST	cx,bit 0	;Tür auf oder zu?
	jnz	@@c		;auf, also zumachen!
	call	keystop
	call	eject
	jr	@@o
@@c:	call	closetray
@@o:	BRES	[KeyLights],bit k_Eject
	call	HiliteCurKey
	ret
endp  KeyEject


; Procedure for pause key
;

proc  KeyPause
	BTST	[KeyLights],bit k_Play
	jz	@@e		;Wenn kein PLAY, dann kein PAUSE
	BTST	[KeyLights],bit k_Pause
	jz	@@pause
	call	continue
	BRES	[KeyLights],bit k_Pause
	call	HiliteCurKey
	mov	[CDPStat],3
	mov	bh,0
	jr	@@exit
@@pause:
	call	stop
	mov	[CDPStat],5
	BSET	[KeyLights],bit k_Pause
@@exit:
	call	HiliteCurKey
	mov	ah,1
	call	DrawCurPKey	;??
@@e:	ret
endp  KeyPause


; Procedure for random key
;
proc  KeyRandom
	BTST	[KeyLights],bit k_Play
	jz	@@start
	call  keystop
	mov   [wasplaying],1
@@start:
	mov   [canedit],0
	BTST	[KeyLights],bit k_Random
	je    @@random
	call  ResetRandom
	jmp   @@rmn1
@@random:
	call  SetRandom
@@rmn1:
	BTOG	[KeyLights],bit k_Random
	call  HiliteCurKey
	call  showtrackCount
	mov   ax,[maxdtime.time]
	stc
	mov   [CDPStat],2
	call  showtime
	cmp   [wasplaying],1
	jne   @@exit
	mov   [wasplaying],0
	call  keyplay
@@exit:
	ret

endp  KeyRandom


; Procedure for mode key
;

proc  KeyPlayMode
	BTST	[KeyLights],bit k_Play
	jz	@@start
  call  keystop
  mov   [wasplaying],1
 @@start:
  mov   [canedit],1
	mov	al,[playmode]
	inc	al
	cmp	al,3
	jc	@@n1
	xor	al,al		;nur 0, 1 oder 2; Modus "3" gibt's hier nicht
@@n1:	mov	[playmode],al
	or	al,al		;"cont"
	jne	@@m1
	call	setcontinuous	;Playliste füllen
	jr	@@exit
@@m1:
	dec	al
	jnz	@@m2
	call	ClearPlaylist	;Playliste leeren
	call	showeditlist	; Show tracks at display
	call	showmodus	;"edit"
	jr	@@exit
@@m2:
	call	setscan
@@exit:
	call	ShowTrackCount		;Anzahl anzeigen
	mov	ax,[maxdtime.time]
	stc
	call	showtime
	cmp	[wasplaying],1
	jne	@@ex
	mov	[wasplaying],0
	call	keyplay
@@ex:	ret
endp  KeyPlayMode

;Zeigt die Länge der gerade fokussierten Spur an. Man beachte die
;Fallunterscheidung beim Edit-Mode; hier nur eine Indirektion!
;PE: keine, PA: keine
;Keine Anzeige, wenn Play-Knopf leuchtet
proc ShowTracklen
	BTST	[KeyLights],bit k_Play
	jnz	@@e
	mov	al,[TrackFocused]
	cmp	[PlayMode],1	;edit
	je	@@1
	lea	bx,[nPlaylist]
	xlatb
@@1:	mov	ah,size TTrackBuf
	mul	ah
	add	ax,ofs tracks
	mov	bx,ax
	mov	ax,[(TTrackBuf bx).len.time]
;	clc
	call	ShowTime
@@e:	ret
endp


;Wartet, bis Tastenrepeat einsetzt oder Taste losgelassen wird
;liefert in AX ASCII- und Scancode sowie AX=0, wenn Taste losgelassen
proc GetKey
@@l:
ifdef BIOSKEY
	KBD	1
	jz	@@f
	KBD	0
	jr	@@l
@@f:
endif
	xor	ax,ax
	BTST	[KeyMode],bit 4
	jz	@@e		;Losgelassen!
	xor	al,al
	xchg	[LastScancode],al
	or	al,al
	jz	@@l		;Kein Repeat
	mov	ah,al		;Erstmal kompatibel bleiben
@@e:	ret
endp

proc KeyPrev
	BTST	[KeyLights],bit k_Play
	jz	@@e
	BSET	[KeyMode],bit 4	;Loslaßcode einfangen
	BSET	[KeyLights],bit k_Prev
	call	HiliteCurKey
	call	GetKey
	or	ax,ax		;Losgelassen?
	jz	@@WholeTrack
;hier Rückspulroutine einbauen:
;* auf QTimeDisk einige Sekunden subtrahieren
;* Test, ob Zeit im tracks-Verzeichnis enthalten ist, und wo (Nummer!)
;* Wenn nicht enthalten, dann ganzer Titel zurück
;* Wenn kein vorhergehender Titel, dann von hinten (?)
;* Restspielzeit des Titels ermitteln
;* Losspielen
;* Anzeigen aktualisieren
;* Das ganze solange, bis GetKey einen Loslaßcode liefert
@@loop:
	mov	dl,[QTimeTrack.f]
	mov	ax,[QTimeTrack.time]
	mov	cx,10		;10 Sekunden rückspulen
	mov	dh,ch
	call	Diff
	pushf
	push	ax dx
	 mov	cx,ax
	 mov	dh,dl
	 mov	al,[CurTnoIdx]
	 call	GetAdrOfIdx
	 mov	si,ax
	pop	bx cx		;Neue Position im Track
	popf
	mov	dh,bl
	jnc	@@NoPrevTrack	;kein Überlauf bei Subtraktion
	mov	al,[CurTnoIdx]
	dec	al
	jz	@@r		;Bereits vorn!
	mov	[CurTnoIdx],al
	call	GetAdrOfIdx
	mov	si,ax
	mov	dl,[(TTrackBuf si).len.f]
	mov	ax,[(TTrackBuf si).len.time]
	xor	cx,cx
	mov	dh,ch		;Null Sekunden
@@NoPrevTrack:
	push	ax dx
	 mov	dl,[(TTrackBuf si).start.f]
	 mov	ax,[(TTrackBuf si).start.time]
	 call	Sum
	pop	bx cx		;Länge
	mov	dh,bl
	call	PlayPiece
	call	PeriodicAction
	;call	WaitTick	;Etwas ausharren
	call	GetKey
	or	ax,ax
	jnz	@@loop		;Der User hält die Taste immer noch gebannt
	mov	[LastScancode],0	;Wiederauslösung verhindern
	jmp	@@r
@@WholeTrack:
	mov	al,[CurTnoIdx]
@@l:	or	al,al
	jz	@@a		;bei Null lassen (nicht zirkular)
	dec	al
@@a:	mov	[CurTnoIdx],al
;  mov   [QTimeTrack],0
	call	stop
	call	keyplay
@@r:	BRES	[KeyLights],bit k_Prev
	call	HiliteCurKey
@@e:	ret
endp

proc KeySucc
	BTST	[KeyLights],bit k_Play
	jz	@@e
	BSET	[KeyMode],bit 4	;Loslaßcode einfangen
	BSET	[KeyLights],bit k_Succ
	call	HiliteCurKey
	call	GetKey
	or	ax,ax		;Losgelassen?
	jz	@@WholeTrack
;hier Vorspulroutine einbauen:
;* auf QTimeDisk einige Sekunden addieren
;* Test, ob Zeit im tracks-Verzeichnis enthalten ist, und wo (Nummer!)
;* Wenn nicht enthalten, dann ganzer Titel vorwärts
;* Wenn kein Folgetitel, dann von vorn (?)
;* Restspielzeit des Titels ermitteln
;* Losspielen
;* Anzeigen aktualisieren
;* Das ganze solange, bis GetKey einen Loslaßcode liefert
@@loop:
	mov	dl,[QTimeTrack.f]
	mov	ax,[QTimeTrack.time]
	mov	cx,10		;10 Sekunden vorspulen
	mov	dh,ch
	call	Sum
	push	ax dx
	 mov	cx,ax
	 mov	dh,dl
	 mov	al,[CurTnoIdx]
	 call	GetAdrOfIdx
	 mov	si,ax
	 mov	dl,[(TTrackBuf si).len.f]
	 mov	ax,[(TTrackBuf si).len.time]
	 call	Diff		;Rest des Titels nach AH:AL:DL
	pop	bx cx		;Neue Position im Track
	mov	dh,bl
	jnc	@@NoNextTrack	;Überlauf bei Subtraktion!
	mov	al,[CurTnoIdx]
	inc	al
	cmp	[nPlayList],al
	jc	@@r
	mov	[CurTnoIdx],al
	call	GetAdrOfIdx
	mov	si,ax
	mov	dl,[(TTrackBuf si).len.f]
	mov	ax,[(TTrackBuf si).len.time]
	xor	cx,cx
	mov	dh,ch		;Null Sekunden
@@NoNextTrack:
	push	ax dx
	 mov	dl,[(TTrackBuf si).start.f]
	 mov	ax,[(TTrackBuf si).start.time]
	 call	Sum
	pop	bx cx		;Länge
	mov	dh,bl
	call	PlayPiece
	call	PeriodicAction
	;call	WaitTick	;Etwas ausharren
	call	GetKey
	or	ax,ax
	jnz	@@loop		;Der User hält die Taste immer noch gebannt
	mov	[LastScancode],0	;Wiederauslösung verhindern
	jmp	@@r
@@WholeTrack:
	mov	al,[CurTnoIdx]
	inc	al
	cmp	[nPlayList],al
	jnc	@@a
	xor	al,al		;Nach letzten Titel nichts auswählen
@@a:	mov	[CurTnoIdx],al
; mov   [QTimeTrack],0
	call	stop
	call	keyplay
@@r:	BRES	[KeyLights],bit k_Succ
	call	HiliteCurKey
@@e:	ret
endp

proc KeyTimeMode
	BTOG	[KeyLights],bit k_TimeMode	;XOR - Bit "verdrehen"
	CallRet	HiliteCurKey
endp

KeyActions:	;Adressen der Handler-Routinen
	dw	ofs KeyPlay	;0: >
	dw	ofs KeyPause	;1: ║
	dw	ofs KeyStop	;2: ■
	dw	ofs KeyPrev	;3: <<
	dw	ofs KeySucc	;4: >>
	dw	ofs KeyEject	;5: ^
	dw	ofs KeyPlaymode	;6: M
	dw	ofs KeyRandom	;7: R
	dw	ofs KeyTimeMode	;8: T

; Handle pressed key
;

keyEnter:
keySpace:	;tun dasselbe wie...
proc  KeyAction
	BTST	[KeyModus],bit 0
	jne	@@trackkeys	;Kompizierte Behandlung!
	mov	bl,[KeyFocused]
	xor	bh,bh
	shl	bx,1
	jmp	[wo KeyActions+bx]	;Programmverteiler: Fokussierte Taste

@@trackkeys:			; Track key pressed
	cmp	[playmode],0	;"cont": Einzelstück auswählen?
	jne	@@setedit	;nein
	call	ClearPlaylist	;ist zwar simpel...
	mov	al,[TrackFocused]
	mov	ah,3		;Einfüge-Bit gesetzt
	call	AppendRemovePlaylist	;Das eine Element an die leere Liste
	mov	[playmode],3	;"single"
	mov	bl,[TrackFocused]	; Play track
	call	play
	mov	bl,[KeyFocused]
	call	SetKeyFocus	;Fokus springt herunter
	call	showplaylist	;hier: die eine Zahl
	call	showmodus	;"single"
	BSET	[KeyLights],bit k_Play
	mov	bl,k_Play
	CallRet	HilitePKey

@@setedit:	;alle anderen Fälle...
	mov	al,[TrackFocused]	;aktuelle Nummer...
	mov	ah,3		;Einfügen oder Löschen
	push	ax
	call	AppendRemovePlaylist	;speichen oder löschen, je nachdem
	pop	ax
	mov	ah,1		;Fokussiert
	CallRet	marktkeyLux
endp  KeyAction


;************************************************
;** Hauptschleife von CDP und Tastenfunktionen **
;************************************************

KeyDispatch:
	DVT	77,keyCursorRight
	DVT	75,keyCursorLeft
	DVT	72,keyCursorUp
	DVT	80,keyCursorDown
	DVT	57,keySpace
	DVT	28,keyEnter
	DVT	15,keyTab
	DVT	71,keyDragLeft	;HOME-Taste
	DVT	73,keyDragUp	;PgUp-Taste
	DVT	79,keyDragRight	;END-Taste
	DVT	81,keyDragDown	;PgDn-Taste
	DVT	0,keyElse

proc mainl
@@l:	;call	WaitTick	;Etwas warten
	int	28h		;Etwas idlen
MainLoop:
	call	PeriodicAction	;Alles auf dem laufenden halten
ifdef BIOSKEY
@@l0:	KBD	1
	jz	@@f0
	KBD	0		;BIOS-Tastaturpuffer leeren
	jr	@@l0
@@f0:
endif
	xor	al,al
	xchg	[LastScancode],al
	or	al,al
	jz	@@l		;Schleifen-Durchlauf mit etwas Warten
	cmp	al,1		; ESC ?
	je	@@e

;Unterprogramme verteilen!
	LD	es,ds
	lea	di,[KeyDispatch]
	call	case
	call	[wo di]
	jmp	MainLoop
keyElse:
@@e:	ret
endp


keyTab:	;macht dasselbe wie...
proc keyCursorRight
	cmp	[CDPStat],2
	jc	@@e
	BTST	[KeyModus],bit 0
	je	@@pkeys1
	mov	bl,[TrackFocused]
	inc	bl
	call	GetMaxDisplayTrack
	cmp	al,bl		;Zu groß?
	jnc	@@nt1
	mov	bl,1		;Von vorn!
@@nt1:	callret	SetTrackFocus
@@pkeys1:
	mov	bl,[KeyFocused]
	inc	bl
	cmp	bl,9
	jc	@@nn1
	xor	bl,bl		;modulo 9
@@nn1:
	call	SetKeyFocus
@@e:	ret
endp

proc keyCursorLeft
	cmp	[CDPStat],2	;Keine Tasten bei 0 oder 1
	jc	@@e
	BTST	[KeyModus],bit 0
	je	@@pkeys2
	mov	bl,[TrackFocused]
	dec	bl
	jnz	@@nt2
	mov	bl,[tracks.nr]
@@nt2:	callret	SetTrackFocus
@@pkeys2:
	mov	bl,[KeyFocused]
	dec	bl
	cmp	bl,-1
	jnz	@@nn2
	mov	bl,8		;modulo 9
@@nn2:	call	SetKeyFocus
@@e:	ret
endp

proc keyCursorUp
	BTST	[KeyModus],bit 0	;Schon oben?
	jne	@@e		;ja, nichtstun (Anschlag)
;	cmp	[CDPStat],2	;bereit? (kein PLAY)
;	jne	@@e		;nein, keine Tastenfunktion!
	cmp	[playmode],2	;"scan"?
	je	@@e		;nichtstun
	cmp	[canedit],1
	jne	@@e
	cmp	[playmode],1	;"edit"?
	jne	@@nn8		;nein
	call	showeditlist	;ja, Edit-Liste anzeigen
	call	showtrackCount	;und die Anzahl gerade ausgewählter Elemente
	mov	ax,[maxdtime.time]
	stc
	call	showtime
@@nn8:
	mov	bl,[TrackFocused]
	call	SetTrackFocus	;Fokus hoch!
@@e:	ret
endp

proc keyCursorDown
	BTST	[KeyModus],bit 0	;Schon unten?
	je	@@e		;ja, nichtstun (Anschlag)
;	cmp	[CDPStat],2	;bereit?
;	jne	@@e		;nein, nichtstun
	cmp	[playmode],2	;"scan"?
	je	@@e		;nichtstun
	cmp	[canedit],1
	jne	@@e
	mov	bl,[KeyFocused]
	call	SetKeyFocus	;Fokus 'runter!
	cmp	[playmode],1	;"edit"?
	jne	@@e
	call	showplaylist	;Die ausgewählten Elemente nun anzeigen
	call	showmodus	;"edit"
	call	showtrackCount	;die Anzahl (bereit zum PLAY drücken)
	mov	ax,[maxdtime.time]
	stc
	call	showtime
@@e:	ret
endp

proc keyDragLeft	;HOME-Taste
	cmp	[by LOW vid_LeftTop],-3
	jz	@@e		;bei "Extrawurst" Links/Rechts
	cmp	[by LOW vid_LeftTop],0	;Schon links?
	jz	@@e		;nichts tun
	call	HideWindow
	dec	[by LOW vid_LeftTop]
	call	ShowWindow
@@e:	ret
endp

proc keyDragUp		;PgUp-Taste
	cmp	[by HIGH vid_LeftTop],0	;Schon oben?
	jz	@@e		;nichts tun
	call	HideWindow
	dec	[by HIGH vid_LeftTop]
	call	ShowWindow
@@e:	ret
endp

proc keyDragRight	;END-Taste
	cmp	[by LOW vid_LeftTop],-3
	jz	@@e		;bei "Extrawurst" Links/Rechts
	mov	al,[by LOW vid_LeftTop]	;Linke Kante
	add	al,SCR_MAIN_WIDTH	;Breite des Fensters dazu
	cmp	[by LOW vid_WidthHeight],al ;Schon rechts oder rechtsheraus?
	jbe	@@e		;nichts tun
	call	HideWindow
	inc	[by LOW vid_LeftTop]
	call	ShowWindow
@@e:	ret
endp

proc keyDragDown	;PgDn-Taste
	mov	al,[by HIGH vid_LeftTop]	;Obere Kante
	add	al,SCR_MAIN_DEPTH	;Höhe des Fensters dazu
	cmp	[by HIGH vid_WidthHeight],al ;Schon unten oder untenheraus?
	jbe	@@e		;nichts tun
	call	HideWindow
	inc	[by HIGH vid_LeftTop]
	call	ShowWindow
@@e:	ret
endp

;***************************************
;** Periodisch aufgerufene Funktionen **
;***************************************

; Wait for a disk inserted and read it in
;
proc PollDisk
	cmp	[CDPStat],0
	je	@@g		;Da ist keine drin gewesen
	call	checkdisk
	jc	@@f		;Fehler: ausgeben
	call	mediachanged
	jnc	@@e		;Kein sicherer Wechsel
	mov	[CDPStat],0
@@f:
	call  cleardisplay
	PrintXY	21,3,scr_nodisk
	BRES	[KeyLights],bit k_Play + bit k_Pause
	mov	bl,k_Pause	;Lichter löschen
	call	HilitePKey
	mov	bx,k_Play
	call	HilitePKey
	mov	bl,k_Eject
	call	SetKeyFocus
@@g:
InitDisk:	;Ansprung beim Aufpoppen
	call	CheckDisk
	jc	@@e		;Fehler belassen
	call	ReadTrackData	;meldet von selber NoAudioDisk
@@e:	ret
endp

;Q-Channel abfragen (wenn CDPStat=3 (abspielend))
proc TestQ
	cmp	[CDPStat],2
	jc	@@exit		;Zustände 0 und 1: Keine Tests!
	BTST	[KeyLights],bit k_Play
	jz	@@NotPlaying
	BTST	[KeyLights],bit k_Pause
	jnz	@@NotPlaying
	call	GetQChannel
	or	ax,ax		;Fehlercode
	jz	@@endplay
;Entspricht aktueller Track dem erwarteten?
;(QTrack==tracks[nPlaylist[CurTnoIdx]].nr)
@@FindTrack:
	mov	al,[CurTnoIdx]
	call	GetAdrOfIdx
	mov	bx,ax
	mov	al,[(TTrackBuf bx).nr]
	cmp	[QTrack],al	;Gleich
	je	@@IsOK		;ja, niemand pfuschte dazwischen
;Hier noch einbauen:
	call	SetContinuous	;Anzeige umschalten
;Welche Titel-Nummer ist es? Liste absuchen...
	xor	ah,ah
	lea	bx,[tracks]
	mov	al,[QTrack]	;Zu suchende Nummer
	mov	cl,[(TTrackBuf bx).nr]	;Anzahl
	xor	ch,ch
	jcxz	@@notfound	;Anzahl=0
@@l:
	inc	ah		;Track-Index-Nummer
	add	bx,size TTrackBuf
	cmp	[(TTrackBuf bx).nr],al
	je	@@found		;Hurra, gefunden
	loop	@@l		;nicht gefunden, weitersuchen
@@notfound:	;Problem, was nun? Disk einlesen!
	call	ReadInTrackData
	call	FillPlaylist
	mov	ah,0		;Ärger für nächste Runde aufheben
@@found:
	mov	[CurTnoIdx],ah	;Neue Index-Nummer eintragen (nachführen)
@@IsOK:
	BTST	[KeyLights],bit k_TimeMode
	jnz	@@negtime
	mov	ax,[QTimeTrack.time]
	clc
	call	showtime	;Track-Zeit direkt anzeigen
	mov	bl,1		;von 1
	mov	bh,[CurTnoIdx]
	or	bh,bh		;Null?
	jz	@@fromQ		;Q-Channel-Wert ausgeben
	dec	bh		;bis zur aktuellen Spur
	call	CalcTimeFromTo	;Zeiten zusammenrechnen
	mov	dh,[QTimeTrack.f]
	mov	cx,[QTimeTrack.time]
	call	Sum
	jr	@@a1
@@fromQ:
	mov	ax,[QTimeDisk.time]
	jr	@@a1
@@negtime:
	mov	al,[CurTnoIdx]
	call	GetAdrOfIdx	;nach AX, notfalls (AL=0) bzgl. Gesamt-CD
	mov	bx,ax
	mov	dl,[(TTrackBuf bx).len.f]
	mov	ax,[(TTrackBuf bx).len.time]
	mov	dh,[QTimeTrack.f]
	mov	cx,[QTimeTrack.time]
	call	Diff
	push	cx dx		;brauche ich noch
	  clc
	  call	showtime
	 pop	dx		;DH zurück
	 mov	bl,[CurTnoIdx]	;Erster Index
	 mov	bh,[nPlaylist]	;letzter Index
	 call	CalcTimeFromTo	;zusammenzählen, DH bleibt
	pop	cx		;CX zurück
	call	Diff		;Dasselbe für Gesamtrestspielzeit
@@a1:	stc
	call	showtime
@@show:
	mov	al,[QTrack]	;vom Q-Channel
	call	showtracknumber0
	jr	@@exit
@@NotPlaying:
	call	GetQChannel	;Prüfen, ob Laufwerk noch steht
;Eventuelles Problem, wenn MSCDEX gerade eine Datenspur liest...
	or	ax,ax
	jz	@@exit
	BSET	[KeyLights],bit k_Play
	mov	bl,k_Play
	call	HilitePKey	;Es wird gerade gespielt, Knopf leuchte!
	BRES	[KeyLights],bit k_Pause
	mov	bl,k_Pause
	call	HilitePKey	;Es wird gerade gespielt, Pause AUS
	jmp	@@FindTrack
@@endplay:
	cmp	[playmode],3	;"single"?
	je	@@stop		;stehenbleiben
	mov	al,[CurTnoIdx]
	cmp	[nPlaylist],al	;Ende erreicht?
	je	@@stop
	inc	[CurTnoIdx]
	call	keyplay		;testet auch CurTnoIdx
	jnc	@@exit
@@stop:
	call	keystop
	mov	[CDPStat],2
@@exit:	ret
endp

; CDP Timer Int process
CDPTimer:
;	ret	;TEST
proc PeriodicAction
;	ret	;TEST+++TEST
	call	PollDisk	;überprüft, ob alles noch rechtens ist
	jc	@@e		;Fehler - kein TestQ
	call	TestQ		;Q-Kanal prüfen und ggf. nächsten Track spielen
@@e:	ret
endp

proc Popup
	VID	0fh		; Check current video mode
	mov	[vid_CurPage],bh ;angezeigte Seite
	push	es
	 LD	es,40h
	 mov	bl,[es:84h]	;Letzte Zeile (EGA/VGA)
	pop	es
	MAX	bl,24		;Mindestens 24 muß AL sein
	inc	bl
	mov	[by HIGH vid_WidthHeight],bl	;Höhe
	mov	[by LOW vid_WidthHeight],ah	;Breite
	mov	bx,0b800h
	BRES	al,bit 7	;ausmaskieren!
	cmp	al,3		;CO80
	je	@@textmode
	cmp	al,2		;BW80
	je	@@textmode
	cmp	ah,132		;132 Spalten gibt's in keinem Grafikmodus
	je	@@textmode
	mov	bh,0b0h
	cmp	al,7		;MONO
	je	@@textmode
	BSET	[vid_Graph],bit 0 ;Grafikmodus annehmen
	mov	[LineIncr],SCR_MAIN_WIDTH
	SDS	[CdpScr],ofs SCR_MAIN	;Zeiger ins eigene Programm hinein
	cmp	al,2		;40spaltiger Textmodus?
	jnc	@@gt
	BSET	[vid_Graph],bit 2	;Textmodus als Grafik
@@gt:	cmp	al,13h		;256-Farben-Modus?
	jnz	@@g1
	BSET	[vid_Graph],bit 1
	jr	@@g1		;gemeinsam weiter
@@textmode:
	BRES	[vid_Graph],bit 0 ;Textmodus annehmen
	mov	[LineIncr],ah	;Zeichen pro Zeile
	mov	[wo HIGH CdpScr],bx
@@g1:
;Prüfen, ob nicht das Darstellungsfenster zu klein ist
	mov	ax,[vid_LeftTop];Fensterecke
	add	ax,SCR_MAIN_DEPTH*256+SCR_MAIN_WIDTH	;Größe des Fensters
	cmp	[by LOW vid_WidthHeight],al
	jnc	@@fx
	mov	[by LOW vid_LeftTop],0
@@fx:	cmp	[by HIGH vid_WidthHeight],ah
	jnc	@@fy
	mov	[by HIGH vid_LeftTop],0
@@fy:	;Spezialfall weniger als 46 Zeichen pro Zeile
	cmp	[by LOW vid_WidthHeight],SCR_MAIN_WIDTH
	jnc	@@fz
	push	[vid_LeftTop]
	mov	[by LOW vid_LeftTop],-3	;Mittig postieren (bei 40 Zeichen)
@@fz:	;Cursor(en) verschwinden lassen
	BTST	[vid_Graph],bit 4	; Mouse off if installed
	jz	@@nomouse1
	mov	ax,2		;Maus AUS
	int     33h
@@nomouse1:
	call	SwapCursor
	;Fenster anzeigen
	call	ShowWindow	;bei Position vid_LeftTop
	BSET	[KeyMode],bit 6	;Verbot für Tastatur-BIOS
	call	mainloop	;Tastatur-Abfrageschleife
	BRES	[KeyMode],bit 6	;Erlaubnis für Tastatur-BIOS
	call	HideWindow	;Fenster zurücknehmen
	mov	[LineIncr],SCR_MAIN_WIDTH
	SDS	[CdpScr],ofs SCR_MAIN	;Zeiger ins eigene Programm hinein
	BRES	[vid_Graph],0fh	;Kein Grafikmodus, keine Grafik-Flags mehr
	;Cursor(en) wieder erscheinen lassen
	call	SwapCursor
	BTST	[vid_Graph],bit 4	; Mouse on if installed
	jz	@@nomouse2
	mov	ax,1
	int	33h
@@nomouse2:
	cmp	[by LOW vid_LeftTop],-3	;Mittig postiert?
	jnz	@@v1
	pop	ax			;alte Position
	mov	[by LOW vid_LeftTop],al	;an ehemalige X-Stelle zurück
@@v1:
	ret
endp  Popup

;==============================================================================
; TSR IRON CURTAIN - HE WHO CROSSES THIS CURTAIN WILL BE GONE AFTER TSR!
;==============================================================================

ResEnd:
TsrCurtain:					;TSR memory break
DATASEG
; Transient code *****************************************************

Mscdex$		db 'MSCDEX $'
drive$		db 'drive $'
found$		db ': found, $'
msg_installed	db '_',8,'■ installed, $'
HotkeyIs$	db 'Hotkey is $'
Shift$		db 'Shift+$'		;hier: links ODER rechts - egal!
Ctrl$		db 'Ctrl+$'
Alt$		db 'Alt+$'
PointNL$	db '".',nl,'$'
NoMscdex$	db "seems to be not loaded. ",7,"Try /D switch",nl,'$'
WrongVersion$	db 'version 2.10+ required ',7,'(or use /D workaround)',nl,'$'
NoAudio$	db 'CD drive is not capable of playing audio',7,nl,'$'
NoDrive$	db "CD drive letter given doesn't exist",7,nl,'$'
NoDriver$	db "CDROM low-level driver given doesn't exist ",7,"(error opening)",nl,'$'
DriverErr$	db "Low-level driver given exist, but won't work properly",7,nl,'$'
NoChange$	db "It's not possible to change CD drive ",7,"while CDP is resident",nl,'$'
CmdLineError$	db "Command line error ",7,"(unknown option or syntax error, try /?)",nl,'$'
No286$		db "This program doesn't run on 8086/88 (PC/XT) machines",7,nl,'$'
NoFreeMux$	db "No free MUX available",7,nl,'$'
msg_alreadyinst  db 'CDP already resident. $'
msg_status1      db 'Last status: $'
msg_status2      db 'no disk inserted.',nl,'$'
msg_status3      db 'no audio disk.',nl,'$'
msg_status4      db 'ready to play.',nl,'$'
msg_status5      db 'playing.',nl,'$'
msg_status6      db 'pausing.',nl,'$'
msg_notinstalled db 'CDP is not ',7,'resident',nl,'$'
msg_cannotuninst db "Can not uninstall ",7,"the resident CDP. Remove other TSR first",nl,'$'
msg_uninstalled  db 'uninstalled and removed from memory.',nl,'$'
msg_copyright	db 'C',8,'CD',8,'DP',8,'P 1.1 (h#s): $'
;Mit LESS (NROFF)-Sequenzen für den Einsatz mit geladenem ANSI.COM
msg_help1	db "haftmann#software CD player - freeware",nl
 db    "CDP - a resident popup music CD player - even for graphics mode.",nl
 db    "Default action is to load the player or to show a status if loaded.",nl
 db    'Command line options: (the switch char can be "-", "/" or omitted,',nl
 db    'a delimiter ":","=" or omitted).',nl
 db nl
 db    "  /H or /?		-> show this help on usage",nl
 db    "  /U			-> tries to uninstall the resident CDP",nl
 db    "  /K:{CSA}key		-> changes the hotkey for popup",nl
 db    "  /D:drivername		-> use CDROM driver instead of MSCDEX",nl
 db    "  /L:drive{:}		-> use CDROM on specific drive letter",nl
;db    "  /V			-> verbose info",nl
;db    "  /S			-> status info only",nl
 db nl
 db    "CDP is based on XCD v1.0ß by Henrik Drewelow, 1994.",nl
 db    '$'

; variables ----------------------------------------------------------

resseg           dw ?                   ; Segment of the resident CDP
command          db 0                   ; Commands in command line
					;  1 -> Uninstall CDP
					;  2 -> Help on Usage
					;  4 -> Hotkey selection
					;  8 -> Ungültige Option
					; 16 -> Treiber gewählt
					; 32 -> MSCDEX gewählt
tsrstat		db	0	;Statusbits von InstChk

PDriverName	dw	0	;zeigt in die Kommandozeile
CODESEG
; library procedures -------------------------------------------------

include "asc2scan.asm"

_SCANTAB
_SCAN2ASC
_ASC2SCAN
_UPCASE
_INSTCHK

; misc. procedures ---------------------------------------------------


; Sets up data for the IOCTRL requests
;

proc setupIOCTRL

	SCS	[IOCTRL.bufptr],offset IOBuf
	mov	[IoCtrl.buflen],size IOBuf
	ret

endp setupIOCTRL


proc DetectMscdex
;CY=1: Kein MSCDEX
	xor	bx,bx
	MUX	1500h
	cmp	bx,1
	ret
endp

proc CheckVersion
;CY=1: Version zu klein
	xor	bx,bx
	MUX	150Ch
	cmp	bx,20ah
	ret
endp

proc GetSysEntry
;CY=1: Fehler (kein Laufwerk auf Buchstabe)
	mov	cx,[CDDrive]
	push	es
	 ld	es,cs
	 mov	bx,offset IoCtrl
	 mov	[(TIoCtrl bx).hdrlen],1Ah	;Länge
	 mov	[(TIoCtrl bx).command],3	;IOCTL Read
	 mov	[IOBuf.request],0	;Adresse Gerätekopf
	 mov	ax,1510h
	 int	2fh			; Pass to mscdex
	 jc	@@e
	 les	bx,[dword IoBuf.data]	;Adresse
	 mov	ax,[(TDevHdr es:bx).pStrat]
	 SES	[Strat],ax
	 mov	ax,[(TDevHdr es:bx).pIntr]
	 SES	[Inter],ax
@@e:	pop	es
	ret
endp


; Checks the Drive capabilities
;PA: CY=1 wenn Laufwerk meint, kein Audio zu unterstützen
proc  checkdrive
	call	getdevicestatus	; Get drive status
	BTST	cx,bit 4	;Dieses Bit muß gesetzt sein!
	jnz	@@e		;okay (CY ist 0)
	stc
@@e:	ret
endp  checkdrive


;textuelle Ausgabe des ausgewählten Hotkeys
;(z.B. 'Hotkey is Ctrl+Alt+"K".'
proc WriteHotKey
	print	HotkeyIs$
	mov	cl,[es:hotkeyshift]
	test	cl,bit 1
	jz	@@n1
	print	Shift$
@@n1:	test	cl,bit 2
	jz	@@n3
	print	Ctrl$
@@n3:	test	cl,bit 3
	jz	@@n4
	print	Alt$
@@n4:	mov	al,'"'		;wegen 1 Zeichen nicht gleich PRINT!
	call	ochr
	mov	al,[es:hotkey]
	call	scan2asc
	call	ochr
	print	PointNL$
	ret
endp  WriteHotKey

; Writes the last Status
;
proc  WriteStatus
	print	msg_status1
	cmp	[es:CDPStat],0
	lea	dx,[msg_status2]
	je	@@we
	cmp	[es:CDPStat],1
	lea	dx,[msg_status3]
	je	@@we
	BTST	[es:KeyLights],bit k_Pause
	lea	dx,[msg_status6]
	jnz	@@we
	BTST	[es:KeyLights],bit k_Play
	lea	dx,[msg_status5]
	jnz	@@we
	lea	dx,[msg_status4]
@@we:	DOS	9
	ret
endp  WriteStatus

;Übergehe Doppelpunkt (colon) oder Gleichheitszeichen (equal sign)
proc OverCE
	lodsb
	cmp	al,':'	;optionaler Doppelpunkt?
	jz	@@over
	cmp	al,'='	;oder Gleichheitszeichen?
	jz	@@over
	dec	si	;Hier beginnen!
@@over:	ret
endp

upvt:	;Unterprogrammverteilertabelle für Kommandozeilenoptionen
	DVT	'U',cmdUninstall
	DVT	'?',cmdHelp
	DVT	'H',cmdHelp
	DVT	'K',cmdHotKey
	DVT	'L',cmdMSCDEX
	DVT	'D',cmdDriver
	DVT	0,  cmdError	;Else-Zweig: Ganz einfach: Fehler


; Parse the command line
;ES zeigt auf residente Kopie

proc  ParseCommandLine
	mov	si,81h                          ; read cmdline len
@@read:
	lodsb		; read char
@@next:
	cmp	al,0dh
	je	@@exit
	cmp	al,' '
	jbe	@@read	;nächstes Zeichen bei Weißraum
	cmp	al,'-'
	je	@@read	;Optionszeichen übergehen (alles ist mit Option!)
	cmp	al,'/'
	je	@@read

@@option:
	call	upcase
	push	es
	 LD	es,ds
	 lea	di,[upvt]	;Unterprogrammverteilertabelle
	 call	case
	pop	es
	jmp	[wo di]
@@exit:
	ret

cmdUninstall:
	or	[command],1	; Uninstall switch
	jr	@@read

cmdHelp:
	or	[command],2	; Help switch
	jr	@@read

cmdHotKey:
	or	[command],4	; Hotkey selection
	xor	bx,bx		;keine Shift-Tasten-Modifikation annehmen
	call	OverCE
@@kl:	lodsb
	cmp	[by si],21h	;Folgt Weißraum?
	jc	@@k1		;ja, Tasten-Bezeichner (keine Shifttasten)
	call	Upcase		;nein - Shift-Bezeichner in AL
	cmp	al,'S'
	mov	ah,bit 1	;Shift-Bit
	jz	@@ks
	cmp	al,'C'
	mov	ah,bit 2	;Control-Bit
	jz	@@ks
	cmp	al,'A'
	mov	ah,bit 3	;Alt-Bit
	jz	@@ks
@@ke:	jmp	CmdError	;Fehler (vielleicht später Extra-Meldung)
@@ks:
	BTST	bh,ah		;Bit bereits gesetzt?
	jnz	@@ke		;Doppelt angeben gibt's nicht!
	or	bh,ah		;Shift-Tasten-Bitmaske: Bit setzen
	inc	bl		;Shift-Tasten-Zähler: erhöhen
	jr	@@kl
@@k1:
	cmp	bl,1		;Nur eine Shift-Taste angegeben?
	je	@@ke		;Fehler!
	call	asc2scan	;muß Zahl oder Buchstabe sein
	jc	@@ke		;Fehler
	mov	[es:HotKey],al	;Taste auf jeden Fall ändern
	or	bl,bl		;Shift-Tasten angegeben?
	jz	@@read		;nein
	mov	[es:HotkeyShift],bh	;ja, auch ändern
	jr	@@read

cmdMSCDEX:
	BSET	[command],bit 5
	call	OverCE
	lodsb
	call	anum		;in Ziffer wandeln
	cmp	al,0ah		;war Ziffer?
	jc	@@grr		;Laufwerksbuchstabe muß ermittelt werden!
	sub	al,0ah
	cmp	al,26
	jnc	cmdError	;Kommandozeilen-Fehler
	cbw
	mov	[CDDrive],ax
	lodsb
	cmp	al,':'		;Laufwerks-Doppelpunkt?
	jnz	@@next		;nein, Zeichen anderweitig interpretieren!
@@grr:	jmp	@@read

cmdDriver:
	BSET	[command],bit 4
	call	OverCE
	mov	[PDriverName],si	;merken
@@l:	lodsb
	cmp	al,' '	;Weißraum?
	ja	@@l
	mov	[by si-1],0	;nullterminieren!
	jmp	@@next	;!! Nicht noch einmal lesen (könnte 0Dh sein) !!

cmdError:
	or	[command],8	;Ungültige Option!
	jmp	@@read

endp  ParseCommandLine


; program entry point ************************************************


proc Main

	print	msg_copyright	;Display Copyright

	IS286
	lea	dx,[No286$]
	jc	MsgExit
	call	InstChk
	mov     [resseg],es	;save resident segment
	mov	[tsrstat],ch


	call    parsecommandline        ;Check program parameter

	test	[command],bit 1		;Help request
	lea	dx,[msg_help1]
	jnz	MsgExit

	test	[command],bit 3		;Kommandozeilen-Fehler
	lea	dx,[CmdLineError$]
	jnz	MsgExit

	BTST	[tsrstat],bit 7
	jnz	@@notinstalled

; ============	TSR ALREADY INSTALLED, CHECK OPTIONS

	test	[command],bit 4 + bit 5	;Treiber oder Laufwerk angegeben?
	lea	dx,[NoChange$]
	jnz	MsgExit			;ist auch ein Fehler (z.Z.)

	test    [command],1             ;uninstall request
	jnz     @@uninstall

; ------------  SHOW HOTKEY AND STATUS, EXIT CDP

	print	msg_alreadyinst         ;display alr installed msg
	ld      es,[resseg]
	call    writehotkey             ;shot the hotkey
	call    writestatus             ;show last status
OKExit:	DOS	4c00h		;error level in AL



; ------------  UNINSTALL RESIDENT CDP, CHECK VECTORS FOR UNINSTALLING

@@uninstall:
	BTST	[tsrstat],0fh	;Anzahl verbogener Vektoren
	jnz	@@cannot
	mov	ah,[MUXI]
	xor	al,al
	MUX			;SI besorgen
	call	Unhook
	PRINT	msg_uninstalled
	jmp	OKExit
@@cannot:
	lea	dx,[msg_cannotuninst]
	jmp	MsgExit

@@notinstalled:
	BTST	[command],1	;uninstall request
	lea	dx,[msg_notinstalled]
	jnz	MsgExit

	call	SetupIOCTRL	;erforderliche Datenstrukturen initialisieren
	test	[command],bit 4	;mit Gerätetreiber verlangt?
	jnz	ConnectToSysDrv
	PRINT	mscdex$
	call	detectmscdex	; Check for MSCDEX
	lea	dx,[NoMscdex$]
	jc	@@MX
	BTST	[command],bit 5	;Laufwerksvorgabe auf Kommandozeile?
	jnz	@@a1		;ja, [CDDrive] belassen
	mov	[cddrive],cx	;1. Laufwerk einschreiben
@@a1:	call	CheckVersion
	lea	dx,[WrongVersion$]
	jc	@@MX
	call	GetSysEntry
	lea	dx,[NoDrive$]
	jc	@@MX
	PRINT	Drive$
	mov	al,[by CDDrive]
	add	al,'A'		; Display drive letter
	call	OCHR
	PRINT	Found$
ContDrv:	;hier weiter im Falle eines Gerätetreibers
	call	CheckDrive	; Check drive capabilities
	lea	dx,[NoAudio$]
@@MX:	jc	MsgExit
	xor	ax,ax
	int	1ah	;BIOS-Uhrzeit nach CX:DX
	mov	[wo LOW RandSeed],dx
	mov	[wo HIGH RandSeed],cx
	DOS	3533h		;Interruptvektor Maus besorgen
	mov	ax,es		;liefert NIL-Pointer?
	or	bx,ax
	jz	@@nomouse	;dann keine Maus
	xor	ax,ax                           ; Check for mouse
	int	33h
	or	ax,ax
	jz	@@nomouse
	BSET	[vid_Graph],bit 4
@@nomouse:
	call	FindFreeMux
	lea	dx,[NoFreeMux$]
	jc	MsgExit
	push	ds es
	 DOS	3400h
	 SES	[InDosPtr],bx
	 DOS	5d06h
	 SDS	[cs:CritErrPtr],si
	pop	es ds
	mov	[word HIGH CdpScr],ds	;Segment einsetzen
	call	HookAll
	print   msg_installed
	LD	es,ds
	call    writehotkey
	BSET	[TSRMode],BPActive
	call	cdptimer
	BRES	[TSRMode],BPActive
	mov	es,[2ch]		;ES=our environment copy
	DOS	49h			;to let DOS free block
	mov	dx,ParaRes	;Gleichung residente Paragrafen
	DOS	3100h		;TSR, AL=err level

ConnectToSysDrv:
	mov	dx,[PDriverName]
	DOS	3D00h		;Gerät zum Lesen öffnen
	lea	dx,[NoDriver$]
	jc	MsgExit		;Fehler!
	xchg	bx,ax		;Handle
	lea	dx,[IOBuf]	;Puffer für IOCTL
	mov	[IOBuf.request],0	;Funktion 0: Gerätetreiberkopfadr.
	mov	cx,5
	DOS	4402h		;IOCTL Read
	jc	@@nodriver1	;Noch ein Fehler
	push	es bx
	 les	bx,[dword IoBuf.data]	;Adresse
	 mov	ax,[(TDevHdr es:bx).pStrat]
	 SES	[Strat],ax
	 mov	ax,[(TDevHdr es:bx).pIntr]
	 SES	[Inter],ax
	pop	bx es
	DOS	3eh		;Gerät schließen
	jmp	ContDrv

; ------------ MESSAGES FOR NOT INSTALLING RESIDENT

@@nodriver1:
	DOS	3eh		;Datei schließen
	lea	dx,[DriverErr$]
MsgExit:
	DOS	9
exit:	DOS	4c01h

endp Main
	endc
Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded