home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Hack-Phreak Scene Programs
/
cleanhpvac.zip
/
cleanhpvac
/
C2SND201.ZIP
/
PPWAV.ASM
< prev
next >
Wrap
Assembly Source File
|
1994-06-11
|
29KB
|
1,497 lines
; PPWAV.ASM
;
; PreProcess Wav: this program performs one or more of the following
; conversions on a .wav file: mix to mono, convert to 8-bit, and halve
; sampling rate. This program is provided for users of PLAYWAV who have
; been confonted with the "Output underflow" message. Its purpose is to
; reduce the size of a .wav file so that it can be played on limited
; hardware (such as a floppy-only system, or to a lesser extent the SL).
; Note that mixing to mono and converting to 8-bit with PPWAV do not
; affect the sound quality when the .wav is to be played on a Tandy with
; PLAYWAV, since those conversions must be done when the .wav is played
; anyway.
; Some unusual .wav types that PLAYWAV can't play can also be con-
; verted with this program (more than 16 bits per channel, more than 2
; channels, sampling rate higher than 65535 Hz).
; The input file is copied rather then overwritten.
; This program is in .exe format due to memory requirements.
; Syntax is as follows:
;
; PPWAV <input file> <output file>
;
; There are no options. The user will be prompted for input from the key-
; board.
;
; Order of segments:
;
SCODE SEGMENT
SCODE ENDS
SDATA SEGMENT
SDATA ENDS
;
; Stack segment.
;
STACK SEGMENT STACK
DW 512 DUP (?)
STACK ENDS
;
; Buffer for output samples (16K).
;
OUTBUF SEGMENT
DB 16384 DUP (?)
OUTBUF ENDS
;
; Buffer for input samples (32K).
;
INBUF SEGMENT
DB 32768 DUP (?)
INBUF ENDS
SDATA SEGMENT
;
; Data.
;
; Template for .wav header. This header will be filled in to match the
; output file and written out. The strings below are also used to verify
; the input .wav.
;
WAVHEADER EQU $
RIFFSTR DB "RIFF" ; RIFF signature = "RIFF"
WAVLEN DD 0 ; (length of .wav file) - 8
WAVESTR DB "WAVE" ; WAVE signature = "WAVE"
FMTSTR DB "fmt " ; format chunk header = "fmt "
DD 16 ; length of format chunk = 16
DW 1 ; format type = 1 (Microsoft PCM)
NCHANNELS DW 0 ; number of channels
SAMPRATE DD 0 ; samples per second
BYTESPERSEC DD 0 ; bytes per second
SAMPSIZE DW 0 ; bytes per (multichannel) sample
SAMPWIDTH DW 0 ; bits per channel
DATASTR DB "data" ; data block header = "data"
DATALEN DD 0 ; number of bytes in data chunk
;
; Additional output parameters.
;
CHANSIZE DW 0 ; bytes per channel
ISSIGNED DB 0 ; 1 = output samples are signed
NSAMPLES DD 0 ; number of samples in output file
;
; Flag, 1 = format chunk processed.
;
FMTDONE DB 0
;
; Input file format parameters.
;
INISSIGNED DB 0 ; 1 = input samples are signed
INNCHANNELS DW 0 ; number of channels
INSAMPRATE DD 0 ; sampling rate
INSAMPWIDTH DW 0 ; bits per channel
INCHANSIZE DW 0 ; bytes per channel
INSAMPSIZE DW 0 ; bytes per (multichannel) sample
INNSAMPLES DD 0 ; number of samples in input file
;
; Small buffer for reading header fields and for processing
; samples.
;
SMALLBUF DB 16 DUP (0)
;
; Small buffers for mixing channels.
;
MIXBUF DB 9 DUP (0)
CHANBUF DB 9 DUP (0)
;
; Number of input samples needed to make 1024 output samples.
;
SAMPTOREAD DW 1024 ; change to 2048 if HALVEFLAG on
;
; Number of bytes to read to get 1024 output samples.
;
BYTESTOREAD DW 0
;
; Number of bytes from the input buffer that have been
; processed.
;
BYTESTAKEN DW 0
;
; Number of bytes placed in the output buffer.
;
BYTESPLACED DW 0
;
; Number of input bytes on each channel to skip over.
;
SKIPLENGTH DW 0
;
; Error messages.
;
USAGEMSG DB "Usage: PPWAV <input file> <output file>",0Dh,0Ah
DB "- see docs for details.",0Dh,0Ah,"$"
INOPENMSG DB "Error opening input file.",0Dh,0Ah,"$"
OUTOPENMSG DB "Unable to create output file.",0Dh,0Ah,"$"
READERRMSG DB "Error reading input .wav file.",0Dh,0Ah,"$"
WRITEERRMSG DB "Error writing output .wav file.",0Dh,0Ah,"$"
BADWAVMSG DB "Input .wav file invalid or unsupported type."
DB 0Dh,0Ah,"$"
;
; File handles.
;
INHANDLE DW 0 ; input file handle
OUTHANDLE DW 0 ; output file handle
;
; Information about the input file to be displayed to the user.
;
MSGA DB "Input file: $"
INFILENAME DB 128 DUP (0) ; input file name
MSGB DB 0Dh,0Ah,9,"$"
MSGC DB " channels",0Dh,0Ah,9,"$"
MSGD DB " samples per second (Hz)",0Dh,0Ah,9,"$"
MSGE DB " bits per channel",0Dh,0Ah,9,"$"
MSGF DB "samples are signed",0Dh,0Ah,0Dh,0Ah,"$"
MSGG DB "samples are unsigned",0Dh,0Ah,0Dh,0Ah,"$"
;
; Action flags. 1 = action selected.
;
MIXFLAG DB 0
CONVERTFLAG DB 0
HALVEFLAG DB 0
;
; Prompts for the user.
;
MONOMSG DB " Mix to mono?$"
CONVERTMSG DB " Convert to 8-bit?$"
HALVEMSG DB "Halve sampling rate?$"
YHIMSG DB " (Y/n) $"
NHIMSG DB " (y/N) $"
;
; Buffer for user input.
;
USERBUF DB 2
ANSWERED DB 0 ; = 1 if the user answered
ANSWER DB 2 DUP (0) ; user's answer in first byte
DATA ENDS
SCODE SEGMENT
;
; Error handling subroutine. Displays the string addressed by DS:DX and
; halts the program.
;
ERROR:
MOV AH,9
INT 21h
MOV AX,4C01h ; return ERRORLEVEL 1
INT 21h
;
; Subroutine, takes pointer to string in DS:SI and length of string in CX,
; skips over blanks and tabs, returns pointer to first nonblank character
; in the string in DS:SI, length of remaining string in CX. If end of
; string is reached, return pointer to end of string in DS:SI, zero in CX.
;
SKIPBLANKS:
PUSH AX
SKIPBLANKS_LOOP:
JCXZ SKIPBLANKS_END
LODSB
DEC CX
CMP AL,9
JE SKIPBLANKS_LOOP
CMP AL,20h
JE SKIPBLANKS_LOOP
DEC SI
INC CX
SKIPBLANKS_END:
POP AX
RET
;
; Subroutine, takes pointer to string is DS:SI and length of string in CX,
; skips over nonblank characters, returns pointer to first blank or tab in
; the string in DS:SI, length of remaining string in CX. If end of string
; is reached, return pointer to end of string in DS:SI, zero in CX.
;
SKIPNONBLANK:
PUSH AX
SKIPNONBLANK_LOOP:
JCXZ SKIPNONBLANK_END
LODSB
DEC CX
CMP AL,9
JE SKIPNONBLANK_LPEND
CMP AL,20h
JNE SKIPNONBLANK_LOOP
SKIPNONBLANK_LPEND:
DEC SI
INC CX
SKIPNONBLANK_END:
POP AX
RET
;
; Subroutine, reads input and output filenames from the command line, opens
; the first file, and creates the second file. Displays a usage message if
; two filenames are not specified.
;
OPENFILES:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
;
; Exchange DS, ES. DS addresses PSP; ES addresses local data.
;
PUSH DS
PUSH ES
POP DS
POP ES
;
; Find first command line parameter, save pointer to it in DI.
;
MOV CL,[80h]
XOR CH,CH
MOV SI,81h
CALL SKIPBLANKS
JCXZ OPENFILES_USAGEERR
MOV DI,SI
;
; Find the end of it and save its length in BX.
;
MOV DX,SI ; for Int 21h fcn 3Dh
CALL SKIPNONBLANK
JCXZ OPENFILES_USAGEERR
MOV BX,SI
SUB BX,DI
;
; Append a null.
;
MOV BYTE PTR [SI],0
INC SI
DEC CX
JCXZ OPENFILES_USAGEERR
;
; Open the input file.
;
MOV AX,3D20h ; open read-only, deny writes
INT 21h
JC OPENFILES_OPEN1ERR
MOV ES:INHANDLE,AX
;
; Find the second command line parameter.
;
CALL SKIPBLANKS
JCXZ OPENFILES_USAGEERR
;
; Append a null.
;
MOV DX,SI ; for Int 21h fcn 3Ch
CALL SKIPNONBLANK
MOV BYTE PTR [SI],0
;
; Create the output file.
;
MOV AH,3Ch
XOR CX,CX ; attribute: normal file
INT 21h
JC OPENFILES_OPEN2ERR
MOV ES:OUTHANDLE,AX
;
; Copy the input filename into the data segment.
;
MOV SI,DI ; saved above
MOV CX,BX ; also
MOV DI,OFFSET INFILENAME
REP MOVSB
MOV BYTE PTR ES:[DI],"$"
;
; Swap DS, ES back.
;
PUSH DS
PUSH ES
POP DS
POP ES
;
; Restore registers and exit.
;
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Two files were not specified on the command line.
;
OPENFILES_USAGEERR:
PUSH ES
POP DS
MOV DX,OFFSET USAGEMSG
JMP ERROR
;
; Unable to open input file.
;
OPENFILES_OPEN1ERR:
PUSH ES
POP DS
MOV DX,OFFSET INOPENMSG
JMP ERROR
;
; Unable to create output file.
;
OPENFILES_OPEN2ERR:
PUSH ES
POP DS
MOV DX,OFFSET OUTOPENMSG
JMP ERROR
;
; Subroutine to read a small number of bytes (specified in CX) from the input
; file into SMALLBUF. Returns number of bytes in AX, pointer to SMALLBUF in
; DX.
;
READSMALL:
PUSH BX
MOV AH,3Fh
MOV BX,INHANDLE
MOV DX,OFFSET SMALLBUF
INT 21h
JC READSMALL_READERR
CMP AX,CX
JNE READSMALL_INVALID
POP BX
RET
;
; Error reading .wav file header.
;
READSMALL_READERR:
MOV DX,OFFSET READERRMSG
JMP ERROR
;
; Invalid .wav header, or unsupported .wav type.
;
READSMALL_INVALID:
MOV DX,OFFSET BADWAVMSG
JMP ERROR
;
; Subroutine for GETWAVHEADER. Reads 4 bytes from the input file and checks
; whether the string read is "fmt ", "data", another ASCII string, or not
; ASCII. Returns AL = 0 if "fmt ", AL = 1 if "data", AL = -1 if another
; ASCII string. Halts the program if not ASCII. Assumes ES addresses data
; segment. Returns pointer to SMALLBUF in DX.
;
GETCHUNK:
PUSH BX
PUSH CX
PUSH SI
PUSH DI
;
; Read 4 bytes from the file.
;
MOV CX,4
CALL READSMALL
;
; Check if format chunk.
;
MOV SI,DX
MOV DI,OFFSET FMTSTR
REPE CMPSB
JNE GETCHUNK_CHKDATA
MOV AL,0
JMP GETCHUNK_EXIT
;
; Check if data chunk.
;
GETCHUNK_CHKDATA:
MOV SI,DX
MOV DI,OFFSET DATASTR
MOV CX,4
REPE CMPSB
JNE GETCHUNK_CHKASCII
MOV AL,1
JMP GETCHUNK_EXIT
;
; Check if a valid chunk header of another type.
;
GETCHUNK_CHKASCII:
MOV SI,DX
MOV CX,4
GETCHUNK_ASCLOOP:
LODSB
CMP AL,20h
JB GETCHUNK_INVALID
CMP AL,7Eh
JA GETCHUNK_INVALID
LOOP GETCHUNK_ASCLOOP
MOV AL,-1
;
; Read the chunk length field and exit.
;
GETCHUNK_EXIT: PUSH AX
MOV CX,4
CALL READSMALL
POP AX
POP DI
POP SI
POP CX
POP BX
RET
;
; Invalid .wav header, or unsupported .wav type.
;
GETCHUNK_INVALID:
MOV DX,OFFSET BADWAVMSG
JMP ERROR
;
; Subroutine for GETWAVHEADER. This routine reads the format chunk from the
; input file and records the information found there. Assumes DS:DX addresses
; the chunk length.
;
DOFORMAT:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
;
; Check chunk length (must be 16).
;
MOV SI,DX
LODSW
CMP AX,16
JNE DOFORMAT_INVALID
MOV CX,AX
LODSW
OR AX,AX
JNZ DOFORMAT_INVALID
;
; Read in format chunk.
;
CALL READSMALL
;
; Verify format tag.
;
MOV SI,DX
LODSW
CMP AX,1
JNE DOFORMAT_INVALID
;
; Get number of channels and save.
;
LODSW
OR AX,AX ; invalid if zero channels
JZ DOFORMAT_INVALID
CMP AX,16 ; an impossibly large value ...
JA DOFORMAT_INVALID
MOV INNCHANNELS,AX
;
; Get sampling rate.
;
LODSW
MOV WORD PTR INSAMPRATE,AX
MOV DX,AX
LODSW
OR DX,AX ; rate of zero invalid
OR DX,DX
JZ DOFORMAT_INVALID
MOV WORD PTR INSAMPRATE+2,AX
;
; Get bits per channel.
;
ADD SI,6 ; skip bytes/sec, block align
LODSW
OR AX,AX ; zero bits per sample invalid
JZ DOFORMAT_INVALID
CMP AX,128 ; an impossibly large value ...
JA DOFORMAT_INVALID
MOV INSAMPWIDTH,AX
;
; Compute bytes per channel.
;
ADD AX,7
SHR AX,1
SHR AX,1
SHR AX,1
MOV INCHANSIZE,AX
;
; Compute bytes per (multichannel) sample.
;
MUL INNCHANNELS
CMP AX,16 ; this is what the buffer will hold ...
JA DOFORMAT_INVALID
MOV INSAMPSIZE,AX
;
; Are samples signed?
;
CMP INCHANSIZE,2
CMC
RCL INISSIGNED,1
;
; Exit.
;
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Invalid .wav header, or unsupported .wav type.
;
DOFORMAT_INVALID:
MOV DX,OFFSET BADWAVMSG
CALL ERROR
;
; Subroutine for GETWAVHEADER. This routine skips over an unknown chunk,
; updating the file pointer. Assumes DS:DX addresses the chunk length.
;
SKIPCHUNK:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
MOV SI,DX
LODSW
MOV DX,AX
LODSW
MOV CX,AX
MOV AX,4201h
MOV BX,INHANDLE
INT 21h
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Routine to read the .wav header from the input file and compute needed
; parameters from it. Skips unknown chunks.
;
GETWAVHEADER:
PUSH AX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
PUSH ES
;
; ES addresses data segment.
;
MOV AX,DS
MOV ES,AX
;
; Read RIFF and WAVE headers from file.
;
MOV CX,12
CALL READSMALL
;
; Verify "RIFF".
;
MOV SI,DX
MOV DI,OFFSET RIFFSTR
MOV CX,4
REPE CMPSB
JNE GETWAVHEADER_INVALID
;
; Verify "WAVE".
;
ADD SI,4 ; skip length field
MOV DI,OFFSET WAVESTR
MOV CX,4
REPE CMPSB
JNE GETWAVHEADER_INVALID
;
; Loop over chunks until data chunk found.
;
GETWAVHEADER_LOOP:
CALL GETCHUNK
CMP AL,0 ; format chunk?
JNE >L0
CMP FMTDONE,1 ; error if > 1 format chunk
JE GETWAVHEADER_INVALID
CALL DOFORMAT
MOV FMTDONE,1 ; mark format done
JMP GETWAVHEADER_LOOP
L0: CMP AL,1 ; data chunk?
JNE >L1
CMP FMTDONE,0 ; error if format chunk does
JE GETWAVHEADER_INVALID ; not precede data chunk
JMP GETWAVHEADER_LOOPEND
L1: CALL SKIPCHUNK ; unknown chunk, skip
JMP GETWAVHEADER_LOOP
;
; Data chunk found, exit.
;
GETWAVHEADER_LOOPEND:
POP ES
POP DI
POP SI
POP DX
POP CX
POP AX
RET
;
; Invalid .wav header, or unsupported .wav type.
;
GETWAVHEADER_INVALID:
MOV DX,OFFSET BADWAVMSG
CALL ERROR
;
; Subroutine to display a decimal number on the screen. The number is in
; DX:AX.
;
SHOWDECIMAL:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
MOV BX,10 ; BX = 10 (constant for division)
XOR CX,CX ; CX counts the digits
MOV DI,DX ; DI:SI holds the number while it's
MOV SI,AX ; being divided away
MOV AX,DX
;
; Divide away the number, saving remainders on the stack.
;
SHOWDECIMAL_LOOP1:
XOR DX,DX
DIV BX
MOV DI,AX
MOV AX,SI
DIV BX
MOV SI,AX
PUSH DX ; push digit (in low byte)
INC CX ; bump count
MOV AX,DI ; go again if not zero
OR AX,AX
JNZ SHOWDECIMAL_LOOP1
OR SI,SI
JNZ SHOWDECIMAL_LOOP1
;
; Pop the digits off the stack and display them. (CX is now the loop
; counter.)
;
SHOWDECIMAL_LOOP2:
POP DX
ADD DL,'0'
MOV AH,2
INT 21h
LOOP SHOWDECIMAL_LOOP2
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Routine to display information about the input .wav for the user.
;
SHOWWAVINFO:
PUSH AX
PUSH DX
;
; Display input filename.
;
MOV DX,OFFSET MSGA
MOV AH,9
INT 21h
MOV DX,OFFSET INFILENAME
MOV AH,9
INT 21h
MOV DX,OFFSET MSGB
MOV AH,9
INT 21h
;
; Display number of channels.
;
MOV AX,INNCHANNELS
XOR DX,DX
CALL SHOWDECIMAL
MOV DX,OFFSET MSGC
MOV AH,9
INT 21h
;
; Display sampling rate.
;
MOV AX,WORD PTR INSAMPRATE
MOV DX,WORD PTR INSAMPRATE+2
CALL SHOWDECIMAL
MOV DX,OFFSET MSGD
MOV AH,9
INT 21h
;
; Display bits per channel.
;
MOV AX,INSAMPWIDTH
XOR DX,DX
CALL SHOWDECIMAL
MOV DX,OFFSET MSGE
MOV AH,9
INT 21h
;
; Display whether samples are signed or unsigned.
;
CMP INISSIGNED,1
JNE >L0
MOV DX,OFFSET MSGF
JMP >L1
L0:
MOV DX,OFFSET MSGG
L1:
MOV AH,9
INT 21h
POP DX
POP AX
RET
;
; Subroutine to display a prompt to the user and get a "yes" or "no" answer.
; Takes 3 parameters. DS:DX addresses the prompt to be displayed (terminated
; with a dollar sign). DS:BX addresses the location where the answer will
; be stored (0 = no, 1 = yes). AL is the default answer (if the user just
; hits return or enters something other than "y" or "n").
;
GETYN:
PUSH AX
PUSH CX
PUSH DX
;
; Save default answer in CL.
;
MOV CL,AL
;
; Display the prompt.
;
MOV AH,9
INT 21h
;
; Display "(Y/n)" if "yes" is the default, or "(y/N)" if "no" is
; the default.
;
CMP CL,1
JNE >L0
MOV DX,OFFSET YHIMSG
JMP >L1
L0:
MOV DX,OFFSET NHIMSG
L1:
MOV AH,9
INT 21h
;
; Get the answer.
;
MOV AH,0Ah
MOV DX,OFFSET USERBUF
INT 21h
;
; Set result according to answer.
;
MOV [BX],CL ; assume no (default) answer
CMP BYTE PTR ANSWERED,1 ; just exit if no answer
JNE GETYN_EXIT
;
; Get answer in AL and convert to 0 or 1.
;
MOV AL,ANSWER
AND AL,0DFh ; convert to uppercase
CMP AL,'Y'
JNE >L0
MOV AL,1
JMP >L1
L0:
CMP AL,'N'
JNE GETYN_EXIT
MOV AL,0
L1:
MOV [BX],AL
;
; Move cursor to next line.
;
GETYN_EXIT:
MOV AH,2
MOV DL,0Dh
INT 21h
MOV AH,2
MOV DL,0Ah
INT 21h
POP DX
POP CX
POP AX
RET
;
; Routine to prompt the user for the desired actions. The user may choose
; to do one or more of the following: mix to mono, convert to 8-bit, and/or
; cut the sampling rate in half.
;
GETACTIONS:
PUSH AX
PUSH BX
PUSH DX
;
; Ask if the user wants to mix to mono.
;
MOV DX,OFFSET MONOMSG
MOV BX,OFFSET MIXFLAG
MOV AL,1
CALL GETYN
;
; Ask if the user wants to convert to 8-bit.
;
MOV DX,OFFSET CONVERTMSG
MOV BX,OFFSET CONVERTFLAG
MOV AL,1
CALL GETYN
;
; Ask if the user wants to cut the sampling rate in half.
;
MOV DX,OFFSET HALVEMSG
MOV BX,OFFSET HALVEFLAG
MOV AL,0
CALL GETYN
POP DX
POP BX
POP AX
RET
;
; Routine to fill in the fields in the output .wav header and write the
; header to the output file.
;
MAKEHEADER:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
;
; Determine number of channels in the output file.
;
CMP MIXFLAG,0
JNE >L0
MOV AX,INNCHANNELS ; no mixing
MOV NCHANNELS,AX
JMP >L1
L0:
MOV NCHANNELS,1 ; mix to mono
;
; Determine sampling rate for output file.
;
L1:
MOV AX,WORD PTR INSAMPRATE
MOV DX,WORD PTR INSAMPRATE+2
CMP HALVEFLAG,0
JE >L2
SHR DX,1 ; cut sampling rate in half
RCR AX,1
L2:
MOV WORD PTR SAMPRATE,AX
MOV WORD PTR SAMPRATE+2,DX
;
; Determine number of bits per channel for the output file.
;
CMP CONVERTFLAG,0
JNE >L3
MOV AX,INSAMPWIDTH ; do not convert to 8-bit
MOV SAMPWIDTH,AX
MOV AX,INCHANSIZE
MOV CHANSIZE,AX
JMP >L4
L3:
MOV SAMPWIDTH,8 ; convert to 8-bit
MOV AX,1
MOV CHANSIZE,AX
;
; Determine whether output samples are signed.
;
L4:
CMP AX,2
CMC
RCL ISSIGNED,1
;
; Determine number of bytes per (multichannel) sample.
;
MUL NCHANNELS
MOV SAMPSIZE,AX
;
; Determine number of bytes per second.
;
MOV AX,WORD PTR SAMPRATE
MUL SAMPSIZE
MOV WORD PTR BYTESPERSEC,AX
MOV BX,DX
MOV AX,WORD PTR SAMPRATE+2
MUL SAMPSIZE
ADD AX,BX
MOV WORD PTR BYTESPERSEC+2,AX
;
; Determine number of (multichannel) samples from length of input
; file.
;
MOV AX,4201h ; get current input file pointer
MOV BX,INHANDLE
XOR CX,CX
XOR DX,DX
INT 21h
JNC >L5
JMP MAKEHEADER_READERR
L5:
MOV DI,DX ; save in DI:SI
MOV SI,AX
MOV AX,4202h ; seek to end of input file
MOV BX,INHANDLE
XOR CX,CX
XOR DX,DX
INT 21h
JNC >L6
JMP MAKEHEADER_READERR
L6:
SUB AX,SI ; determine number of sample bytes
SBB DX,DI
MOV BX,AX ; divide high word by length of sample
MOV AX,DX
XOR DX,DX
DIV INSAMPSIZE
MOV WORD PTR INNSAMPLES+2,AX ; save high word of result
MOV AX,BX ; divide low word + remainder
DIV INSAMPSIZE
MOV WORD PTR INNSAMPLES,AX ; save low word of result
MOV AX,4200h ; seek back to start of sample data
MOV BX,INHANDLE
MOV CX,DI
MOV DX,SI
INT 21h
JC MAKEHEADER_READERR
;
; Determine number of output samples.
;
MOV AX,WORD PTR INNSAMPLES
MOV DX,WORD PTR INNSAMPLES+2
CMP HALVEFLAG,0
JE >L7
ADD AX,1 ; divide by 2 and round up
ADC DX,0
SHR DX,1
RCR AX,1
L7:
MOV WORD PTR NSAMPLES,AX
MOV WORD PTR NSAMPLES+2,DX
;
; Determine length of output data.
;
MUL SAMPSIZE
MOV WORD PTR DATALEN,AX
MOV BX,DX
MOV AX,WORD PTR NSAMPLES+2
MUL SAMPSIZE
ADD AX,BX
MOV WORD PTR DATALEN+2,AX
;
; Determine length of output .wav file.
;
MOV DX,AX
MOV AX,WORD PTR DATALEN
ADD AX,36
ADC DX,0
MOV WORD PTR WAVLEN,AX
MOV WORD PTR WAVLEN+2,DX
;
; Write the header out.
;
MOV AH,40h
MOV BX,OUTHANDLE
MOV CX,44
MOV DX,WAVHEADER
INT 21h
JC MAKEHEADER_WRITEERR
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Seek error on input file.
;
MAKEHEADER_READERR:
MOV DX,OFFSET READERRMSG
JMP ERROR
;
; Error writing to output file.
;
MAKEHEADER_WRITEERR:
MOV DX,OFFSET WRITEERRMSG
JMP ERROR
;
; Subroutine, reads data into input buffer, sufficient to make 1024 output
; samples. The amount of data successfully read is returned in CX - the
; equivalent in output samples of the data successfully read. Halts the
; program with an error message on file error.
;
READDATA:
PUSH AX
PUSH BX
PUSH DX
;
; Read from the input file.
;
PUSH DS
MOV AH,3Fh
MOV BX,INHANDLE
MOV CX,BYTESTOREAD
MOV DX,SEG INBUF
MOV DS,DX
XOR DX,DX
INT 21h
POP DS
JC READDATA_READERR
;
; Check if the full amount requested was read.
;
CMP AX,CX
JB READDATA_EOF
;
; Got the full amount.
;
MOV CX,1024
JMP READDATA_EXIT
;
; End of file - less than the full amount requested was read.
;
READDATA_EOF:
XOR DX,DX ; calculate number of samples read
DIV INSAMPSIZE
MOV CX,AX
CMP HALVEFLAG,0 ; if not halving, exit
JE READDATA_EXIT
INC CX ; halving - divide by 2 and round up
SHR CX,1
;
; Exit.
;
READDATA_EXIT:
POP DX
POP BX
POP AX
RET
;
; Error reading input file.
;
READDATA_READERR:
MOV DX,OFFSET READERRMSG
JMP ERROR
;
; Subroutine to write out the output buffer contents. Halts the program
; with an error message if unsuccessful. Uses BYTESPLACED.
;
WRITEDATA:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DS
MOV AH,40h
MOV BX,OUTHANDLE
MOV CX,BYTESPLACED
MOV DX,SEG OUTBUF
MOV DS,DX
XOR DX,DX
INT 21h
POP DS
JC WRITEDATA_WRITEERR
POP DX
POP CX
POP BX
POP AX
RET
;
; Error writing to output file.
;
WRITEDATA_WRITEERR:
MOV DX,OFFSET WRITEERRMSG
JMP ERROR
;
; Subroutine to copy a multichannel sample from the input buffer to the small
; buffer in the data segment.
;
COPYSAMPLE:
PUSH AX
PUSH CX
PUSH SI
PUSH DI
PUSH DS
PUSH ES
MOV CX,INSAMPSIZE ; CX = number of bytes to copy
MOV SI,BYTESTAKEN ; DS:SI -> bytes to copy
PUSH DS
POP ES ; ES:DI -> small buffer
MOV AX,SEG INBUF
MOV DS,AX
MOV DI,OFFSET SMALLBUF
REP MOVSB
POP ES
POP DS
MOV BYTESTAKEN,SI
POP DI
POP SI
POP CX
POP AX
RET
;
; Subroutine to convert the multichannel sample in the small buffer to 8-bit.
;
CONVTO8:
CMP INCHANSIZE,1 ; if already 8-bit, return immediately
JNE CONVTO8_PROCEED
RET
CONVTO8_PROCEED:
PUSH AX
PUSH CX
PUSH SI
PUSH DI
PUSH ES
MOV SI,OFFSET SMALLBUF
PUSH DS
POP ES
MOV DI,SI
MOV CX,INNCHANNELS
CONVTO8_CHANLOOP:
ADD SI,SKIPLENGTH
LODSB
ADD AL,128
STOSB
LOOP CONVTO8_CHANLOOP
POP ES
POP DI
POP SI
POP CX
POP AX
RET
;
; Subroutine to mix a multichannel 8-bit unsigned sample. The sample is
; assumed to be in the small buffer, and ES and DS are assumed to address
; the data segment. The result is placed back in the small buffer.
;
MIX8:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
;
; DS:SI addresses the sample.
;
MOV SI,OFFSET SMALLBUF
;
; Add up the channels.
;
MOV CX,INNCHANNELS
XOR BX,BX
MIX8_ADDLOOP:
LODSB
ADD BL,AL
ADC BH,0
LOOP MIX8_ADDLOOP
;
; Divide by the number of input channels and save result.
;
MOV AX,BX
XOR DX,DX
DIV INNCHANNELS
MOV SMALLBUF,AL
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Subroutine to mix a multichannel 16 (or more)-bit signed sample. The
; sample is assumed to be in the small buffer, and ES and DS are assumed
; to address the data segment. The result is placed back in the small
; buffer.
;
MIX16:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
;
; Clear the mixing buffer.
;
MOV DI,OFFSET MIXBUF
MOV AL,0
MOV CX,CHANSIZE
REP STOSB
STOSB
;
; Loop over the channels.
;
MOV SI,OFFSET SMALLBUF
MOV CX,INNCHANNELS
MIX16_ADDLOOP:
PUSH CX
;
; Copy all but the last byte of the channel to the channel buffer.
;
MOV CX,CHANSIZE ; CHANSIZE is at least 2 for signed
DEC CX
MOV DI,OFFSET CHANBUF
REP MOVSB
;
; Get the last byte, sign extend, add to convert to unsigned, and
; place in the channel buffer.
;
LODSB
CBW
ADD AX,80h
MOV [DI],AX
;
; Add the channel to the mixing buffer.
;
PUSH SI
MOV SI,OFFSET CHANBUF
MOV DI,OFFSET MIXBUF
MOV CX,CHANSIZE
INC CX
CLC
LAHF
MIX16_DOADD:
LODSB
SAHF
ADC [DI],AL
LAHF
INC DI
LOOP MIX16_DOADD
POP SI
;
; Go to next channel.
;
POP CX
LOOP MIX16_ADDLOOP
;
; Divide by the number of channels.
;
STD ; set direction to decrement
MOV CX,CHANSIZE
MOV SI,CX
ADD SI,OFFSET MIXBUF
DEC CX
MOV DI,CX
ADD DI,OFFSET SMALLBUF
;
; Divide high 2 bytes as word to avoid overflow.
;
LODSB
MOV AH,AL
LODSB
XOR DX,DX
DIV INNCHANNELS
STOSB ; discard high 8 bits of quotient
MOV AH,DL
;
; Complete the division.
;
MOV BL,BYTE PTR INNCHANNELS
MIX16_DIVLOOP:
LODSB
DIV BL
STOSB
LOOP MIX16_DIVLOOP
CLD ; restore direction flag
;
; Convert the result to signed.
;
MOV SI,OFFSET SMALLBUF-1
ADD SI,CHANSIZE
ADD BYTE PTR [SI],80h
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
RET
;
; Subroutine to mix the multichannel sample in the small buffer to mono.
; Assumes that the sample has already been converted to 8-bit if that was
; to be done. This version special-cases 8-bit samples.
;
; Exit immediately if already mono.
;
DOMIX:
CMP INNCHANNELS,1
JNE DOMIX_PROCEED
RET
;
; Both DS and ES address the data segment.
;
DOMIX_PROCEED:
PUSH ES
PUSH DS
POP ES
;
; Call the appropriate subroutine for signed or unsigned (8-bit)
; samples.
;
CMP ISSIGNED,0
JE DOMIX_8BIT
CALL MIX16
JMP DOMIX_EXIT
DOMIX_8BIT:
CALL MIX8
DOMIX_EXIT:
POP ES
RET
;
; Subroutine to copy a sample from the small buffer to the output buffer.
; Uses and updates BYTESPLACED.
;
PUTSAMPLE:
PUSH CX
PUSH SI
PUSH DI
PUSH ES
MOV CX,SEG OUTBUF
MOV ES,CX
MOV DI,BYTESPLACED
MOV SI,OFFSET SMALLBUF
MOV CX,SAMPSIZE
REP MOVSB
MOV BYTESPLACED,DI
POP ES
POP DI
POP SI
POP CX
RET
;
; Routine to perform the actual conversion of samples in the input file and
; write the results to the output file. Samples are converted 1024 output
; samples at a time; if HALVEFLAG is on, this will be 2048 input samples.
;
PERFORM:
PUSH AX
;
; Determine number of input samples for each pass.
;
CMP HALVEFLAG,0
JE PERFORM_1024
MOV SAMPTOREAD,2048
PERFORM_1024:
;
; Determine number of input bytes for each pass.
;
MOV AX,SAMPTOREAD
MUL INSAMPSIZE
MOV BYTESTOREAD,AX
;
; Determine number of bytes to "skip over" from each input channel.
;
MOV AX,INCHANSIZE
SUB AX,CHANSIZE
MOV SKIPLENGTH,AX
;
; Main loop. Read data (BYTESTOREAD bytes) from the input file into
; the input buffer.
;
PERFORM_MAINLOOP:
CALL READDATA
JCXZ PERFORM_MAINLPEND ; exit loop and end of file
;
; Number of bytes taken from the input buffer set to zero.
;
MOV BYTESTAKEN,0
;
; Number of bytes placed in output buffer set to zero.
;
MOV BYTESPLACED,0
;
; Loop over samples in the buffer.
;
PERFORM_SAMPLOOP:
CALL COPYSAMPLE ; get a sample in the small buffer
;
; If converting to 8-bit, do the conversion.
;
CMP CONVERTFLAG,0
JE PERFORM_CHKMIX
CALL CONVTO8
;
; If mixing to mono, do so.
;
PERFORM_CHKMIX:
CMP MIXFLAG,0
JE PERFORM_DOPUT
CALL DOMIX
;
; Put the sample in the output buffer.
;
PERFORM_DOPUT:
CALL PUTSAMPLE
;
; If halving the sampling rate, skip a sample.
;
CMP HALVEFLAG,0
JE PERFORM_SAMPLPTEST
MOV AX,BYTESTAKEN
ADD AX,INSAMPSIZE
MOV BYTESTAKEN,AX
PERFORM_SAMPLPTEST:
LOOP PERFORM_SAMPLOOP
;
; Write the output buffer and go read more data.
;
CALL WRITEDATA
JMP PERFORM_MAINLOOP
PERFORM_MAINLPEND:
POP AX
RET
;
; Main program.
;
MAIN:
;
; DS addresses data segment (always - if any routine changes DS, it
; must change it back before returning).
;
MOV AX,SEG SDATA
MOV DS,AX
;
; Set direction to increment (always - if any routine changes DF, it
; must restore it before returning).
;
CLD
;
; Open input file and create output file.
;
CALL OPENFILES
;
; Read the .wav header from the input file.
;
CALL GETWAVHEADER
;
; Display the header information for the user.
;
CALL SHOWWAVINFO
;
; Get desired actions from user.
;
CALL GETACTIONS
;
; Fill in fields in the output .wav header and write it out.
;
CALL MAKEHEADER
;
; Convert the file.
;
CALL PERFORM
;
; Terminate.
;
MOV AX,4C00h
INT 21h
SCODE ENDS
END MAIN