_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