This article contains the following executables: ACOMP.ARC
John is president of THE Audio Solution and director of product development for Milliken Publishing. He is the author of 688 Attack Sub and can be contacted at 747 Napa Lane, St. Charles, MO 63303.
As we create more powerful applications software, making audio data part of the user interface becomes more and more desirable. Voice e-mail, spoken context-sensitive online help, training systems, educational software, and games are all prime applications of this technology.
However, the amount of disk storage space digitized sound requires is a barrier to the widespread adoption of sound. Standard compression techniques using tools such as ARC or ZIP, for instance, will achieve only about a 10 percent compression rate on audio data. Clearly, compressing digitized sound requires algorithms crafted to match the nature of audio data itself. Consequently, this article presents a compression algorithm called "ACOMP" that yields better than 6:1 compression on human voice, and between 1.5:1 and 3:1 on music, while maintaining almost full fidelity.
Digitized sound is produced by a process called "analog-to-digital" (A/D) conversion, in which sound waves are converted into a digital number that represents the volume (amplitude) of the sound. On a computer, a single digital sample is usually recorded in one byte as an 8-bit number between 0 and 255, where 0 is the quietest and 255 the loudest; a good sound sample should just fit within this range. Many repeated samples must be taken to record an appreciable amount of sound. Acceptable human voice, for example, requires between 7000 and 9000 samples per second (7-9 KHz). Note that this is the same method by which sound is stored on a compact disc. However, a compact disc stores 44,000 samples per second, each 16 bits in resolution. At this resolution, one minute of sound takes four Mbytes of storage! Even with the much lower resolution of 9-KHz, 8-bit sound, one minute of speech takes over half a megabyte. ACOMP lets you store that same minute of sound in as little as 100K.
How Audio Compression Works
The human ear doesn't recognize sound by volume. You can take an entire sound sample, round it off to the closest multiple of 16 (reduce it to 4-bit data), and having achieved 2:1 compression, still hear it. What the human ear does hear is the frequency response within that data. A compression algorithm that closely approximates the data points' original values, but which cannot track frequencies within that sound, will distort the frequency response, rendering it virtually unrecognizable to the human ear.
Almost all audio-compression algorithms incorporate two basic techniques: silence encoding and delta modulation. Silence encoding is extremely important for human speech. If you examine a waveform of human speech, you will see long, relatively flat pauses between the spoken words. Silence encoding represents these pauses in a single byte of data as a pause duration rather than storing each individual data point over and over. When performing silence encoding, the user usually specifies an acceptable threshold value, typically +/-1 or 2. As long as the data samples fluctuate by no more than this amount, the whole section is considered a pause. This helps achieve extremely high data-compression rates for human voice. The trade-off is that you lose some of the highest-frequency sounds. Some of the "s"s, for example, begin to lose clarity.
The other method of compressing audio data uses a technique called "delta modulation." While ordinary compression algorithms seek an efficient method of recording the input data stream itself, delta modulation tries to find an efficient method of recording the changes in the data stream. By modeling the changes from one data sample to the next, we accurately maintain the frequency response of the input data, while balancing exact accuracy of amplitude data. (By modeling the changes in the data, we are taking the first derivative of the waveform, which contains velocity information.) Any small errors introduced into the reconstructed data are heard as slight static. The less accurately we model the data stream, the more static we hear. However, the frequency response of the input data stream is never lost, because whenever the original sound sample goes up, we go up, and when it goes down, we go down.
How Delta Modulation Works
Delta modulation is a simple concept. In an 8-bit input data stream which yields numbers between 0 and 255, you would need numbers between -255 and +255 to store the values that represent each data-point change. This would require nine bits--one more than the original sound sample! However, you don't really need nine bits. Instead, you can use four bits to represent numbers from -8 to +8 (skipping 0). If one sample were 0 and the next 230, you might store a +7 with a multiplier of 32. This would produce a value of 224. This isn't the 230 value it should be, but the exact value is less important than the frequency response, which has been maintained. So you have stored an 8-bit value in just four bits, but this gives you only a 2:1 compression rate. It doesn't model the waveform well because each data point can be off by as much as +/-31 from its actual value. The result is a lot of static.
When working with delta modulation, you can control two variables: bit resolution and the multiplier.
Bit resolution is the number of bits used to represent the delta modulation. The fewer you use, the better compression rate you achieve. With 1-bit delta modulation, you achieve 8:1 compression but may do a poor job of modeling the data.
The multiplier is the constant multiplier that controls the size of the delta mod. Ideally, the constant multiplier should roughly match the mean value the input data stream is changing from one sample to the next, divided by the total bit resolution.
After writing a number of audio-compression routines that use different bit resolutions and multipliers, I concluded that the best audio-compression routine should use the best bit size and the best multiplier value that best matches the input data stream at any given time. (In short, rather than picking one set of rules to apply on the source data, the algorithm should adapt its modeling technique to match the input data stream itself). When no bit size or multiplier will model the data as accurately as you wish, then you store that data out in raw format, causing a resynchronization to occur, and assuring that the sound quality will not be compromised. When using ACOMP, the user specifies the mean acceptable error. ACOMP will then try every multiplier and bit size possible at every point in the input data stream, ensuring that the mean error never exceed that value. The result yields high compression rates with almost no static. If your application can live with a little more static, you can pump the mean error value up to 6 or 10 and get vastly higher compression rates. (The mean error value indicates that on the average, no reconstructed data sample is off by more than +/- that amount from the original data sample.)
To accomplish this task, ACOMP needs to represent the following:
- Silence Encoding, the duration of a pause in the original sample.
- Resynchronization, a raw data sample to avoid introducing error.
- Bit Size, the number of bits used to represent the delta mod (1, 2, or 4).
- Multiplier, the size of the delta mod from one to the next, valid 1-16.
Figure 1: ACOMP file format.
BYTE 0-1: Length of audio sample, decompressed size, in 8086 low/high format BYTE 2-3: Recording frequency of audio sample, low/high format BYTE 4: Frame size, 8-248 BYTE 5: Squelch size used at compression time BYTE 6-7: Maximum error used at compression time BYTE 8: Initial audio sample BYTE 9: First header byte Bit 7: RESYNC. If bit 7 is on, then the bottom 7 bits of this byte represent a resynchronization value, rounded to the closest multiple of 2. Take the bottom 7 bits, left shift them once, and this 8-bit value represents the resynchronized data-stream value. Bit 6: SQUELCH. If bit 5 is on and bit 7 is off, then this is a silence-squelching command byte. The bottom 6 bits represent a repeat count of the current data value. Bit 4-5: Delta-mod bit size. If bit 7 and bit 6 are off, then these 2 bits represent the delta-mod size used to delta modulate the frame of data. 01 represents 1-bit modulation, 10 represents 2-bit delta modulation, and 11 represents 4-bit delta modulation. Bit 0-3: Represents delta-mod multiplier value. Equal to the value of this nibble plus 1. Valid multipliers are 1-16. If you receive a delta-modulation header byte, record the delta-modulation bit size and the multiplier value. Then grab bytes of delta-mod data until a full frame of data samples has been exhausted. The format for delta modulation values are as follows: 1-bit delta mod: 0 --> -1*multiplier 1 --> +1*multiplier 2-bit delta mod: 00 --> -2*multiplier 01 --> -1*multiplier 10 --> +1*multiplier 11 --> +2*multiplier 4-bit delta mod: 0000 --> -8*multiplier 0001 --> -7*multiplier 0010 --> -6*multiplier 0011 --> -5*multiplier 0100 --> -4*multiplier 0101 --> -3*multiplier 0110 --> -2*multiplier 0111 --> +1*multiplier 1000 --> +1*multiplier 1001 --> +2*multiplier 1010 --> +3*multiplier 1011 --> +4*multiplier 1100 --> +5*multiplier 1101 --> +6*multiplier 1110 --> +7*multiplier 1111 --> +8*multiplier
For each data sample, the delta-mod value is added to its predecessor. You must make certain that the byte doesn't under- or overflow. Once the frame of data samples has been exhausted, you go back up to the top, looking for the next header byte. You do this until the entire audio file has been decompressed.
Figure 2: Pseudocode for the ACOMP algorithm.
While there are data samples left to process: If current data can be silence encoded, then do so. else (For 1 Frame) Try every possible multiplier value 1-16 using 1-bit modulation, keeping the best. If the best is within the error threshold, store frame as 1 bit. else Try every possible multiplier value 1-16 using 2-bit modulation, keeping the best. If the best is within the error threshold, store frame as 2 bit. else Try every possible multiplier value 1-16 using 4-bit modulation, keeping the best. If the best is within the error threshold, store frame as 4 bit. else Store a resynchronization value.
Note that ACOMP is far from a real-time compression algorithm. Because it does an exhaustive search for the best possible bit resolution and delta-modulation value for every frame of data, it can take a substantial amount of time to compress a sound file. However, you can decompress the resulting audio data in near real-time. A faster compression algorithm could be developed by using some frequency analysis on the input data stream rather than performing an exhaustive search.
The data definition for ACOMP-compressed audio supports compressed audio in sizes no greater than 64K in length. Because sound files can exceed this, an extended ACOMP file format has been defined; see Figure 3. This format is composed of a collection of individual ACOMP-compressed data frames. The default buffer size is 64K, but you can select a different buffer size using the command-line switch /b. If the input data extreme exceeds the current size, then an extended ACOMP audio file (comprised of n frames of audio data) is produced. One advantage to this technique is that the decompression code need only allocate memory in the size of this buffer to decompress even a very large audio file.
Figure 3: Extended ACOMP file format: Supports n frames of ACOMP-compressed data.
// ABX file format: // Bytes 0-1: int TotalFrames; Total number of ACOMP frames in file. // Bytes 2-5: long int TotalSize; Total size of original source file. // Bytes 6-7: unsigned int bufsize; Frame buffer size used to compress in. // Bytes 8-9: Unsigned int freq; Playback frequency of audio file. // .... ABH HEADERS[TotalFrames] Array of headers indicating all // audio frame data. typedef struct { long int fileaddress; // Address in file of this audio section. unsigned int fsize; // compressed file size. unsigned int usize; // uncompressed file size. } ABH;
A standardized file format for compressed digitized sound is a valuable resource. ACOMP is efficient enough at compressing human speech that it actually becomes practical to send voice data over a normal modem communication channel. Applications for this kind of audio compression extend to anywhere you would want to send voice data across a communications channel, or incorporate voice data into a computer game, online help system, or multimedia application.
_AUDIO COMPRESSION_ by John W. Ratcliff[LISTING ONE]
<a name="0181_000a"> ;; AC.ASM -> ACOMP assembly language compressor. Written by John W. Ratcliff, ;; 1991. Uses Turbo Assembler IDEAL mode and makes HEAVE use of macros. This ;; algorithm performs an exhaustive search for the best delta mod for each ;; section of the waveform. It is very CPU intensive, and the algorithm can ;; be a little difficult to follow in assembly language. IDEAL ; Enter Turbo Assembler's IDEAL mode. JUMPS ; Allow automatic jump sizing. INCLUDE "prologue.mac" ; Include usefull assembly lanugage macro file. SEGMENT _TEXT BYTE PUBLIC 'CODE' ASSUME CS:_TEXT ;; This macro computes the amount of error in a frame of data, at this ;; bit resolution and this delta mod location. Macro DoError BITS LOCAL @@NO mov bx,[COMP] ; Current delta mod value. mov bh,BITS ; Current bit resolution. push [MINERR] ; Pass minimum error so far. push [PREV] ; Pass previous data point. call ComputeError ; Compute error for frame. add sp,4 ; Balance stack. cmp dx,[MINERR] ; Less than previous minimum? jge @@NO ; no, don't update. mov [MINERR],dx ; Save as new miniume mov [BESTPREV],ax ; Best previous location. xor ah,ah ; mov al,bl ; Get best delta modution. mov [BEST1],ax ; save it. mov al,bh ; Get best bits. mov [BEST2],ax ; Save it. @@NO: endm SQLCH equ 64 ; Squelch bit. RESYNC equ 128 ; Resynchronization bit. DELTAMOD equ 00110000b ; Bit mask for delta mod bits. ONEBIT equ 00010000b ; Bit pattern for one bit delta mod. TWOBIT equ 00100000b ; Bit pattern for two bit delta mod. FOURBIT equ 00110000b ; Bit pattern for two bit delta mod. ;; This macro echos a message to the text screen, so that we can ;; monitor the progress of the compression algorithm. Macro Note MSG push ax lea ax,[MSG] push ax call Notify add sp,2 pop ax endm public _CompressAudio ;;This the the ACOMP compression procedure. ;;int far CompressAudio(unsigned char far *shand, ;; Address of audio data to compress. ;; unsigned char far *dhand, ;; Destination address of compressed data. ;; unsigned int slen, Length of audio data to compress. ;; int squelch, Squelch value allowed. ;; int freq, Playback frequency of audio data. ;; int frame, Frame size. ;; int maxerr); Maximum error allowed. Proc _CompressAudio far ARG SHAN:DWORD,DHAN:DWORD,SLEN:WORD,SQUELCH:WORD,FREQ:WORD, FRAME:WORD,MAXERR:WORD LOCAL PREV:WORD,COMP:WORD,MINERR:WORD,BEST1:WORD,BEST2: WORD,BESTPREV:WORD = LocalSpace PENTER LocalSpace PushCREGS lds si,[SHAN] ; Get source address. les di,[DHAN] ; Get destination address. mov cx,[SLEN] ; Get length of audio data. mov ax,cx ; Get length of audio sample into AX stosw ; Store it. mov ax,[FREQ] ; Get frequency of recording. stosw ; Store it. mov ax,[FRAME] ; Get the frame size. stosb ; Store it. mov ax,[SQUELCH] ; Get squelch size stosb ; Store it. mov ax,[MAXERR] ; Get maximum error allowed. stosw ; Save it. xor ax,ax lodsb ; Get first data sample mov [PREV],ax stosb ; Store first data sample. dec cx ; Decrement sample size count. jz @@DONE @@SQU: mov ah,0Bh ; Test keyboard status. int 21h or al,al jz @@NOK mov ah,08h ; If a key was pressed get that key int 21h ; value, and see if it was the cmp al,27 ; escape key. jne @@NOK xor ax,ax ; If escape, return to caller, with abort. jmp @@EXIT @@NOK: xor ax,ax mov dx,[SQUELCH] ; Get squelch value. push cx ; Save remaining data count. push si ; Save si. @@CK1: lodsb ; Get data byte. sub ax,[PREV] ; Difference from previous data sample? jns @@CK2 ; if positive leave it alone. neg ax ; Make it positive. @@CK2: cmp ax,dx ; Is it within the squelch range? jg @@NOS ; yes, keep checking! loop @@CK1 ; Keep going. inc si ; Plus one, this last one counts. @@NOS: pop ax ; Get back start SI mov dx,si ; DX contains current address. sub dx,ax ; Compute number of squelch bytes encountered. dec dx ; Less, last non squelch byte. cmp dx,3 ; At least three? jle @@NOSQ ; no, don't squelch it. @@SQS: cmp dx,63 ; Is it under 63? jle @@SEND mov ax,(63 + SQLCH) ; Send count. sub dx,63 ; Less the 63 count we just sent. stosb ; Write squelch byte out. jmp short @@SQS ; Continue. @@SEND: mov ax,dx ; Get remaining count. or ax,SQLCH ; Or squelch bit on. stosb ; Send squelch count out. dec si ; Back up to last data point. pop ax ; Pull CX off of stack, use current count. Note msg0 jmp short @@NXT @@NOSQ: mov si,ax ; Replace where source was. pop cx ; Get back remaining data count. @@NXT: jcxz @@DONE ; Exit if done. cmp cx,[FRAME] ; Below current frame size? jae @@GO ; no, go ahead. @@FIN: lodsb ; Get raw sample. shr al,1 ; Down to closest aproximated value. or al,RESYNC ; Add resync bit to it. stosb ; Store out. loop @@FIN ; Keep sending final bytes. jmp @@DONE ; exit, after sending final bytes. @@GO: mov [MINERR],07FFFh push cx mov cx,[FRAME] ; Set CX to frame size. mov [COMP],1 @@ALL1: DoError 1 ; Try one bit mode, +/-1. inc [COMP] cmp [COMP],17 ; Try delta comp values clean up to 16!! jne @@ALL1 mov ax,[MINERR] cmp ax,[MAXERR] jle @@BCMP ; Not good enough... mov [COMP],1 @@ALL2: DoError 2 ; Try two bit mode, +/-1. inc [COMP] cmp [COMP],17 ; Try delta comp values clean up to 16!! jne @@ALL2 mov ax,[MINERR] cmp ax,[MAXERR] jle @@BCMP mov [COMP],1 @@ALL4: DoError 8 ; Try four bit mode, +/-1. inc [COMP] cmp [COMP],17 ; Try delta comp values clean up to 16!! jne @@ALL4 mov ax,[MINERR] ; Get what the minimum error was. cmp ax,[MAXERR] ; Minimum error > maximum error? jle @@BCMP ; no, then send frame. pop cx ; Get back CX lodsb ; Get data sample. and al,(NOT 1) ; Strip off bottom bit. xor ah,ah mov [PREV],ax ; New previous. shr al,1 ; /2 or al,RESYNC ; Or resync bit on. stosb ; Store it out into data stream. Note msg1 loop @@SQU ; Go check squelching. jmp @@DONE ; Done, if this was last data sample. @@BCMP: mov bx,[BEST1] ; Get best comp. mov ax,[BEST2] ; Get best bit size. mov bh,al ; Into BH mov ax,32000 push ax push [PREV] ; Pass prev. call ComputeError ; Re-compute error term. add sp,4 mov [PREV],ax ; New previous. ;; Now time to store results! mov bx,[BEST1] ; Get best comp. cmp [BEST2],1 ; 1 bit? jne @@NXT1 call Fold1Bit ; Fold 1 bit data. Note msg2 jmp short @@IN ; Reenter. @@NXT1: cmp [BEST2],2 ; 2 bit data? jne @@NXT2 call Fold2Bit Note msg3 jmp short @@IN @@NXT2: call Fold4Bit Note msg4 @@IN: mov ax,[FRAME] pop cx ; Get back CX add si,ax ; Advance source sub cx,ax ; Decrement data count. jnz @@SQU ; Continue, if not at end. @@DONE: mov ax,di ; Size of compressed file. les di,[DHAN] sub ax,di ; Difference. @@EXIT: PopCREGS PLEAVE ret endp ;; Compute error: Registers on entry are: ;; DS:SI -> source data. ;; CX -> number of bytes to compute error term in. ;; DX -> total error incurred. ;; BL -> delta comp size. ;; BH -> maximum bit size value, positive or negative. ;; Exit: CX,DS:SI stay the same. ;; DX -> total error term. ;; AX -> new previous. Proc ComputeError near ARG PREV:WORD,MINERR:WORD LOCAL CUR:WORD = LocalSpace PENTER LocalSpace push cx push si push di ; Save destination address. xor dx,dx ; Initally no error. @@CERR: lodsb ; Get a data byte. xor ah,ah ; Zero high byte. mov [CUR],ax ; Save as current sample. sub ax,[PREV] cmp bl,1 je @@ND idiv bl ; Divided by delta mod size. @@ND: or al,al js @@DON ; Do negative side. jnz @@CNT ; If not zero then continue. inc al ; Can't represent a zero, make it one. @@CNT: cmp al,bh ; > max representative size? jle @@OK ; no, it fit as is. mov al,bh ; Make it the max representative size. jmp short @@OK ; @@DON: neg al ; Make it positive. cmp al,bh ; > max representative size? jbe @@K2 ; no, use it. mov al,bh ; Make it the max representative size. @@K2: neg al ; Make it negative again. @@OK: stosb ; Store data value out. imul bl ; Times delta comp value. add ax,[PREV] ; Add to previous data point. js @@CS ; Do signed case. cmp ax,255 ; Did it over flow? jle @@K3 ; No, then it fit byte sized. mov ax,255 ; Make it byte sized. jmp short @@K3 ; Re-enter @@CS: xor ax,ax ; Close as we can get, underflow. @@K3: mov [PREV],ax ; This is our new aproximated value. sub ax,[CUR] ; Less actual value. jns @@K4 ; if positive then fine. neg ax ; Take absolute value. @@K4: add dx,ax ; Add into total error. cmp dx,[MINERR] ; Greater than minimum error allowed? jg @@OUT loop @@CERR @@OUT: mov ax,[PREV] ; Current previous data point. pop di ; Restore destination address. pop si ; Reset SI back to start. pop cx ; Reset CX back to start. PLEAVE ret endp Macro BuildByte LOCAL @@HOP1,@@HOP2 lodsb or al,al ; Is it signed? jns @@HOP1 shl ah,1 ; Rotate. jmp short @@HOP2 @@HOP1: stc rcl ah,1 @@HOP2: endm ;; Fold 1 bit data. ;; ES:DI -> points to data ready to fold out. ;; CX-> frame size. ;; BL-> contains delta size. Proc Fold1Bit near push ds push si push di ; Header byte address. push es pop ds ; DS=ES mov si,di ; Source and dest. inc di ; skip past header byte. @@FOLD: xor ah,ah ; Dest byte to be built, zero it. BuildByte BuildByte BuildByte BuildByte BuildByte BuildByte BuildByte BuildByte mov al,ah stosb ; Store it out. sub cx,8 ; Less the 8 samples just folded up. jnz @@FOLD ; Continue. pop si ; Get back header byte address. mov al,bl ; Get delta comp size. dec al ; Less one. or al,ONEBIT ; Or the One Bit mode flag on. mov [ds:si],al ; Store header byte. pop si pop ds ret endp ;; 2 Bit Format: 00 -> -2 ;; 01 -> -1 ;; 10 -> +1 ;; 11 -> +2 Macro BByte LOCAL @@HOP1,@@HOP2 lodsb or al,al ; Is it signed? jns @@HOP1 add al,2 ; Adjust it. jmp short @@HOP2 @@HOP1: inc al ; Plus 1 to fit into format size. @@HOP2: shl ah,1 shl ah,1 or ah,al ; Place bits into byte being built. endm ;; Fold 2 bit data. ;; ES:DI -> points to data ready to fold out. ;; CX-> frame size. ;; BL-> contains delta size. Proc Fold2Bit near push ds push si @@F2: push di ; Header byte address. push es pop ds ; DS=ES mov si,di ; Source and dest. inc di ; skip past header byte. @@FOLD: xor ah,ah ; Dest byte to be built, zero it. BByte BByte BByte BByte mov al,ah stosb ; Store it out. sub cx,4 ; Folded up 4 samples. jnz @@FOLD ; Continue. pop si ; Get back header byte address. mov al,bl ; Get delta comp size. dec al ; Less one. or al,TWOBIT ; Or the One Bit mode flag on. mov [ds:si],al ; Store header byte. pop si pop ds ret endp ;; Four bit format: ;; 0 -> -8 ;; 1 -> -7 ;; 2 -> -6 ;; 3 -> -5 ;; 4 -> -4 ;; 5 -> -3 ;; 6 -> -2 ;; 7 -> -1 ;; 8 -> +1 ;; 9 -> +2 ;;10 -> +3 ;;11 -> +4 ;;12 -> +5 ;;13 -> +6 ;;14 -> +7 ;;15 -> +8 Macro Adjust4bit LOCAL @@HOP1,@@HOP2 lodsb or al,al jns @@HOP1 add al,8 ; Adjust it. jmp short @@HOP2 @@HOP1: add al,7 ; Adjust it. @@HOP2: endm ;; Fold 4 bit data. ;; ES:DI -> points to data ready to fold out. ;; CX-> frame size. ;; BL-> contains delta size. Proc Fold4Bit near push ds push si push di ; Header byte address. push es pop ds ; DS=ES mov si,di ; Source and dest the same. inc di ; skip past header byte. @@FOLD: Adjust4bit ; Get first sample. ShiftL al,4 ; Into high nibble. mov ah,al ; Into AH Adjust4bit ; Get next nibble. or al,ah ; One whole byte. stosb ; Store it out. sub cx,2 ; Folded up 4 samples. jnz @@FOLD ; Continue. pop si ; Get back header byte address. mov al,bl ; Get delta comp size. dec al ; Less one. or al,FOURBIT ; Or the One Bit mode flag on. mov [ds:si],al ; Store header byte. pop si pop ds ret endp msg0 db "SQUELCH" msg1 db "RESYNC " msg2 db "1 BIT " msg3 db "2 BIT " msg4 db "4 BIT " Proc Notify near ARG MSG:WORD PENTER 0 PushAll push cs pop ds mov ax,0B800h mov es,ax mov si,[MSG] xor di,di mov ah,1Fh mov cx,7 @@SND: lodsb stosw loop @@SND PopAll PLEAVE ret endp ENDS END <a name="0181_000b"> <a name="0181_000c">[LISTING TWO]
<a name="0181_000c"> ;; UC.ASM -> Uncompress ACOMP compressed audio data. ;; Written by John W. Ratcliff, 1991. ;; Uses Turbo Assembler IDEAL mode. IDEAL ; Enter Turbo Assembler IDEAL mode. JUMPS ; Allow automatic jump sizing. INCLUDE "prologue.mac" ; Include common useful assembly macros. SMALL_MODEL equ 0 ;: true only if trying to generate near calls SETUPSEGMENT ; Setup _TEXT segment. Macro CPROC name public _&name IF SMALL_MODEL Proc _&name near ELSE Proc _&name far ENDIF endm SQLCH equ 64 ; Squelch byte flag RESYNC equ 128 ; Resync byte flag. DELTAMOD equ 00110000b ; Bit mask for delta mod bits. ONEBIT equ 00010000b ; Bit pattern for one bit delta mod. TWOBIT equ 00100000b ; Bit pattern for two bit delta mod. FOURBIT equ 00110000b ; Bit pattern for two bit delta mod. base dw ? ; Base address inside translate table. TRANS db -8,-7,-6,-5,-4,-3,-2,-1,1,2,3,4,5,6,7,8 db -16,-14,-12,-10,-8,-6,-4,-2,2,4,6,8,10,12,14,16 db -24,-21,-18,-15,-12,-9,-6,-3,3,6,9,12,15,18,21,24 db -32,-28,-24,-20,-16,-12,-8,-4,4,8,12,16,20,24,28,32 db -40,-35,-30,-25,-20,-15,-10,-5,5,10,15,20,25,30,35,40 db -48,-42,-36,-30,-24,-18,-12,-6,6,12,18,24,30,36,42,48 db -56,-49,-42,-35,-28,-21,-14,-7,7,14,21,28,35,42,49,56 db -64,-56,-48,-40,-32,-24,-16,-8,8,16,24,32,40,48,56,64 db -72,-63,-54,-45,-36,-27,-18,-9,9,18,27,36,45,54,63,72 db -80,-70,-60,-50,-40,-30,-20,-10,10,20,30,40,50,60,70,80 db -88,-77,-66,-55,-44,-33,-22,-11,11,22,33,44,55,66,77,88 db -96,-84,-72,-60,-48,-36,-24,-12,12,24,36,48,60,72,84,96 db -104,-91,-78,-65,-52,-39,-26,-13,13,26,39,52,65,78,91,104 db -112,-98,-84,-70,-56,-42,-28,-14,14,28,42,56,70,84,98,112 db -120,-105,-90,-75,-60,-45,-30,-15,15,30,45,60,75,90,105,120 db -128,-112,-96,-80,-64,-48,-32,-16,16,32,48,64,80,96,112,127 CPROC GetFreq ; Report playback frequency for an ACOMP file. ARG SOURCE:DWORD PENTER 0 push es les bx,[SOURCE] mov ax,[es:bx+2] pop es PLEAVE ret endp ;; DX contains PREVIOUS. ;; AH contains bit mask being rotated out. ;; BX up/down 1 bit value. Macro Delta1 LOCAL @@UP,@@STORE shl ah,1 ; Rotate bit mask out. jc @@UP sub dx,bx jns @@STORE xor dx,dx ; Zero it out. jmp short @@STORE @@UP: add dx,bx or dh,dh jz @@STORE mov dx,255 @@STORE:mov al,dl ; Store result. stosb endm ;; BX-> base address of translate table. ;; DX-> previous. ;; AL-> index. Macro DeModulate LOCAL @@HIGH,@@OK xlat [cs:bx] ; Translate into lookup table. cbw ; Make it a signed word. add dx,ax ; Do word sized add, into previous. jns @@HIGH xor dx,dx ; Underflowed. @@HIGH: or dh,dh ; Did it overflow? jz @@OK mov dx,255 ; Maxed out. @@OK: mov al,dl stosb endm ;;unsigned int far UnCompressAudio(unsigned char far *source,unsigned char far *dest); ;; UnCompressAudio will decompress data which was compressed using ACOMP ;; into the destination address provided. UnCompressAudio returns the ;; total size, in bytes, of the uncompressed audio data. CPROC UnCompressAudio ARG SHAN:DWORD,DHAN:DWORD LOCAL SLEN:WORD,FREQ:WORD,FRAME:WORD,BITS:WORD = LocalSpace PENTER LocalSpace PushCREGS lds si,[SHAN] ; Get source segment les di,[DHAN] ; Get destination segment lodsw ; Get length. mov [SLEN],ax ; Save length. mov cx,ax ; Into CX lodsw ; Frequency. mov [FREQ],ax ; Save frequency lodsb ; Get frame size. xor ah,ah ; Zero high byte mov [FRAME],ax ; Save it. lodsb ; Get squelch, and skip it. lodsw ; Get maximum error, and skip it. lodsb ; Get initial previous data point. stosb ; Store it. xor ah,ah ; zero high byte. mov dx,ax ; Save into previous word. dec cx ; Decrement total by one. jz @@DONE ; Exit mov ah,al ; AH, always the previous. @@DCMP: lodsb ; Get sample. test al,RESYNC ; Resync byte? jz @@NOTR ; no, skip. shl al,1 ; Times two. mov dl,al ; Into previous. xor dh,dh ; Zero high word. stosb ; Store it. loop @@DCMP ; Next one. jmp @@DONE @@NOTR: test al,SQLCH ; Squelch byte? jz @@FRAM ; no, then it is a frame. and al,00111111b ; Leave just the count. push cx ; Save current countdown counter. mov cl,al ; get repeat count xor ch,ch ; zero high byte of CX mov bx,cx ; Repeat count in DX mov al,dl ; Repeat of previous. rep stosb ; Repeat it. pop cx ; Get back remaining count. sub cx,bx ; Less. jnz @@DCMP ; Keep going. jmp @@DONE @@FRAM: mov bx,ax ; command byte into BX and bx,0Fh ; Multiplier being used. ShiftL bx,4 ; Times 16. add bx,offset TRANS ; Plus address of translate table. and al,DELTAMOD ; Leave just delta mod. push cx mov cx,[FRAME] ; Get frame size. cmp al,ONEBIT ; In one bit delta mod? jne @@NEXT1 ; no, try other. ShiftR cx,3 ; /8 mov bl,[cs:bx+8] ; Get up amount xor bh,bh ; Zero high byte. @@GO: lodsb xchg al,ah ; Place prev in AL, Bit mask in AH Delta1 Delta1 Delta1 Delta1 Delta1 Delta1 Delta1 Delta1 mov ah,al loop @@GO jmp @@RENTER @@NEXT1:cmp al,TWOBIT ; In two bit delta mod mode? jne @@NEXT2 add bx,6 ; Point at +- 2 bit's in table. shr cx,1 shr cx,1 ; 4 samples per byte. @@GOGO: lodsb ShiftR al,6 DeModulate mov al,[ds:si-1] ShiftR al,4 and al,3 DeModulate mov al,[ds:si-1] ShiftR al,2 and al,3 DeModulate mov al,[ds:si-1] and al,3 DeModulate loop @@GOGO jmp short @@RENTER @@NEXT2:shr cx,1 ; Two samples per byte. @@GO2: lodsb ; Get sample. ShiftR al,4 DeModulate mov al,[ds:si-1] and al,0Fh DeModulate loop @@GO2 @@RENTER: pop cx sub cx,[FRAME] jnz @@DCMP ; Continue decompress @@DONE: mov ax,[SLEN] ; Uncompressed length. PopCREGS PLEAVE ret endp ENDS END
Copyright © 1992, Dr. Dobb's Journal