AUDIO COMPRESSION

Digitized sound requires its own compression algorithms

This article contains the following executables: ACOMP.ARC

John W. Ratcliff

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:

Additionally, the ACOMP header needs to indicate the frequency, of the sound sample and the frame size used for delta modulation. Figure 1 details the ACOMP data format, while Figure 2 describes ACOMP in pseudocode. The program AC.ASM (Listing One, page 96) is the source to the compression routine itself. The program UC.ASM (Listing Two, page 99) contains all the code necessary to decompress an ACOMP-compressed audio sample. This decompression algorithm involves a lot of bit-twiddling, something almost all assembly-languages excel at. (Available electronically are assembly language macro headers; a C-prototype header file for UC.ASM; a linkable, object-module version of UC.ASM; a C-prototype header for DOSCALLS.H; C-callable procedures into DOS INT 21 functions; a linkable, object-module version of DOSCALLS.ASM; executable versions of ACOMP and UCOMP; documentation of the ACOMP file format and extended ACOMP file definitions; demonstration batch files; demonstration sound files; a C version of the decompression program, and an IBM internal-speaker sound driver.)

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]


;; 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






[LISTING TWO]


;; 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

Back to Table of Contents