2000 TITLE RAWPLAY.ASM ;============================================ ;Plays raw sample files through SoundBlaster ;============================================ INCLUDE KEYB.MAC INCLUDE CONV.MAC INCLUDE SCREEN.MAC ;----------------------------------------------------------------------- CSEG SEGMENT 'CODE' ASSUME CS:CSEG,DS:CSEG ORG 100h start: jmp begin ;======================================================================= io_addx DW 220H intr_num DB 7 voice_status DW 0 ORG_INT_ADDX LABEL DWORD ORG_INT2_ADDX LABEL DWORD ORG_INT2_OFF DW ? ORG_INT2_SEG DW ? ORG_INT3_ADDX LABEL DWORD ORG_INT3_OFF DW ? ORG_INT3_SEG DW ? ORG_INT5_ADDX LABEL DWORD ORG_INT5_OFF DW ? ORG_INT5_SEG DW ? ORG_INT7_ADDX LABEL DWORD ORG_INT7_OFF DW ? ORG_INT7_SEG DW ? ;--------------------- ; DMA DATA | ;--------------------- DMA_CURRENT_PAGE DB ? DMA_CURRENT_ADDX DW ? DMA_CURRENT_COUNT DW ? PAGE_TO_DMA DB ? LEN_L_TO_DMA DW ? LEN_H_TO_DMA DW ? LAST_DMA_OFFSET DW ? ; SUBTTL DSP module WAIT_TIME EQU 0200H DMA_VOICE_IN EQU 45H DMA_VOICE_OUT EQU 49H DSP_ID_CMD EQU 0E0H DSP_VER_CMD EQU 0E1H DSP_VI8_CMD EQU 24H DSP_VO8_CMD EQU 14H DSP_VO2_CMD EQU 17H DSP_VO4_CMD EQU 75H DSP_VO25_CMD EQU 77H DSP_MDAC1_CMD EQU 61H DSP_MDAC2_CMD EQU 62H DSP_MDAC3_CMD EQU 63H DSP_MDAC4_CMD EQU 64H DSP_MDAC5_CMD EQU 65H DSP_MDAC6_CMD EQU 66H DSP_MDAC7_CMD EQU 67H DSP_TIME_CMD EQU 40H DSP_SILENCE_CMD EQU 80H DSP_PAUSE_DMA_CMD EQU 0D0H DSP_ONSPK_CMD EQU 0D1H DSP_OFFSPK_CMD EQU 0D3H DSP_CONT_DMA_CMD EQU 0D4H DSP_INTRQ_CMD EQU 0F2H CMS_TEST_CODE EQU 0C6H RESET_TEST_CODE EQU 0AAH CMS_EXIST EQU 1 FM_MUSIC_EXIST EQU 2 CTV_VOICE_EXIST EQU 4 FM_WAIT_TIME EQU 40H stackspace db 32 dup ('STACK ') newstack dw 0 WRITE_DSP_TIME PROC NEAR PUSH CX MOV CX,WAIT_TIME MOV AH,AL WDT10: IN AL,DX OR AL,AL JNS WDT20 LOOP WDT10 STC JMP SHORT WDT90 WDT20: MOV AL,AH OUT DX,AL CLC WDT90: POP CX RET WRITE_DSP_TIME ENDP READ_DSP_TIME PROC NEAR PUSH CX PUSH DX MOV DX,io_addx ADD DL,0EH MOV CX,WAIT_TIME RDT10: IN AL,DX OR AL,AL JS RDT20 LOOP RDT10 STC JMP SHORT RDT90 RDT20: SUB DL,4 IN AL,DX CLC RDT90: POP DX POP CX RET READ_DSP_TIME ENDP WRITE_DSP PROC NEAR MOV AH,AL MOV AL,0F0H WD10: IN AL,DX OR AL,AL JS WD10 MOV AL,AH OUT DX,AL RET WRITE_DSP ENDP READ_DSP PROC NEAR PUSH DX MOV DX,io_addx ADD DL,0EH SUB AL,AL RD10: IN AL,DX OR AL,AL JNS RD10 SUB DL,4 IN AL,DX POP DX RET READ_DSP ENDP RESET_DSP PROC NEAR MOV DX,io_addx ADD DL,6 MOV AL,1 OUT DX,AL IN AL,DX RDSP05: INC AL JNZ RDSP05 OUT DX,AL MOV CL,20H RDSP10: CALL READ_DSP_TIME CMP AL,0AAH JE RDSP20 DEC CL JNZ RDSP10 MOV AX,2 JMP SHORT RDSP90 RDSP20: SUB AX,AX RDSP90: OR AX,AX RET RESET_DSP ENDP VERIFY_IO_CHK PROC NEAR MOV BX,2 MOV AL,DSP_ID_CMD MOV DX,io_addx ADD DX,0CH CALL WRITE_DSP_TIME JC VIO90 MOV AL,0AAH CALL WRITE_DSP_TIME JC VIO90 CALL READ_DSP_TIME JC VIO90 CMP AL,055H JNE VIO90 SUB BX,BX VIO90: MOV AX,BX OR AX,AX RET VERIFY_IO_CHK ENDP VERIFY_INTR PROC NEAR MOV AL,2 MOV DX,OFFSET DUMMY_DMA_INT2 MOV BX,OFFSET ORG_INT2_ADDX CALL SETUP_INTERRUPT MOV AL,3 MOV DX,OFFSET DUMMY_DMA_INT3 MOV BX,OFFSET ORG_INT3_ADDX CALL SETUP_INTERRUPT MOV AL,5 MOV DX,OFFSET DUMMY_DMA_INT5 MOV BX,OFFSET ORG_INT5_ADDX CALL SETUP_INTERRUPT MOV AL,7 MOV DX,OFFSET DUMMY_DMA_INT7 MOV BX,OFFSET ORG_INT7_ADDX CALL SETUP_INTERRUPT MOV intr_num,0 MOV DX,io_addx ADD DX,0CH MOV AL,DSP_INTRQ_CMD CALL WRITE_DSP SUB AX,AX MOV CX,WAIT_TIME*4 VI10: CMP intr_num,0 JNZ VI90 LOOP VI10 MOV AX,3 VI90: PUSH AX MOV AL,2 MOV BX,OFFSET ORG_INT2_ADDX CALL RESTORE_INTERRUPT MOV AL,3 MOV BX,OFFSET ORG_INT3_ADDX CALL RESTORE_INTERRUPT MOV AL,5 MOV BX,OFFSET ORG_INT5_ADDX CALL RESTORE_INTERRUPT MOV AL,7 MOV BX,OFFSET ORG_INT7_ADDX CALL RESTORE_INTERRUPT POP AX OR AX,AX RET VERIFY_INTR ENDP CHK_DSP_VERSION PROC NEAR MOV AL,DSP_VER_CMD MOV DX,io_addx ADD DL,0CH CALL WRITE_DSP CALL READ_DSP MOV AH,AL CALL READ_DSP MOV BX,1 CMP AX,101H JB CDV90 SUB BX,BX CDV90: MOV AX,BX OR AX,AX RET CHK_DSP_VERSION ENDP PAUSE_DSP_DMA PROC NEAR PUSHF MOV AH,DSP_PAUSE_DMA_CMD MOV BX,OFFSET voice_status SUB CX,CX MOV DX,io_addx ADD DL,0CH PDD10: STI CMP CX,[BX] JE PDD90 CLI IN AL,DX OR AL,AL JNS PDD10 PDD20: IN AL,DX OR AL,AL JS PDD20 MOV AL,AH OUT DX,AL PDD90: POPF RET PAUSE_DSP_DMA ENDP ;-------------------------------------------- ; entry: DH = dma mode : ; DL = page : ; AX = current addx : ; CX = current count : ;-------------------------------------------- DMA_ADDX_REG EQU 02H DMA_COUNT_REG EQU 03H DMA_MASK_REG EQU 0AH DMA_MODE_REG EQU 0BH DMA_FF_REG EQU 0CH DMA_PAGE_REG EQU 83H PROG_DMA PROC NEAR PUSH BX MOV BX,AX MOV AL,5 OUT DMA_MASK_REG,AL SUB AL,AL OUT DMA_FF_REG,AL MOV AL,DH OUT DMA_MODE_REG,AL MOV AL,BL OUT DMA_ADDX_REG,AL MOV AL,BH OUT DMA_ADDX_REG,AL MOV AL,CL OUT DMA_COUNT_REG,AL MOV AL,CH OUT DMA_COUNT_REG,AL MOV AL,DL OUT DMA_PAGE_REG,AL MOV AL,1 OUT DMA_MASK_REG,AL POP BX RET PROG_DMA ENDP CALC_20BIT_ADDX PROC NEA 2000 R PUSH CX MOV CL,4 ROL DX,CL MOV CX,DX AND DX,0FH AND CX,0FFF0H ADD AX,CX ADC DX,0 POP CX RET CALC_20BIT_ADDX ENDP ;------------------------------------------------- ; entry: AL = INTERRUPT NUM | ; DX = new vector ofs, seg is alway CS | ; BX = offset of store buffer : ;------------------------------------------------- SETUP_INTERRUPT PROC PUSH BX PUSH CX PUSH DX CLI MOV CL,AL ; preserve interrupt number for use ADD AL,8 ; calculate interrupt vector addx CBW SHL AL,1 SHL AL,1 MOV DI,AX PUSH ES ; setup and preserve interrupt SUB AX,AX MOV ES,AX MOV AX,ES:[DI] MOV [BX],AX MOV ES:[DI],DX MOV AX,ES:[DI+2] MOV [BX+2],AX MOV ES:[DI+2],CS POP ES MOV AH,1 ; enable interrupt control mask-bit SHL AH,CL NOT AH IN AL,21H AND AL,AH OUT 21H,AL STI POP DX POP CX POP BX RET SETUP_INTERRUPT ENDP ;------------------------------------------------- ; entry: AL = INTERRUPT NUM | ; BX = offset to stored addx | ;------------------------------------------------- RESTORE_INTERRUPT PROC CLI MOV CL,AL ADD AL,8 ; calculate interrupt vector addx CBW SHL AL,1 SHL AL,1 MOV DI,AX PUSH ES ; restore interrupt vector SUB AX,AX MOV ES,AX MOV AX,[BX] MOV ES:[DI],AX MOV AX,[BX+2] MOV ES:[DI+2],AX POP ES MOV AH,1 SHL AH,CL IN AL,21H OR AL,AH OUT 21H,AL STI RET RESTORE_INTERRUPT ENDP DUMMY_DMA_INT2 PROC FAR PUSH DX MOV DL,2 JMP DUMMY_DMA_ISR DUMMY_DMA_INT2 ENDP DUMMY_DMA_INT3 PROC FAR PUSH DX MOV DL,3 JMP DUMMY_DMA_ISR DUMMY_DMA_INT3 ENDP DUMMY_DMA_INT5 PROC FAR PUSH DX MOV DL,5 JMP DUMMY_DMA_ISR DUMMY_DMA_INT5 ENDP DUMMY_DMA_INT7 PROC FAR PUSH DX MOV DL,7 DUMMY_DMA_ISR: PUSH DS PUSH AX MOV AX,CS MOV DS,AX MOV intr_num,DL MOV DX,io_addx ADD DX,0EH IN AL,DX MOV AL,20H OUT 20H,AL POP AX POP DS POP DX IRET DUMMY_DMA_INT7 ENDP DMA_OUT_INTR PROC PUSH DS PUSH ES PUSH AX PUSH BX PUSH CX PUSH DX PUSH DI PUSH SI PUSH BP CLD MOV AX,CS MOV DS,AX MOV ES,AX MOV DX,io_addx ADD DL,0EH IN AL,DX MOV AX,LEN_L_TO_DMA OR AX,AX JNZ VO_INT10 CALL END_DMA_TRANSFER JMP SHORT VO_INT90 VO_INT10: CALL DMA_OUT_TRANSFER VO_INT90: MOV AL,20H OUT 20H,AL POP BP POP SI POP DI POP DX POP CX POP BX POP AX POP ES POP DS IRET DMA_OUT_INTR ENDP DMA_OUT_TRANSFER PROC NEAR MOV CX,0FFFFH ; get current page end address CMP PAGE_TO_DMA,0 ; last page to dma ? JNZ DOT10 ; no, skip INC PAGE_TO_DMA MOV CX,LAST_DMA_OFFSET ; get end addx DOT10: SUB CX,DMA_CURRENT_ADDX ; calcutate current page addx MOV DMA_CURRENT_COUNT,CX INC CX JZ DOT20 SUB LEN_L_TO_DMA,CX SBB LEN_H_TO_DMA,0 JMP SHORT DOT30 DOT20: DEC LEN_H_TO_DMA DOT30: MOV DH,DMA_VOICE_OUT MOV DL,DMA_CURRENT_PAGE MOV AX,DMA_CURRENT_ADDX MOV CX,DMA_CURRENT_COUNT CALL PROG_DMA DEC PAGE_TO_DMA INC DMA_CURRENT_PAGE MOV DMA_CURRENT_ADDX,0 MOV CX,DMA_CURRENT_COUNT MOV DX,io_addx ADD DL,0CH MOV AL,DSP_VO8_CMD CALL WRITE_DSP MOV AL,CL CALL WRITE_DSP MOV AL,CH CALL WRITE_DSP DOT90: RET DMA_OUT_TRANSFER ENDP END_DMA_TRANSFER PROC NEAR MOV AL,5 OUT DMA_MASK_REG,AL MOV AL,intr_num MOV BX,OFFSET ORG_INT_ADDX CALL RESTORE_INTERRUPT MOV voice_status,0 MOV DX,io_addx ADD DL,0EH IN AL,DX RET END_DMA_TRANSFER ENDP ;--------------------------------------- ; WAIT_FM_STATUS : ; entry: AL = status to wait : ; 3 msb is concern : ; exit : AX destroy : ; carry set for fail : ; carry clr for pass : ;--------------------------------------- WAIT_FM_STATUS PROC NEAR PUSH CX PUSH DX MOV CX,FM_WAIT_TIME MOV AH,AL AND AH,0E0H ; only 3 msb are FM status MOV DX,io_addx ADD DL,8 WFS10: IN AL,DX AND AL,0E0H CMP AH,AL JE WFS20 LOOP WFS10 STC JMP SHORT WFS90 WFS20: CLC WFS90: POP DX POP CX RET WAIT_FM_STATUS ENDP ;--------------------------------------- ; WRITE_FM : ; entry: AH = data value : ; AL = addx value : ; exit : AX destroy : ; DX destroy : ;--------------------------------------- WRITE_FM PROC NEAR MOV DX,io_addx ADD DL,8 OUT DX,AL CALL FM_DELAY MOV AL,AH INC DX OUT DX,AL CALL FM_DELAY RET WRITE_FM ENDP FM_DELAY PROC PUSH AX PUSH DX MOV DX,io_addx ADD DL,8 IN AL,DX IN AL,DX IN AL,DX IN AL,DX IN AL,DX POP DX POP AX RET FM_DELAY ENDP ctv_detect PROC PUSH DS PUSH ES PUSH DI PUSH SI MOV AX,CS MOV DS,AX MOV ES,AX CALL RESET_DSP JNZ ID90 CALL VERIFY_IO_CHK JNZ ID90 CALL CHK_DSP_VERSION JNZ ID90 CALL VERIFY_INTR JNZ ID90 MOV AL,1 ; on speaker CALL ON_OFF_SPEAKER SUB AX,AX ID90: POP SI POP DI POP ES POP DS RET ctv_detect ENDP SPK_STK STRUC DW ? DW ? SPK_FLAG DW ? SPK_STK ENDS ctv_speaker PROC NEAR PUSH BP MOV BP,SP PUSH DS MOV AX,CS MOV DS,AX MOV AX,[BP+SPK_FLAG] CALL ON_OFF_SPEAKER POP DS POP BP RET ctv_speaker ENDP ON_OFF_SPEAKER PROC NEAR MOV DX,io_addx ADD DX,0CH MOV AH,DSP_ONSPK_CMD OR AL,AL JNZ O 2000 OS10 MOV AH,DSP_OFFSPK_CMD OOS10: MOV AL,AH CALL WRITE_DSP SUB AX,AX ; inidcate no error RET ON_OFF_SPEAKER ENDP BUF_OFF DW ? BUF_SEG DW ? COUNT DW ? SAMPLING DW ? ctv_output PROC NEAR PUSH DS PUSH ES PUSH DI PUSH SI MOV AX,CS MOV DS,AX MOV ES,AX CMP voice_status,0 JZ OV10 MOV AX,1 JMP SHORT OV90 OV10: MOV voice_status,1 MOV DX,io_addx ADD DL,0CH MOV DX,0FH ; calculate sampling rate value for MOV AX,4240H ; DSP MOV CX,SAMPLING DIV CX MOV CL,AL NEG CL MOV DX,io_addx ADD DL,0CH MOV AL,DSP_TIME_CMD CALL WRITE_DSP MOV AL,CL CALL WRITE_DSP MOV AL,intr_num MOV DX,OFFSET DMA_OUT_INTR MOV BX,OFFSET ORG_INT_ADDX CALL SETUP_INTERRUPT MOV DX,BUF_SEG ;Buffer segment MOV AX,BUF_OFF CALL CALC_20BIT_ADDX MOV DMA_CURRENT_PAGE,DL MOV DMA_CURRENT_ADDX,AX MOV CX,COUNT MOV LEN_L_TO_DMA,CX MOV LEN_H_TO_DMA,0 ADD AX,COUNT ADC DL,0 SUB AX,1 SBB DL,0 MOV LAST_DMA_OFFSET,AX SUB DL,DMA_CURRENT_PAGE MOV PAGE_TO_DMA,DL CALL DMA_OUT_TRANSFER SUB AX,AX OV90: POP SI POP DI POP ES POP DS RET ctv_output ENDP ctv_halt PROC NEAR PUSH DS PUSH ES PUSH DI PUSH SI MOV AX,CS MOV DS,AX MOV ES,AX MOV AX,1 CMP voice_status,0 JZ TVP90 CALL PAUSE_DSP_DMA CALL END_DMA_TRANSFER SUB AX,AX TVP90: POP SI POP DI POP ES POP DS RET ctv_halt ENDP ctv_pause PROC PUSH DS PUSH ES PUSH DI PUSH SI MOV AX,CS MOV DS,AX MOV ES,AX MOV AX,1 CMP voice_status,1 JNE PV90 CALL PAUSE_DSP_DMA SUB AX,AX PV90: POP SI POP DI POP ES POP DS RET ctv_pause ENDP ctv_continue PROC PUSH DS PUSH ES PUSH DI PUSH SI MOV AX,CS MOV DS,AX MOV ES,AX MOV AX,1 CMP voice_status,1 JNE CV90 MOV DX,io_addx ADD DL,0CH MOV AL,DSP_CONT_DMA_CMD CALL WRITE_DSP SUB AX,AX CV90: POP SI POP DI POP ES POP DS RET ctv_continue ENDP ctv_uninstall PROC PUSH DS PUSH ES PUSH DI PUSH SI MOV AX,CS MOV DS,AX MOV ES,AX CMP voice_status,0 JZ UI90 CALL PAUSE_DSP_DMA CALL END_DMA_TRANSFER UI90: SUB AL,AL CALL ON_OFF_SPEAKER SUB AX,AX POP SI POP DI POP ES POP DS RET ctv_uninstall ENDP ;----------------------------------------------------------- ; usage: : ; unint ctv_card_here() : ; description: : ; detect the Game Blaster or Sound Blaster Card and : ; the configuration : ; entry: : ; the global i/o addx, ct_io_addx must set to a default : ; value, 2x0H, before call. : ; exit: : ; High Byte = 0 : ; Low Byte format - : ; 0000 0001 : C/MS music exists : ; 0000 0010 : FM music exists : ; 0000 0100 : CTV Voice exists : ;----------------------------------------------------------- ctv_card_here PROC NEAR PUSH DS ; for multi data model, set DS to MOV AX,CS ; DGROUP segment MOV DS,AX SUB BX,BX ; assume Creative Card doesn't exist, ; return 0 ;----------------------------- ; detect Game Blaster : ;----------------------------- MOV DX,io_addx ; get default i/o addx ADD DL,6 ; write the test code to MOV AL,CMS_TEST_CODE ; output port 2x6H OUT DX,AL ; ... SUB AL,AL ADD DL,4 ; read the data back from OUT DX,AL IN AL,DX ; input port 2xAH CMP AL,CMS_TEST_CODE ; the same data ? JNE CARD10 ; no, try Sound Blaster Card SUB DL,4 ; to ensure, try inverse data MOV AL,NOT CMS_TEST_CODE ; output port 2x6H OUT DX,AL ; ... SUB AL,AL ADD DL,4 ; read the data back from OUT DX,AL IN AL,DX ; input port 2xAH CMP AL,NOT CMS_TEST_CODE ; the same data ? JNE CARD10 ; no, try Sound Blaster Card MOV BX,CMS_EXIST JMP SHORT CARD50 ;-------------------------------------- ; detect the Sound Blaster Card : ;-------------------------------------- CARD10: CALL RESET_DSP JNZ CARD50 CARD40: MOV DX,io_addx ADD DL,0CH MOV AL,DSP_ID_CMD CALL WRITE_DSP_TIME JC CARD50 MOV AL,CMS_TEST_CODE CALL WRITE_DSP_TIME JC CARD50 CALL READ_DSP_TIME JC CARD50 CMP AL,NOT CMS_TEST_CODE JNE CARD50 MOV BX,CMS_EXIST + CTV_VOICE_EXIST ;----------------------------- ; detect the FM Music : ;----------------------------- CARD50: MOV AX,0001H ; reset the FM chip CALL WRITE_FM MOV AX,6004H ; mask of timer 1 & 2 CALL WRITE_FM MOV AX,8004H ; reset irq and both timer flags CALL WRITE_FM MOV AL,0 ; read for 3 msb clear CALL WAIT_FM_STATUS JC CARD90 MOV AX,0FF02H ; write timer 1 count CALL WRITE_FM MOV AX,2104H ; start timer 1 CALL WRITE_FM MOV AL,0C0H ; wait for irq and timer 1 end CALL WAIT_FM_STATUS JC CARD90 MOV AX,6004H ; mask of timer 1 & 2 CALL WRITE_FM MOV AX,8004H ; reset irq and both timer flags CALL WRITE_FM ADD BX,FM_MUSIC_EXIST CARD90: MOV AX,BX POP DS RET ctv_card_here ENDP ;------------------------------------------------------------------------ buf_init PROC NEAR mov bp,sp mov dx,word ptr[bp] mov sp,OFFSET newstack ;New stack pointer mov ah,4aH ;Mshrink mov bx,65535/16 ;Bytes left for program int 21H mov ah,48H d4b ;Malloc mov bx,65535/16 int 21H mov buf_seg,ax ;Pointer to allocated segment mov buf_off,0 mov bp,sp mov word ptr[bp],dx ret buf_init ENDP fn_buf db 80,0 fname db 80 dup(0) fhandle dw 0 dec_buf db 7 dup(0) ;----------------------------------------------------------------------- read_file PROC NEAR cmp byte ptr cs:[80h],0 ;Check for command parameters jz no_params mov si,81h Skip_leadspc: ;Skip leading spaces inc si cmp byte ptr[si],20h jz skip_leadspc mov di,OFFSET fname ;Extract filename next_fnbyte: movsb cmp byte ptr[si],20h jnz next_fnbyte skip_midspc: ;Skip middle spaces inc si cmp byte ptr[si],20h jz skip_midspc mov di,OFFSET dec_buf next_smpbyte: ;Extract sampling rate value movsb cmp byte ptr[si],0dh jz param_ready cmp byte ptr[si],20h jz param_ready jmp next_smpbyte param_ready: jmp over_msgs no_params: Print 'File to play: ' readstr fn_buf mov bp,offset fname mov si,OFFSET fn_buf+1 ;Remove trailing CR xor dx,dx mov dl,byte ptr[si] mov si,dx mov byte ptr [bp+si],00h cr_lf Print 'Sample rate: ' get_dec dec_buf over_msgs: dec2hex dec_buf mov sampling,dx mov ah,3dh ;Open file mov al,0 mov dx,offset fname int 21h jc read_error xchg ax,bx push ds mov ds,buf_seg ;Change to Buffer Segment mov ah,3fh ;Read file mov cx,65535 mov dx,00h int 21h pop ds mov count,ax ;Save bytes read in COUNT abort: ret read_error: pushf cr_lf Print 'Error: file not found' cr_lf popf jmp abort read_file ENDP ;------------------------------------------------------------------------ single_outp PROC NEAR mov cx,count shr cx,1 mov si,0 push ds mov ds,buf_seg lopi: mov al,17h call write_dsp mov al,ds:byte ptr[si] call write_dsp inc si mov al,ds:byte ptr[si] call write_dsp inc si loop lopi pop ds ret single_outp ENDP ;======================================================================== begin: call buf_init call read_file jc error_exit call reset_dsp mov al,01h call on_off_speaker ;Turn on voice speaker call ctv_output ; call single_outp push es mov es,buf_seg ;Mfree mov ah,49H int 21H pop es error_exit: mov ax,4c00h int 21h ;======================================================================== CSEG ENDS END start  . 0